@eifi1/ui-kit 0.32.2 → 0.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. package/README.md +17 -9
  2. package/dist/account/delete-account-setting.d.ts +11 -10
  3. package/dist/account/delete-account-setting.js +2 -1
  4. package/dist/account/delete-account-setting.js.map +1 -1
  5. package/dist/account/email-change-setting.d.ts +5 -0
  6. package/dist/account/email-change-setting.js +4 -3
  7. package/dist/account/email-change-setting.js.map +1 -1
  8. package/dist/account/sessions-setting.d.ts +4 -0
  9. package/dist/account/sessions-setting.js +3 -2
  10. package/dist/account/sessions-setting.js.map +1 -1
  11. package/dist/admin/admin-action-confirm.d.ts +11 -10
  12. package/dist/admin/admin-action-confirm.js.map +1 -1
  13. package/dist/admin/invitations-panel.d.ts +11 -10
  14. package/dist/admin/invitations-panel.js +4 -3
  15. package/dist/admin/invitations-panel.js.map +1 -1
  16. package/dist/admin/plan-change.d.ts +101 -0
  17. package/dist/admin/plan-change.js +210 -0
  18. package/dist/admin/plan-change.js.map +1 -0
  19. package/dist/admin/reviewer-scope-editor.d.ts +11 -10
  20. package/dist/admin/reviewer-scope-editor.js.map +1 -1
  21. package/dist/admin/role-select.d.ts +8 -7
  22. package/dist/admin/role-select.js.map +1 -1
  23. package/dist/admin/transfer-ownership-dialog.d.ts +11 -10
  24. package/dist/admin/user-roster.d.ts +8 -7
  25. package/dist/admin/user-roster.js +2 -2
  26. package/dist/admin/user-roster.js.map +1 -1
  27. package/dist/auth/auth-errors.d.ts +13 -2
  28. package/dist/auth/auth-errors.js +5 -1
  29. package/dist/auth/auth-errors.js.map +1 -1
  30. package/dist/auth/complete-name-dialog.d.ts +4 -1
  31. package/dist/auth/complete-name-dialog.js +2 -1
  32. package/dist/auth/complete-name-dialog.js.map +1 -1
  33. package/dist/auth/register-form.d.ts +11 -10
  34. package/dist/billing/billing-banner.d.ts +14 -11
  35. package/dist/billing/billing-banner.js.map +1 -1
  36. package/dist/billing/billing-labels.d.ts +11 -10
  37. package/dist/billing/billing-labels.js +10 -1
  38. package/dist/billing/billing-labels.js.map +1 -1
  39. package/dist/billing/billing-lock.d.ts +40 -12
  40. package/dist/billing/billing-lock.js +9 -3
  41. package/dist/billing/billing-lock.js.map +1 -1
  42. package/dist/billing/billing-overview.d.ts +155 -0
  43. package/dist/billing/billing-overview.js +2 -0
  44. package/dist/billing/billing-overview.js.map +1 -0
  45. package/dist/billing/billing-standing.d.ts +11 -10
  46. package/dist/billing/checkout-processing.d.ts +218 -0
  47. package/dist/billing/checkout-processing.js +198 -0
  48. package/dist/billing/checkout-processing.js.map +1 -0
  49. package/dist/billing/checkout-return.d.ts +51 -0
  50. package/dist/billing/checkout-return.js +43 -0
  51. package/dist/billing/checkout-return.js.map +1 -0
  52. package/dist/billing/pay-page.d.ts +215 -0
  53. package/dist/billing/pay-page.js +113 -0
  54. package/dist/billing/pay-page.js.map +1 -0
  55. package/dist/billing/plan-card.d.ts +11 -10
  56. package/dist/billing/plan-limit-notice.d.ts +11 -10
  57. package/dist/billing/plan-limit-toast.d.ts +144 -0
  58. package/dist/billing/plan-limit-toast.js +40 -0
  59. package/dist/billing/plan-limit-toast.js.map +1 -0
  60. package/dist/billing/subscription-actions.d.ts +39 -15
  61. package/dist/billing/subscription-actions.js +9 -2
  62. package/dist/billing/subscription-actions.js.map +1 -1
  63. package/dist/billing/subscription-status-chip.d.ts +10 -9
  64. package/dist/bin/eifi1-pay-page.mjs +166 -0
  65. package/dist/chart.d.ts +11 -10
  66. package/dist/components/account-chips.d.ts +6 -1
  67. package/dist/components/account-settings.d.ts +4 -1
  68. package/dist/components/account-settings.js +2 -1
  69. package/dist/components/account-settings.js.map +1 -1
  70. package/dist/components/amount-input.d.ts +11 -10
  71. package/dist/components/autocomplete.d.ts +2 -1
  72. package/dist/components/autocomplete.js +5 -3
  73. package/dist/components/autocomplete.js.map +1 -1
  74. package/dist/components/button-group.d.ts +11 -10
  75. package/dist/components/button-group.js +1 -1
  76. package/dist/components/button-group.js.map +1 -1
  77. package/dist/components/calculator.d.ts +11 -10
  78. package/dist/components/calculator.js +3 -3
  79. package/dist/components/calculator.js.map +1 -1
  80. package/dist/components/checkbox-group.d.ts +2 -1
  81. package/dist/components/checkbox-group.js.map +1 -1
  82. package/dist/components/checkbox.d.ts +2 -1
  83. package/dist/components/checkbox.js.map +1 -1
  84. package/dist/components/chip.d.ts +2 -1
  85. package/dist/components/chip.js +19 -17
  86. package/dist/components/chip.js.map +1 -1
  87. package/dist/components/choice-card.d.ts +3 -1
  88. package/dist/components/choice-card.js +1 -1
  89. package/dist/components/choice-card.js.map +1 -1
  90. package/dist/components/combobox-core.js +2 -1
  91. package/dist/components/combobox-core.js.map +1 -1
  92. package/dist/components/combobox.d.ts +4 -3
  93. package/dist/components/combobox.js +2 -2
  94. package/dist/components/combobox.js.map +1 -1
  95. package/dist/components/confirm-dialog.d.ts +11 -10
  96. package/dist/components/confirm-dialog.js +6 -3
  97. package/dist/components/confirm-dialog.js.map +1 -1
  98. package/dist/components/copy-button.d.ts +12 -11
  99. package/dist/components/country-select.d.ts +2 -1
  100. package/dist/components/country-select.js.map +1 -1
  101. package/dist/components/danger-confirm.d.ts +11 -10
  102. package/dist/components/danger-confirm.js.map +1 -1
  103. package/dist/components/data-table-cells.d.ts +12 -1
  104. package/dist/components/data-table-filter-popover.d.ts +12 -1
  105. package/dist/components/data-table-filters.d.ts +12 -1
  106. package/dist/components/data-table-labels.d.ts +12 -1
  107. package/dist/components/data-table-pagination.d.ts +12 -1
  108. package/dist/components/data-table.d.ts +12 -1
  109. package/dist/components/data-table.js +3 -3
  110. package/dist/components/data-table.js.map +1 -1
  111. package/dist/components/date-picker.js +6 -4
  112. package/dist/components/date-picker.js.map +1 -1
  113. package/dist/components/dropdown.js +9 -1
  114. package/dist/components/dropdown.js.map +1 -1
  115. package/dist/components/entity-combobox.d.ts +3 -2
  116. package/dist/components/entity-combobox.js.map +1 -1
  117. package/dist/components/facing-pair.d.ts +11 -10
  118. package/dist/components/field-parts.d.ts +32 -4
  119. package/dist/components/field-parts.js +54 -19
  120. package/dist/components/field-parts.js.map +1 -1
  121. package/dist/components/file-button.d.ts +11 -10
  122. package/dist/components/file-button.js +3 -2
  123. package/dist/components/file-button.js.map +1 -1
  124. package/dist/components/file-dropzone.d.ts +12 -11
  125. package/dist/components/file-dropzone.js.map +1 -1
  126. package/dist/components/floating-panel.js +5 -5
  127. package/dist/components/floating-panel.js.map +1 -1
  128. package/dist/components/form-actions.d.ts +10 -9
  129. package/dist/components/form-actions.js +3 -3
  130. package/dist/components/form-actions.js.map +1 -1
  131. package/dist/components/full-bleed-dialog.js.map +1 -1
  132. package/dist/components/iban-input.d.ts +11 -10
  133. package/dist/components/inline-edit-field.d.ts +2 -1
  134. package/dist/components/inline-edit-field.js +6 -6
  135. package/dist/components/inline-edit-field.js.map +1 -1
  136. package/dist/components/language-select.d.ts +11 -10
  137. package/dist/components/legal-page.d.ts +11 -10
  138. package/dist/components/legal.d.ts +11 -10
  139. package/dist/components/list.d.ts +2 -1
  140. package/dist/components/list.js +4 -5
  141. package/dist/components/list.js.map +1 -1
  142. package/dist/components/modal.js.map +1 -1
  143. package/dist/components/money-field.d.ts +11 -10
  144. package/dist/components/month-picker.js +54 -44
  145. package/dist/components/month-picker.js.map +1 -1
  146. package/dist/components/multi-entity-combobox.d.ts +3 -2
  147. package/dist/components/multi-entity-combobox.js.map +1 -1
  148. package/dist/components/number-field.d.ts +18 -10
  149. package/dist/components/number-field.js.map +1 -1
  150. package/dist/components/number-input.d.ts +37 -10
  151. package/dist/components/number-input.js +27 -12
  152. package/dist/components/number-input.js.map +1 -1
  153. package/dist/components/numpad-sheet.d.ts +11 -10
  154. package/dist/components/numpad-sheet.js +2 -2
  155. package/dist/components/numpad-sheet.js.map +1 -1
  156. package/dist/components/phone-input.d.ts +11 -10
  157. package/dist/components/popover.js +6 -2
  158. package/dist/components/popover.js.map +1 -1
  159. package/dist/components/row-actions.d.ts +6 -5
  160. package/dist/components/row-actions.js +130 -32
  161. package/dist/components/row-actions.js.map +1 -1
  162. package/dist/components/series-chart.d.ts +11 -10
  163. package/dist/components/settings-fields.d.ts +11 -10
  164. package/dist/components/share-card.d.ts +8 -7
  165. package/dist/components/share-card.js +26 -10
  166. package/dist/components/share-card.js.map +1 -1
  167. package/dist/components/skeleton.d.ts +10 -2
  168. package/dist/components/skeleton.js.map +1 -1
  169. package/dist/components/status-dot.d.ts +1 -0
  170. package/dist/components/status-dot.js +2 -2
  171. package/dist/components/status-dot.js.map +1 -1
  172. package/dist/components/swatch-picker.d.ts +2 -1
  173. package/dist/components/swatch-picker.js.map +1 -1
  174. package/dist/components/swipeable-row.d.ts +88 -17
  175. package/dist/components/swipeable-row.js +46 -9
  176. package/dist/components/swipeable-row.js.map +1 -1
  177. package/dist/components/switch.d.ts +2 -1
  178. package/dist/components/switch.js.map +1 -1
  179. package/dist/components/table.d.ts +2 -1
  180. package/dist/components/table.js +6 -0
  181. package/dist/components/table.js.map +1 -1
  182. package/dist/components/text-link.d.ts +10 -9
  183. package/dist/components/time-input.d.ts +11 -10
  184. package/dist/components/toggle-group.d.ts +2 -1
  185. package/dist/components/toggle-group.js +1 -1
  186. package/dist/components/toggle-group.js.map +1 -1
  187. package/dist/components/translation-review-editor.d.ts +4 -0
  188. package/dist/components/translation-review-editor.js +4 -3
  189. package/dist/components/translation-review-editor.js.map +1 -1
  190. package/dist/components/translation-review-parts.d.ts +1 -0
  191. package/dist/components/translation-review.d.ts +1 -0
  192. package/dist/components/translation-review.js +15 -18
  193. package/dist/components/translation-review.js.map +1 -1
  194. package/dist/components/ui.d.ts +10 -9
  195. package/dist/components/ui.js +47 -27
  196. package/dist/components/ui.js.map +1 -1
  197. package/dist/components/use-table-state.d.ts +12 -1
  198. package/dist/components/user-avatar.d.ts +1 -0
  199. package/dist/components/write-lock.d.ts +101 -13
  200. package/dist/components/write-lock.js +37 -13
  201. package/dist/components/write-lock.js.map +1 -1
  202. package/dist/{data-table-labels-kpAAVi6g.d.ts → data-table-labels-BJ5JE_X2.d.ts} +17 -0
  203. package/dist/data-table.d.ts +12 -1
  204. package/dist/{delete-account-setting-Dy4kJgUm.d.ts → delete-account-setting-DAgGBrbn.d.ts} +393 -42
  205. package/dist/feedback/feedback-attachment.d.ts +11 -10
  206. package/dist/feedback/feedback-attachment.js +7 -7
  207. package/dist/feedback/feedback-attachment.js.map +1 -1
  208. package/dist/feedback/feedback-context.d.ts +11 -10
  209. package/dist/feedback/feedback-crash.d.ts +11 -10
  210. package/dist/feedback/feedback-dialog.d.ts +11 -10
  211. package/dist/feedback/feedback-inbox.d.ts +10 -9
  212. package/dist/feedback/feedback-inbox.js +7 -3
  213. package/dist/feedback/feedback-inbox.js.map +1 -1
  214. package/dist/feedback/feedback-labels.d.ts +11 -10
  215. package/dist/feedback/feedback-menu.d.ts +4 -3
  216. package/dist/feedback/feedback-record.d.ts +11 -10
  217. package/dist/feedback/feedback-row-detail.d.ts +11 -10
  218. package/dist/feedback/feedback-row-detail.js.map +1 -1
  219. package/dist/feedback/feedback-status-undo.d.ts +11 -10
  220. package/dist/feedback/feedback-submit.d.ts +11 -10
  221. package/dist/feedback/feedback-swipe.d.ts +11 -10
  222. package/dist/feedback/feedback-swipe.js +3 -6
  223. package/dist/feedback/feedback-swipe.js.map +1 -1
  224. package/dist/feedback/feedback-table.d.ts +11 -10
  225. package/dist/feedback/feedback-table.js.map +1 -1
  226. package/dist/feedback/feedback-thread.d.ts +11 -10
  227. package/dist/feedback/feedback-thread.js +5 -1
  228. package/dist/feedback/feedback-thread.js.map +1 -1
  229. package/dist/feedback.d.ts +11 -10
  230. package/dist/hooks/use-anchored-panel.d.ts +37 -7
  231. package/dist/hooks/use-anchored-panel.js +6 -7
  232. package/dist/hooks/use-anchored-panel.js.map +1 -1
  233. package/dist/hooks/use-file-drop.d.ts +11 -10
  234. package/dist/hooks/use-focus-trap.d.ts +8 -0
  235. package/dist/hooks/use-focus-trap.js +17 -11
  236. package/dist/hooks/use-focus-trap.js.map +1 -1
  237. package/dist/i18n/defaults.d.ts +11 -10
  238. package/dist/i18n/defaults.js +2 -0
  239. package/dist/i18n/defaults.js.map +1 -1
  240. package/dist/i18n/german.d.ts +11 -10
  241. package/dist/i18n/german.js +37 -1
  242. package/dist/i18n/german.js.map +1 -1
  243. package/dist/i18n/kit-labels.d.ts +5 -4
  244. package/dist/i18n/kit-labels.js.map +1 -1
  245. package/dist/i18n/languages.d.ts +11 -10
  246. package/dist/i18n/locales/de-CH.d.ts +11 -10
  247. package/dist/i18n/locales/en.d.ts +11 -10
  248. package/dist/i18n/locales/es.d.ts +11 -10
  249. package/dist/i18n/locales/es.js +35 -1
  250. package/dist/i18n/locales/es.js.map +1 -1
  251. package/dist/i18n/locales/fr.d.ts +11 -10
  252. package/dist/i18n/locales/fr.js +37 -1
  253. package/dist/i18n/locales/fr.js.map +1 -1
  254. package/dist/i18n/locales/hu.d.ts +11 -10
  255. package/dist/i18n/locales/hu.js +38 -1
  256. package/dist/i18n/locales/hu.js.map +1 -1
  257. package/dist/i18n/locales/it.d.ts +11 -10
  258. package/dist/i18n/locales/it.js +35 -1
  259. package/dist/i18n/locales/it.js.map +1 -1
  260. package/dist/i18n/locales/zh.d.ts +11 -10
  261. package/dist/i18n/locales/zh.js +35 -1
  262. package/dist/i18n/locales/zh.js.map +1 -1
  263. package/dist/i18n/plurals.d.ts +20 -1
  264. package/dist/i18n/plurals.js +11 -0
  265. package/dist/i18n/plurals.js.map +1 -1
  266. package/dist/i18n/review.d.ts +11 -10
  267. package/dist/i18n/review.js +8 -1
  268. package/dist/i18n/review.js.map +1 -1
  269. package/dist/index.d.ts +11 -6
  270. package/dist/index.js +40 -2
  271. package/dist/index.js.map +1 -1
  272. package/dist/landing/landing-actions.d.ts +11 -10
  273. package/dist/landing/landing-sections.d.ts +11 -10
  274. package/dist/landing/public-header.d.ts +11 -10
  275. package/dist/landing/routing.d.ts +11 -10
  276. package/dist/pay/.well-known/apple-developer-merchantid-domain-association +1 -0
  277. package/dist/pay/index.html +30 -0
  278. package/dist/pay/pay.css +126 -0
  279. package/dist/pay/pay.js +198 -0
  280. package/dist/rhf/fields.d.ts +11 -10
  281. package/dist/rhf/form.d.ts +11 -10
  282. package/dist/rhf.d.ts +11 -10
  283. package/dist/settings/settings-section.d.ts +11 -10
  284. package/dist/shell/app-shell.d.ts +10 -9
  285. package/dist/shell/top-bar-brand.d.ts +11 -10
  286. package/dist/shell/topbar-action-menu.d.ts +1 -0
  287. package/dist/shell.d.ts +11 -10
  288. package/dist/testing/css-variables.d.ts +74 -0
  289. package/dist/testing/css-variables.generated.d.ts +7 -0
  290. package/dist/testing/css-variables.generated.js +8 -0
  291. package/dist/testing/css-variables.generated.js.map +1 -0
  292. package/dist/testing/css-variables.js +93 -0
  293. package/dist/testing/css-variables.js.map +1 -0
  294. package/dist/testing/index.d.ts +1 -0
  295. package/dist/testing/index.js +12 -0
  296. package/dist/testing/index.js.map +1 -0
  297. package/dist/theme/contrast-tokens.d.ts +44 -1
  298. package/dist/theme/contrast-tokens.js +21 -0
  299. package/dist/theme/contrast-tokens.js.map +1 -1
  300. package/dist/theme/contrast.d.ts +2 -2
  301. package/dist/theme/palette-derive.js +60 -11
  302. package/dist/theme/palette-derive.js.map +1 -1
  303. package/dist/theme/palette-presets.js +45 -43
  304. package/dist/theme/palette-presets.js.map +1 -1
  305. package/dist/wizard/stepper-nav.d.ts +12 -11
  306. package/dist/wizard/stepper-nav.js.map +1 -1
  307. package/dist/wizard.d.ts +11 -10
  308. package/package.json +10 -2
  309. package/src/account/delete-account-setting.tsx +6 -1
  310. package/src/account/email-change-setting.tsx +9 -3
  311. package/src/account/sessions-setting.tsx +7 -2
  312. package/src/admin/admin-action-confirm.tsx +2 -1
  313. package/src/admin/invitations-panel.tsx +9 -3
  314. package/src/admin/plan-change.tsx +487 -0
  315. package/src/admin/reviewer-scope-editor.tsx +2 -1
  316. package/src/admin/role-select.tsx +2 -1
  317. package/src/admin/user-roster.tsx +5 -3
  318. package/src/auth/auth-errors.ts +25 -2
  319. package/src/auth/complete-name-dialog.tsx +6 -2
  320. package/src/billing/billing-banner.tsx +3 -1
  321. package/src/billing/billing-labels.ts +45 -0
  322. package/src/billing/billing-lock.ts +36 -3
  323. package/src/billing/billing-overview.ts +58 -0
  324. package/src/billing/checkout-processing.ts +391 -0
  325. package/src/billing/checkout-return.ts +90 -0
  326. package/src/billing/pay-page.ts +246 -0
  327. package/src/billing/plan-limit-toast.tsx +91 -0
  328. package/src/billing/subscription-actions.tsx +41 -8
  329. package/src/components/account-settings.tsx +6 -2
  330. package/src/components/autocomplete.tsx +10 -4
  331. package/src/components/button-group.tsx +4 -1
  332. package/src/components/calculator.tsx +10 -6
  333. package/src/components/checkbox-group.tsx +2 -2
  334. package/src/components/checkbox.tsx +2 -1
  335. package/src/components/chip.tsx +37 -32
  336. package/src/components/choice-card.tsx +4 -2
  337. package/src/components/combobox-core.tsx +5 -2
  338. package/src/components/combobox.tsx +8 -6
  339. package/src/components/confirm-dialog.tsx +13 -6
  340. package/src/components/country-select.tsx +2 -2
  341. package/src/components/danger-confirm.tsx +2 -2
  342. package/src/components/data-table.tsx +20 -9
  343. package/src/components/date-picker.tsx +10 -7
  344. package/src/components/dropdown.tsx +12 -2
  345. package/src/components/entity-combobox.tsx +3 -3
  346. package/src/components/field-parts.tsx +61 -14
  347. package/src/components/file-button.tsx +5 -4
  348. package/src/components/file-dropzone.tsx +2 -2
  349. package/src/components/floating-panel.tsx +7 -5
  350. package/src/components/form-actions.tsx +12 -5
  351. package/src/components/full-bleed-dialog.tsx +6 -0
  352. package/src/components/inline-edit-field.tsx +13 -13
  353. package/src/components/list.tsx +10 -9
  354. package/src/components/modal.tsx +5 -0
  355. package/src/components/month-picker.tsx +8 -2
  356. package/src/components/multi-entity-combobox.tsx +3 -3
  357. package/src/components/number-field.tsx +7 -0
  358. package/src/components/number-input.tsx +71 -11
  359. package/src/components/numpad-sheet.tsx +8 -4
  360. package/src/components/popover.tsx +6 -2
  361. package/src/components/row-actions.tsx +208 -58
  362. package/src/components/share-card.tsx +67 -13
  363. package/src/components/skeleton.tsx +10 -2
  364. package/src/components/status-dot.tsx +7 -7
  365. package/src/components/swatch-picker.tsx +2 -2
  366. package/src/components/swipeable-row.tsx +138 -27
  367. package/src/components/switch.tsx +2 -1
  368. package/src/components/table.tsx +8 -1
  369. package/src/components/toggle-group.tsx +5 -3
  370. package/src/components/translation-review-editor.tsx +8 -3
  371. package/src/components/translation-review.tsx +18 -22
  372. package/src/components/ui.tsx +92 -43
  373. package/src/components/write-lock.tsx +150 -24
  374. package/src/feedback/feedback-attachment.tsx +13 -9
  375. package/src/feedback/feedback-inbox.tsx +10 -6
  376. package/src/feedback/feedback-row-detail.tsx +3 -2
  377. package/src/feedback/feedback-swipe.tsx +6 -11
  378. package/src/feedback/feedback-table.tsx +2 -1
  379. package/src/feedback/feedback-thread.tsx +20 -1
  380. package/src/hooks/use-anchored-panel.ts +60 -14
  381. package/src/hooks/use-focus-trap.ts +55 -12
  382. package/src/i18n/defaults.ts +2 -0
  383. package/src/i18n/german.ts +41 -0
  384. package/src/i18n/kit-labels.tsx +4 -0
  385. package/src/i18n/locales/es.ts +35 -0
  386. package/src/i18n/locales/fr.ts +41 -0
  387. package/src/i18n/locales/hu.ts +39 -0
  388. package/src/i18n/locales/it.ts +36 -0
  389. package/src/i18n/locales/zh.ts +34 -0
  390. package/src/i18n/plurals.ts +29 -0
  391. package/src/i18n/review.ts +7 -0
  392. package/src/index.ts +51 -5
  393. package/src/testing/css-variables.generated.ts +112 -0
  394. package/src/testing/css-variables.ts +191 -0
  395. package/src/testing/index.ts +13 -0
  396. package/src/theme/contrast-tokens.ts +53 -2
  397. package/src/theme/palette-derive.ts +87 -10
  398. package/src/theme/palette-presets.ts +45 -43
  399. package/src/wizard/stepper-nav.tsx +2 -1
  400. package/tokens.css +324 -46
@@ -30,8 +30,8 @@ const PAD_KEYS = [
30
30
  { kind: "ins", label: "+", ins: "+", accent: true, name: "plus" }
31
31
  ];
32
32
  const PAD_BTN = `flex h-14 items-center justify-center rounded-lg text-lg font-medium transition-transform select-none focus:outline-none ${FOCUS_RING} focus-visible:ring-inset active:scale-[0.97]`;
33
- const PAD_DIGIT = "bg-[var(--bg-surface-2)] text-[var(--text-primary)] active:bg-[var(--border)]";
34
- const PAD_ACCENT = "bg-[var(--border)] text-[var(--text-primary)] active:bg-[var(--bg-surface-2)]";
33
+ const PAD_DIGIT = "bg-surface-2 text-primary active:bg-active";
34
+ const PAD_ACCENT = "bg-active text-primary active:bg-surface-2";
35
35
  function NumberPadSheet({
36
36
  value,
37
37
  onChange,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/numpad-sheet.tsx"],"sourcesContent":["import { createPortal } from \"react-dom\";\nimport { Delete } from \"lucide-react\";\nimport { useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { FOCUS_RING } from \"./focus-ring\";\nimport { useEscapeKey } from \"../hooks/use-dismiss\";\nimport { evaluateExpression, formatResult, sanitizeLive } from \"../lib/calc\";\nimport {\n DEFAULT_CALCULATOR_LABELS,\n useKitLabels,\n type CalculatorLabels,\n} from \"../i18n/kit-labels\";\n\n/**\n * A full calculator keypad rendered as a bottom sheet — the mobile counterpart to\n * the desktop {@link CalculatorButton} popover, and the replacement for the old\n * under-the-field operator bar (`MathKeys`, feedback #334).\n *\n * A native app (YNAB) can swap the system keyboard for its own keypad; a PWA\n * cannot — but it CAN suppress the OS keyboard with `inputMode=\"none\"` on the\n * host field and paint its own keypad in the freed space. So the host input keeps\n * real focus/caret/selection + the existing value/commit contract, while this\n * sheet drives it: digits + operators + a live `= …` preview, evaluated through\n * the shared {@link evaluateExpression} engine. Desktop is untouched (it keeps the\n * native keyboard and the calculator popover); the host gates this to mobile.\n *\n * Buttons commit through the host's `onChange`/blur, so the sheet is stateless —\n * the field text is the single source of truth. `onPointerDown` preventDefault on\n * the root keeps the host input focused (caret stays, keyboard stays hidden) as\n * the user taps keys; the buttons' `onClick` still fires.\n */\n/** Names for the pad itself and its four non-digit keys. The first four are\n * screen-reader names; `done` is the one string here that is READ OFF THE SCREEN —\n * the brand-filled primary button — and it was a hardcoded English literal while\n * its three neighbours each took an override, so a German phone showed \"Done\"\n * among German labels with no prop able to change it. */\nexport interface NumberPadSheetLabels extends Partial<CalculatorLabels> {\n /** The pad's own name — the `calculator.panel` key under its older name here. */\n pad?: string;\n backspace?: string;\n clear?: string;\n equals?: string;\n /** Visible text on the primary key, not an `aria-label`. */\n done?: string;\n}\n\ntype PadKey =\n | { kind: \"ins\"; label: string; ins: string; accent?: boolean; name?: PadKeyName }\n | { kind: \"back\" };\n\n/** The {@link CalculatorLabels} key naming a non-digit key; digits are their glyph. */\ntype PadKeyName = \"plus\" | \"minus\" | \"times\" | \"divide\" | \"decimal\";\n\n// Insert chars that survive `sanitizeLive` (it keeps `× ÷` and ASCII `+ - * /`\n// but strips the unicode minus `−`, so minus inserts an ASCII \"-\" while showing\n// the nicer glyph). The evaluator normalises `× ÷` back to `* /`.\nconst PAD_KEYS: PadKey[] = [\n { kind: \"ins\", label: \"7\", ins: \"7\" },\n { kind: \"ins\", label: \"8\", ins: \"8\" },\n { kind: \"ins\", label: \"9\", ins: \"9\" },\n { kind: \"ins\", label: \"÷\", ins: \"÷\", accent: true, name: \"divide\" },\n { kind: \"ins\", label: \"4\", ins: \"4\" },\n { kind: \"ins\", label: \"5\", ins: \"5\" },\n { kind: \"ins\", label: \"6\", ins: \"6\" },\n { kind: \"ins\", label: \"×\", ins: \"×\", accent: true, name: \"times\" },\n { kind: \"ins\", label: \"1\", ins: \"1\" },\n { kind: \"ins\", label: \"2\", ins: \"2\" },\n { kind: \"ins\", label: \"3\", ins: \"3\" },\n { kind: \"ins\", label: \"−\", ins: \"-\", accent: true, name: \"minus\" },\n { kind: \"ins\", label: \".\", ins: \".\", name: \"decimal\" },\n { kind: \"ins\", label: \"0\", ins: \"0\" },\n { kind: \"back\" },\n { kind: \"ins\", label: \"+\", ins: \"+\", accent: true, name: \"plus\" },\n];\n\n/** Every key's shape — including the thing this pad used to delete without replacing.\n *\n * `focus:outline-none` alone is not a style choice, it is the removal of the only\n * signal a keyboard user has; on a 4×4 grid of identical tiles it leaves no way at all\n * to tell which key Enter is about to press (the audit's §a11y, and the same defect as\n * the dropdown search box).\n *\n * `focus-visible` and `ring-inset`, for the two reasons {@link ToggleGroup} already\n * writes down. This is a touch control first, and a plain `focus:` ring paints itself\n * on every TAP, because a tap focuses the button. And the keys sit 6px apart, which is\n * thinner than an outward ring plus the neighbour it would spill onto — inset keeps the\n * ring inside the key it describes.\n *\n * --brand rather than --border-strong: the operator keys are filled with --border, and\n * a ring in the colour of the thing it surrounds is not an indicator. */\nconst PAD_BTN = `flex h-14 items-center justify-center rounded-lg text-lg font-medium transition-transform select-none focus:outline-none ${FOCUS_RING} focus-visible:ring-inset active:scale-[0.97]`;\nconst PAD_DIGIT = \"bg-[var(--bg-surface-2)] text-[var(--text-primary)] active:bg-[var(--border)]\";\nconst PAD_ACCENT = \"bg-[var(--border)] text-[var(--text-primary)] active:bg-[var(--bg-surface-2)]\";\n\nexport function NumberPadSheet({\n value,\n onChange,\n onDone,\n label,\n labels,\n decimalMark = \".\",\n onResult,\n}: {\n value: string;\n /** Fired with the raw (sanitised) field text on every key — same contract as\n * the host field's own `onChange`. */\n onChange: (value: string) => void;\n /** The glyph on the decimal key — \",\" where the host shows a comma (AmountInput\n * in fr-CH, kastlan 40). With \",\" the key inserts a comma and the text is handed\n * back unsanitized, for the host to read by its locale (keksdose G1). */\n decimalMark?: \".\" | \",\";\n /** \"=\": the evaluated result, dot-decimal. Default `onChange`. A host that reads\n * typed text by the locale's marks takes it apart from keystrokes, since a result\n * like \"1.234\" is a decimal and never a grouped thousand. */\n onResult?: (value: string) => void;\n /** Fired on \"Done\": the host blurs the input, which commits (evaluates) and\n * unmounts the sheet via its existing blur handler. */\n onDone: () => void;\n /** Optional field label, echoed in the sheet header so the user still knows\n * which field they're editing when the sheet covers it. */\n label?: ReactNode;\n /** Names for the pad itself and its non-digit keys — screen-reader names, except\n * `done`, which is the visible text on the primary key. Merged over `calculator`\n * from `<UiKitProvider labels>`: this is the same keypad as `CalculatorButton`'s,\n * on a phone, and one translation covers both. Digits need no name — their glyph\n * IS the name; the operators do (see {@link CalculatorLabels.plus}). */\n labels?: NumberPadSheetLabels;\n}) {\n // `pad` is this sheet's older spelling of `panel`, so it is folded in as that.\n const fromProps = useMemo(() => {\n if (!labels) return undefined;\n const { pad, ...rest } = labels;\n return pad === undefined ? rest : { ...rest, panel: pad };\n }, [labels]);\n const text = useKitLabels(\"calculator\", DEFAULT_CALCULATOR_LABELS, fromProps);\n\n // NO `useBodyScrollLock`, and that is the point of the control (Keksdose live\n // #317: *\"Background not scrollable when the amount input calculator field is\n // open\"*).\n //\n // This sheet is a KEYBOARD, not a dialog. It is opened by focusing a field and\n // exists only because a PWA cannot swap the system keyboard for its own — so it\n // suppresses the OS keyboard with `inputMode=\"none\"` and paints itself in the\n // freed space. An OS keyboard does not freeze the page behind it; it takes the\n // bottom of the screen and leaves you free to scroll what is left, which is how\n // you reach the field you are typing into when the keys cover it.\n //\n // Locking here made the page unreachable at exactly the moment it matters most:\n // correcting a receipt total against the running line sum, where the figure you\n // are comparing against sits below the keypad. The other four holders of that\n // hook are all modal — `Modal`, `PickerSheet`, `FullBleedDialog`, the DataTable\n // row dialog — and they stay locked, because for them the page behind is not\n // part of the task.\n //\n // The pad still does not scroll ITSELF away under a stray drag: the root's\n // `onPointerDown` preventDefault (below) keeps the host input focused and eats\n // the gesture on the sheet, so a touch that starts on a key is not a page scroll.\n\n // ── NO focus trap either, and for a harder reason than the scroll lock ────────\n //\n // Wave 3 put `useFocusTrap` on every overlay in this package. This one is the\n // exception, and it is not an oversight to be tidied up later: a trap here would\n // DELETE THE COMPONENT IT WAS PROTECTING. Both hosts render the pad with\n // `showNumpad = isMobile && !disabled && focused`, where `focused` is the host\n // input's own focus state. Moving focus into the pad blurs that input, the host\n // sets `focused` false, and the pad unmounts — on mount, before a single key can be\n // pressed. The blur also COMMITS, so the trap would evaluate a half-typed\n // expression on the way out.\n //\n // That is the contract, not an accident of it. This sheet exists because a PWA\n // cannot swap the system keyboard for its own: the host keeps real focus, caret and\n // selection while `inputMode=\"none\"` suppresses the OS keys, and the root's\n // `onPointerDown` preventDefault below is there for the same reason. The pad is\n // `role=\"group\"`, not a dialog. A keyboard does not trap focus; it is what you type\n // WITH.\n //\n // What was genuinely missing is a way OUT that is not a tap. `useEscapeKey` and not\n // a handler on the sheet, because the sheet never has the focus a keydown would\n // bubble from — the host does, and the host is not this component. Escape does what\n // the Done key does rather than cancelling, because committing on blur is the\n // host's contract and a cancel would have to be the host's to offer.\n //\n // The limit, since it is better written down than discovered: inside a `Modal` the\n // dialog's own Escape handler sits on the focus path and stops the event before a\n // document listener sees it, so there the press closes the dialog — which it did\n // before this line existed too, and which takes the pad with it either way.\n useEscapeKey(onDone);\n\n const result = evaluateExpression(value);\n const preview = result !== null && formatResult(result) !== value.trim() ? `= ${formatResult(result)}` : \"\";\n\n // With a \",\" mark the host keeps the typist's own text (AmountInput, keksdose G1):\n // the key inserts the comma it shows, and the host, not this pad, decides which mark\n // is the decimal one — sanitizing here would fold a grouping \".\" into a decimal.\n const insert = (ch: string) =>\n onChange(decimalMark === \",\" ? value + (ch === \".\" ? \",\" : ch) : sanitizeLive(value + ch));\n const backspace = () => onChange(value.slice(0, -1));\n const clearAll = () => onChange(\"\");\n const equals = () => {\n if (result !== null) (onResult ?? onChange)(formatResult(result));\n };\n\n const sheet = (\n <div\n role=\"group\"\n aria-label={text.panel}\n // Keep the host input focused when tapping the pad: preventDefault on\n // pointerdown blocks the focus/blur, while the buttons' click still fires.\n onPointerDown={(e) => e.preventDefault()}\n className=\"fixed inset-x-0 bottom-0 z-50 border-t border-[var(--border)] bg-[var(--bg-surface)] px-2 pt-2 shadow-[0_-4px_24px_rgba(0,0,0,0.12)]\"\n style={{ paddingBottom: \"max(0.5rem, env(safe-area-inset-bottom))\" }}\n >\n {/* Header display: which field + the running expression and live result, so\n the value stays visible even when the sheet covers the field. */}\n <div className=\"flex items-end justify-between gap-3 px-2 pb-2\">\n {label != null && (\n <span className=\"truncate pb-1 text-xs font-medium text-[var(--money-neutral)]\">{label}</span>\n )}\n {/* At the logical end, opposite the label; the figures themselves stay\n left-to-right and right-aligned, like the desktop calculator's. */}\n <div className=\"ms-auto min-w-0\">\n <div dir=\"ltr\" className=\"truncate text-right font-mono text-lg leading-tight text-[var(--text-primary)]\">{value || \"0\"}</div>\n <div dir=\"ltr\" className=\"h-4 text-right font-mono text-xs text-[var(--money-neutral)]\">{preview}</div>\n </div>\n </div>\n\n <div className=\"grid grid-cols-4 gap-1.5\">\n {PAD_KEYS.map((key, i) =>\n key.kind === \"back\" ? (\n <button\n key=\"back\"\n type=\"button\"\n aria-label={text.backspace}\n onClick={backspace}\n className={cn(PAD_BTN, PAD_ACCENT)}\n >\n <Delete className=\"size-5\" />\n </button>\n ) : (\n <button\n key={`${key.ins}-${i}`}\n type=\"button\"\n aria-label={key.name ? text[key.name] : key.label}\n onClick={() => insert(key.ins)}\n className={cn(PAD_BTN, key.accent ? PAD_ACCENT : PAD_DIGIT)}\n >\n {key.kind === \"ins\" && key.name === \"decimal\" ? decimalMark : key.label}\n </button>\n ),\n )}\n </div>\n\n <div className=\"mt-1.5 grid grid-cols-4 gap-1.5\">\n <button type=\"button\" aria-label={text.clear} onClick={clearAll} className={cn(PAD_BTN, PAD_ACCENT)}>\n C\n </button>\n <button type=\"button\" aria-label={text.equals} onClick={equals} className={cn(PAD_BTN, PAD_ACCENT)}>\n =\n </button>\n <button\n type=\"button\"\n onClick={onDone}\n // The one key whose own fill is --brand, so it rings in the colour that fill\n // was chosen to be legible against instead (tailwind-merge keeps the later\n // ring colour). A --brand ring here would be present in the DOM and invisible\n // on screen, which is the defect this change exists to fix.\n className={cn(\n PAD_BTN,\n \"col-span-2 bg-[var(--brand)] text-[var(--brand-contrast)] focus-visible:ring-[var(--brand-contrast)] active:bg-[var(--brand-hover)]\",\n )}\n >\n {text.done}\n </button>\n </div>\n </div>\n );\n\n return createPortal(sheet, document.body);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/numpad-sheet.tsx"],"sourcesContent":["import { createPortal } from \"react-dom\";\nimport { Delete } from \"lucide-react\";\nimport { useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { FOCUS_RING } from \"./focus-ring\";\nimport { useEscapeKey } from \"../hooks/use-dismiss\";\nimport { evaluateExpression, formatResult, sanitizeLive } from \"../lib/calc\";\nimport {\n DEFAULT_CALCULATOR_LABELS,\n useKitLabels,\n type CalculatorLabels,\n} from \"../i18n/kit-labels\";\n\n/**\n * A full calculator keypad rendered as a bottom sheet — the mobile counterpart to\n * the desktop {@link CalculatorButton} popover, and the replacement for the old\n * under-the-field operator bar (`MathKeys`, feedback #334).\n *\n * A native app (YNAB) can swap the system keyboard for its own keypad; a PWA\n * cannot — but it CAN suppress the OS keyboard with `inputMode=\"none\"` on the\n * host field and paint its own keypad in the freed space. So the host input keeps\n * real focus/caret/selection + the existing value/commit contract, while this\n * sheet drives it: digits + operators + a live `= …` preview, evaluated through\n * the shared {@link evaluateExpression} engine. Desktop is untouched (it keeps the\n * native keyboard and the calculator popover); the host gates this to mobile.\n *\n * Buttons commit through the host's `onChange`/blur, so the sheet is stateless —\n * the field text is the single source of truth. `onPointerDown` preventDefault on\n * the root keeps the host input focused (caret stays, keyboard stays hidden) as\n * the user taps keys; the buttons' `onClick` still fires.\n */\n/** Names for the pad itself and its four non-digit keys. The first four are\n * screen-reader names; `done` is the one string here that is READ OFF THE SCREEN —\n * the brand-filled primary button — and it was a hardcoded English literal while\n * its three neighbours each took an override, so a German phone showed \"Done\"\n * among German labels with no prop able to change it. */\nexport interface NumberPadSheetLabels extends Partial<CalculatorLabels> {\n /** The pad's own name — the `calculator.panel` key under its older name here. */\n pad?: string;\n backspace?: string;\n clear?: string;\n equals?: string;\n /** Visible text on the primary key, not an `aria-label`. */\n done?: string;\n}\n\ntype PadKey =\n | { kind: \"ins\"; label: string; ins: string; accent?: boolean; name?: PadKeyName }\n | { kind: \"back\" };\n\n/** The {@link CalculatorLabels} key naming a non-digit key; digits are their glyph. */\ntype PadKeyName = \"plus\" | \"minus\" | \"times\" | \"divide\" | \"decimal\";\n\n// Insert chars that survive `sanitizeLive` (it keeps `× ÷` and ASCII `+ - * /`\n// but strips the unicode minus `−`, so minus inserts an ASCII \"-\" while showing\n// the nicer glyph). The evaluator normalises `× ÷` back to `* /`.\nconst PAD_KEYS: PadKey[] = [\n { kind: \"ins\", label: \"7\", ins: \"7\" },\n { kind: \"ins\", label: \"8\", ins: \"8\" },\n { kind: \"ins\", label: \"9\", ins: \"9\" },\n { kind: \"ins\", label: \"÷\", ins: \"÷\", accent: true, name: \"divide\" },\n { kind: \"ins\", label: \"4\", ins: \"4\" },\n { kind: \"ins\", label: \"5\", ins: \"5\" },\n { kind: \"ins\", label: \"6\", ins: \"6\" },\n { kind: \"ins\", label: \"×\", ins: \"×\", accent: true, name: \"times\" },\n { kind: \"ins\", label: \"1\", ins: \"1\" },\n { kind: \"ins\", label: \"2\", ins: \"2\" },\n { kind: \"ins\", label: \"3\", ins: \"3\" },\n { kind: \"ins\", label: \"−\", ins: \"-\", accent: true, name: \"minus\" },\n { kind: \"ins\", label: \".\", ins: \".\", name: \"decimal\" },\n { kind: \"ins\", label: \"0\", ins: \"0\" },\n { kind: \"back\" },\n { kind: \"ins\", label: \"+\", ins: \"+\", accent: true, name: \"plus\" },\n];\n\n/** Every key's shape — including the thing this pad used to delete without replacing.\n *\n * `focus:outline-none` alone is not a style choice, it is the removal of the only\n * signal a keyboard user has; on a 4×4 grid of identical tiles it leaves no way at all\n * to tell which key Enter is about to press (the audit's §a11y, and the same defect as\n * the dropdown search box).\n *\n * `focus-visible` and `ring-inset`, for the two reasons {@link ToggleGroup} already\n * writes down. This is a touch control first, and a plain `focus:` ring paints itself\n * on every TAP, because a tap focuses the button. And the keys sit 6px apart, which is\n * thinner than an outward ring plus the neighbour it would spill onto — inset keeps the\n * ring inside the key it describes.\n *\n * --brand rather than --border-strong: the operator keys are filled with a step of the\n * ink, and a ring in the colour of the thing it surrounds is not an indicator. */\nconst PAD_BTN = `flex h-14 items-center justify-center rounded-lg text-lg font-medium transition-transform select-none focus:outline-none ${FOCUS_RING} focus-visible:ring-inset active:scale-[0.97]`;\n// Digits on the well, operators a step down on `--bg-active`, each pressing toward the\n// other. `--bg-active`, not `--border` (0.33, docs/colour-roles-harmonization.md §12.9):\n// More contrast steps the border until it is a 3:1 LINE, and as a fill under the label\n// that left 2.7–3.3:1; `--bg-active` carries the ink at 7:1 and up at both levels.\nconst PAD_DIGIT = \"bg-surface-2 text-primary active:bg-active\";\nconst PAD_ACCENT = \"bg-active text-primary active:bg-surface-2\";\n\nexport function NumberPadSheet({\n value,\n onChange,\n onDone,\n label,\n labels,\n decimalMark = \".\",\n onResult,\n}: {\n value: string;\n /** Fired with the raw (sanitised) field text on every key — same contract as\n * the host field's own `onChange`. */\n onChange: (value: string) => void;\n /** The glyph on the decimal key — \",\" where the host shows a comma (AmountInput\n * in fr-CH, kastlan 40). With \",\" the key inserts a comma and the text is handed\n * back unsanitized, for the host to read by its locale (keksdose G1). */\n decimalMark?: \".\" | \",\";\n /** \"=\": the evaluated result, dot-decimal. Default `onChange`. A host that reads\n * typed text by the locale's marks takes it apart from keystrokes, since a result\n * like \"1.234\" is a decimal and never a grouped thousand. */\n onResult?: (value: string) => void;\n /** Fired on \"Done\": the host blurs the input, which commits (evaluates) and\n * unmounts the sheet via its existing blur handler. */\n onDone: () => void;\n /** Optional field label, echoed in the sheet header so the user still knows\n * which field they're editing when the sheet covers it. */\n label?: ReactNode;\n /** Names for the pad itself and its non-digit keys — screen-reader names, except\n * `done`, which is the visible text on the primary key. Merged over `calculator`\n * from `<UiKitProvider labels>`: this is the same keypad as `CalculatorButton`'s,\n * on a phone, and one translation covers both. Digits need no name — their glyph\n * IS the name; the operators do (see {@link CalculatorLabels.plus}). */\n labels?: NumberPadSheetLabels;\n}) {\n // `pad` is this sheet's older spelling of `panel`, so it is folded in as that.\n const fromProps = useMemo(() => {\n if (!labels) return undefined;\n const { pad, ...rest } = labels;\n return pad === undefined ? rest : { ...rest, panel: pad };\n }, [labels]);\n const text = useKitLabels(\"calculator\", DEFAULT_CALCULATOR_LABELS, fromProps);\n\n // NO `useBodyScrollLock`, and that is the point of the control (Keksdose live\n // #317: *\"Background not scrollable when the amount input calculator field is\n // open\"*).\n //\n // This sheet is a KEYBOARD, not a dialog. It is opened by focusing a field and\n // exists only because a PWA cannot swap the system keyboard for its own — so it\n // suppresses the OS keyboard with `inputMode=\"none\"` and paints itself in the\n // freed space. An OS keyboard does not freeze the page behind it; it takes the\n // bottom of the screen and leaves you free to scroll what is left, which is how\n // you reach the field you are typing into when the keys cover it.\n //\n // Locking here made the page unreachable at exactly the moment it matters most:\n // correcting a receipt total against the running line sum, where the figure you\n // are comparing against sits below the keypad. The other four holders of that\n // hook are all modal — `Modal`, `PickerSheet`, `FullBleedDialog`, the DataTable\n // row dialog — and they stay locked, because for them the page behind is not\n // part of the task.\n //\n // The pad still does not scroll ITSELF away under a stray drag: the root's\n // `onPointerDown` preventDefault (below) keeps the host input focused and eats\n // the gesture on the sheet, so a touch that starts on a key is not a page scroll.\n\n // ── NO focus trap either, and for a harder reason than the scroll lock ────────\n //\n // Wave 3 put `useFocusTrap` on every overlay in this package. This one is the\n // exception, and it is not an oversight to be tidied up later: a trap here would\n // DELETE THE COMPONENT IT WAS PROTECTING. Both hosts render the pad with\n // `showNumpad = isMobile && !disabled && focused`, where `focused` is the host\n // input's own focus state. Moving focus into the pad blurs that input, the host\n // sets `focused` false, and the pad unmounts — on mount, before a single key can be\n // pressed. The blur also COMMITS, so the trap would evaluate a half-typed\n // expression on the way out.\n //\n // That is the contract, not an accident of it. This sheet exists because a PWA\n // cannot swap the system keyboard for its own: the host keeps real focus, caret and\n // selection while `inputMode=\"none\"` suppresses the OS keys, and the root's\n // `onPointerDown` preventDefault below is there for the same reason. The pad is\n // `role=\"group\"`, not a dialog. A keyboard does not trap focus; it is what you type\n // WITH.\n //\n // What was genuinely missing is a way OUT that is not a tap. `useEscapeKey` and not\n // a handler on the sheet, because the sheet never has the focus a keydown would\n // bubble from — the host does, and the host is not this component. Escape does what\n // the Done key does rather than cancelling, because committing on blur is the\n // host's contract and a cancel would have to be the host's to offer.\n //\n // The limit, since it is better written down than discovered: inside a `Modal` the\n // dialog's own Escape handler sits on the focus path and stops the event before a\n // document listener sees it, so there the press closes the dialog — which it did\n // before this line existed too, and which takes the pad with it either way.\n useEscapeKey(onDone);\n\n const result = evaluateExpression(value);\n const preview = result !== null && formatResult(result) !== value.trim() ? `= ${formatResult(result)}` : \"\";\n\n // With a \",\" mark the host keeps the typist's own text (AmountInput, keksdose G1):\n // the key inserts the comma it shows, and the host, not this pad, decides which mark\n // is the decimal one — sanitizing here would fold a grouping \".\" into a decimal.\n const insert = (ch: string) =>\n onChange(decimalMark === \",\" ? value + (ch === \".\" ? \",\" : ch) : sanitizeLive(value + ch));\n const backspace = () => onChange(value.slice(0, -1));\n const clearAll = () => onChange(\"\");\n const equals = () => {\n if (result !== null) (onResult ?? onChange)(formatResult(result));\n };\n\n const sheet = (\n <div\n role=\"group\"\n aria-label={text.panel}\n // Keep the host input focused when tapping the pad: preventDefault on\n // pointerdown blocks the focus/blur, while the buttons' click still fires.\n onPointerDown={(e) => e.preventDefault()}\n className=\"fixed inset-x-0 bottom-0 z-50 border-t border-[var(--border)] bg-[var(--bg-surface)] px-2 pt-2 shadow-[0_-4px_24px_rgba(0,0,0,0.12)]\"\n style={{ paddingBottom: \"max(0.5rem, env(safe-area-inset-bottom))\" }}\n >\n {/* Header display: which field + the running expression and live result, so\n the value stays visible even when the sheet covers the field. */}\n <div className=\"flex items-end justify-between gap-3 px-2 pb-2\">\n {label != null && (\n <span className=\"truncate pb-1 text-xs font-medium text-[var(--money-neutral)]\">{label}</span>\n )}\n {/* At the logical end, opposite the label; the figures themselves stay\n left-to-right and right-aligned, like the desktop calculator's. */}\n <div className=\"ms-auto min-w-0\">\n <div dir=\"ltr\" className=\"truncate text-right font-mono text-lg leading-tight text-[var(--text-primary)]\">{value || \"0\"}</div>\n <div dir=\"ltr\" className=\"h-4 text-right font-mono text-xs text-[var(--money-neutral)]\">{preview}</div>\n </div>\n </div>\n\n <div className=\"grid grid-cols-4 gap-1.5\">\n {PAD_KEYS.map((key, i) =>\n key.kind === \"back\" ? (\n <button\n key=\"back\"\n type=\"button\"\n aria-label={text.backspace}\n onClick={backspace}\n className={cn(PAD_BTN, PAD_ACCENT)}\n >\n <Delete className=\"size-5\" />\n </button>\n ) : (\n <button\n key={`${key.ins}-${i}`}\n type=\"button\"\n aria-label={key.name ? text[key.name] : key.label}\n onClick={() => insert(key.ins)}\n className={cn(PAD_BTN, key.accent ? PAD_ACCENT : PAD_DIGIT)}\n >\n {key.kind === \"ins\" && key.name === \"decimal\" ? decimalMark : key.label}\n </button>\n ),\n )}\n </div>\n\n <div className=\"mt-1.5 grid grid-cols-4 gap-1.5\">\n <button type=\"button\" aria-label={text.clear} onClick={clearAll} className={cn(PAD_BTN, PAD_ACCENT)}>\n C\n </button>\n <button type=\"button\" aria-label={text.equals} onClick={equals} className={cn(PAD_BTN, PAD_ACCENT)}>\n =\n </button>\n <button\n type=\"button\"\n onClick={onDone}\n // The one key whose own fill is --brand, so it rings in the colour that fill\n // was chosen to be legible against instead (tailwind-merge keeps the later\n // ring colour). A --brand ring here would be present in the DOM and invisible\n // on screen, which is the defect this change exists to fix.\n className={cn(\n PAD_BTN,\n \"col-span-2 bg-[var(--brand)] text-[var(--brand-contrast)] focus-visible:ring-[var(--brand-contrast)] active:bg-[var(--brand-hover)]\",\n )}\n >\n {text.done}\n </button>\n </div>\n </div>\n );\n\n return createPortal(sheet, document.body);\n}\n"],"mappings":"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","names":[]}
@@ -1,19 +1,24 @@
1
1
  import 'react';
2
2
  import '../lib/phone.js';
3
- export { cJ as DEFAULT_PHONE_INPUT_LABELS, eq as PhoneInput, er as PhoneInputLabels, es as PhoneInputProps } from '../delete-account-setting-Dy4kJgUm.js';
3
+ export { cJ as DEFAULT_PHONE_INPUT_LABELS, er as PhoneInput, es as PhoneInputLabels, et as PhoneInputProps } from '../delete-account-setting-DAgGBrbn.js';
4
4
  import './tooltip.js';
5
5
  import '../hooks/use-anchored-rect.js';
6
6
  import '../lib/clipping.js';
7
- import '../data-table-labels-kpAAVi6g.js';
7
+ import '../data-table-labels-BJ5JE_X2.js';
8
8
  import './data-table-sort.js';
9
9
  import './swipeable-row.js';
10
+ import './status-dot.js';
11
+ import './progress-bar.js';
12
+ import './signed-amount.js';
13
+ import './stat-tile.js';
14
+ import './chip.js';
15
+ import 'lucide-react';
16
+ import './field-parts.js';
17
+ import './write-lock.js';
10
18
  import './mini-calendar.js';
11
19
  import './calendar-heatmap.js';
12
20
  import './popover.js';
13
21
  import '../hooks/use-breakpoint.js';
14
- import './chip.js';
15
- import 'lucide-react';
16
- import './field-parts.js';
17
22
  import './field-sync.js';
18
23
  import '../wizard/types.js';
19
24
  import '../tour/tour.js';
@@ -25,7 +30,6 @@ import './page-contents.js';
25
30
  import './series-chart-labels.js';
26
31
  import './pie-chart-labels.js';
27
32
  import './sparkline.js';
28
- import './stat-tile.js';
29
33
  import './signature-pad.js';
30
34
  import './password-strength.js';
31
35
  import './swatch-picker.js';
@@ -37,9 +41,6 @@ import '../hooks/use-search-param-state.js';
37
41
  import './measured-grid.js';
38
42
  import './user-avatar.js';
39
43
  import '../lib/person-name.js';
40
- import './status-dot.js';
41
- import './progress-bar.js';
42
- import './signed-amount.js';
43
44
  import '../shell/topbar-action-menu.js';
44
45
  import './menu-item.js';
45
46
  import './floating-panel.js';
@@ -55,7 +56,6 @@ import './error-boundary.js';
55
56
  import './authed-image.js';
56
57
  import './image-grid.js';
57
58
  import './lightbox.js';
58
- import './write-lock.js';
59
59
  import '../auth/sign-in-form.js';
60
60
  import '../landing/access.js';
61
61
  import '../landing/landing-labels.js';
@@ -82,6 +82,7 @@ import '../admin/admin-parts.js';
82
82
  import '../admin/admin-action-log.js';
83
83
  import '../settings/settings-labels.js';
84
84
  import '../demo/demo-labels.js';
85
+ import '../i18n/plurals.js';
85
86
  import './appearance-labels.js';
86
87
  import '../theme/contrast.js';
87
88
  import '../theme/text-size.js';
@@ -44,7 +44,10 @@ function Popover({
44
44
  const remPx = useRemPx();
45
45
  const panelWidth = rect ? Math.min(lengthPx(width, remPx), Math.max(0, window.innerWidth - 2 * EDGE)) : 0;
46
46
  const pos = rect ? {
47
- top: placement.top,
47
+ // Flipped above, the panel hangs from its bottom edge, so one shorter than its
48
+ // cap still meets the trigger (keksdose K1); the edge not in use is cleared.
49
+ top: placement.above ? void 0 : placement.top,
50
+ bottom: placement.above ? placement.bottom : void 0,
48
51
  left: Math.min(
49
52
  Math.max(EDGE, dir === "rtl" ? rect.left : rect.right - panelWidth),
50
53
  window.innerWidth - panelWidth - EDGE
@@ -89,12 +92,13 @@ function Popover({
89
92
  tabIndex: -1,
90
93
  onKeyDown: handleKeyDown,
91
94
  style: {
92
- // Merged over the caller's, never replaced by it: these four are measured
95
+ // Merged over the caller's, never replaced by it: these are measured
93
96
  // against the trigger every scroll and resize, and a panel that dropped
94
97
  // them would paint at the top-left of the viewport.
95
98
  ...style,
96
99
  position: "fixed",
97
100
  top: pos.top,
101
+ bottom: pos.bottom,
98
102
  left: pos.left,
99
103
  width: panelWidth,
100
104
  maxHeight: placement.maxHeight
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/popover.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, KeyboardEvent, ReactNode, RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useAnchoredPanel } from \"../hooks/use-anchored-panel\";\nimport { useEscapeKey, useOutsideClick } from \"../hooks/use-dismiss\";\nimport { useFocusTrap } from \"../hooks/use-focus-trap\";\nimport { lengthPx, useRemPx, type RemLength } from \"../hooks/use-breakpoint\";\nimport { cn } from \"../lib/cn\";\nimport { dirOf } from \"../lib/direction\";\nimport type { Direction } from \"../lib/direction\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\n\n/** The panel's accessible name. A `role=\"dialog\"` with no name announces as \"dialog\"\n * and nothing else, which is worse than the unlabelled div it replaced — so it is a\n * prop with an English default, like every other string this package renders. Callers\n * should pass the control's own name (\"Filter\", \"Choose a date\"); the default is only\n * there so an unmigrated call site is still named. */\nexport interface PopoverLabels {\n panel: string;\n}\n\n// Exported since the kit grew one label tree (`UiKitLabels`, src/i18n): the complete\n// English reference a translator works from has to be able to name every namespace.\nexport const DEFAULT_POPOVER_LABELS: PopoverLabels = {\n // \"Pop-up\", not \"Popover\": the panel's accessible name is read to USERS, and\n // \"popover\" is a developer's word for it (reported by keksdose, 0.5.0). Only a\n // bare Popover falls back to this — the kit's own pickers name their panels.\n panel: \"Pop-up\",\n};\n\n/**\n * Exported, and extending the `<div>` attributes, so a consumer wrapping this — which is\n * what `DateField` and `FilterPopover` in this very package do — can hand anything\n * through to the PANEL: a `data-tour` anchor for the kit's own guided tour, a test id,\n * an `aria-describedby`, or an `aria-label` naming this particular popover.\n *\n * `children` is omitted from those attributes rather than inherited: here it is a render\n * prop taking the panel's `close`, not a `ReactNode`, so the two cannot be reconciled.\n */\nexport interface PopoverProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> {\n trigger: (state: { open: boolean; toggle: () => void; ref: RefObject<HTMLButtonElement | null> }) => ReactNode;\n children: (close: () => void) => ReactNode;\n /**\n * The panel's width: `\"18rem\"` (the default) grows with the text size — the panel\n * holds text, and a 288 px calendar at 150 % cut its own month off (0.32,\n * docs/text-size-harmonization.md §3.2). A plain number is px, fixed, as before.\n * Either way the panel is never wider than the viewport less 8 px a side: 18rem is\n * 432 px at Extra large, more than a 360 px phone has.\n */\n width?: number | RemLength;\n labels?: Partial<PopoverLabels>;\n /** Id for the panel, so a trigger wearing `role=\"combobox\"` can `aria-controls` it.\n * Required by ARIA on that role, and the panel is rendered here rather than by the\n * caller, so the id has to come in from outside. */\n panelId?: string;\n /** Extra classes for the PANEL. The trigger is the caller's own element. */\n className?: string;\n}\n\n/** 288 px at Normal, in rem so it follows the text size (§3.2). */\nconst POPOVER_WIDTH: RemLength = \"18rem\";\n\n/** The gap the panel keeps from either edge of the viewport, in px. */\nconst EDGE = 8;\n\n/**\n * Portal-rendered popover anchored under its trigger button. The panel is fixed\n * to the viewport (so table/overflow ancestors can't clip it) and re-aligns on\n * scroll/resize; it closes on outside-click and Escape.\n *\n * ## Trapped, not merely focused — and why, because it is the closer call\n *\n * The audit's finding was that this panel *\"portals out of the tab order with no role\n * and no focus move\"*: Tab from the trigger went straight past the panel into the page\n * behind it, so the calendar, the calculator keypad and every column filter in the kit\n * were mouse-only. Two shapes fix that, and a popover is not automatically a dialog, so\n * this was chosen rather than inherited:\n *\n * - **The light one** — move focus in on open, hand it back on close, and let Tab walk\n * OUT of the panel into whatever follows the trigger. That is the right shape for a\n * non-modal popover sitting inline in the document.\n * - **The trap** — Tab cycles inside the panel; Escape and a click outside are the\n * ways out. What is here.\n *\n * The deciding fact is the portal. This panel is rendered to the END of `<body>`, not\n * beside its trigger, so \"let Tab walk out\" does not mean \"carry on from the trigger\" —\n * it means land at the end of the document, several hundred controls from where the\n * user was. Tabbing out of it would be a worse place to arrive than the one the finding\n * complains about, and fixing that properly needs a sentinel-and-return dance that\n * nothing in this package has. The trap gets the user back to the trigger every time,\n * because `useFocusTrap` restores focus on close.\n *\n * And the panels this kit actually opens are self-contained: pick a date, tap out a\n * sum, narrow a column. Each of them commits and closes, and each already has two\n * documented ways out wired above — Escape and an outside press — so containment costs\n * a keyboard user nothing they had.\n *\n * **It stays non-modal in every other respect**, deliberately: no backdrop, no body\n * scroll lock, and no `aria-modal`, so the page behind is still readable by a screen\n * reader, still scrollable, and still clickable — clicking it is what dismisses this.\n * Only Tab is contained. A popover that took the page away as well would be a\n * {@link Modal}, and there is one of those.\n */\nexport function Popover({\n trigger,\n children,\n width = POPOVER_WIDTH,\n labels,\n panelId,\n className,\n style,\n \"aria-label\": ariaLabel,\n onKeyDown: onKeyDownProp,\n ...rest\n}: PopoverProps) {\n const triggerRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n const [open, setOpen] = useState(false);\n // The trigger's reading direction, read when it is pressed. The panel is portalled to\n // <body>, out of any `dir=\"rtl\"` subtree the trigger sits in, so without carrying it\n // across a right-to-left calendar or filter rendered left-to-right.\n const [dir, setDir] = useState<Direction>(\"ltr\");\n const close = () => setOpen(false);\n const toggle = () => {\n setDir(dirOf(triggerRef.current));\n setOpen((v) => !v);\n };\n // prop > `<UiKitProvider labels={{ popover }}>` > English. The provider's\n // `popover.panel` is only the last-resort name for a panel no caller named.\n const text = useKitLabels(\"popover\", DEFAULT_POPOVER_LABELS, labels);\n\n // Align the panel's END edge to the trigger's — right in LTR, left in RTL; it was\n // always `rect.right`, so an RTL panel hung off the wrong side — and clamp it\n // horizontally; the\n // hook owns the vertical half — below by default, flipped above (and height-capped)\n // when that is where the room is, e.g. once a mobile keyboard has eaten the bottom\n // of the screen (feedback #135). It re-measures on scroll/resize.\n const placement = useAnchoredPanel(triggerRef, open);\n const rect = placement.rect;\n // In px for the arithmetic below, at the text size in force; capped at the viewport.\n const remPx = useRemPx();\n const panelWidth = rect\n ? Math.min(lengthPx(width, remPx), Math.max(0, window.innerWidth - 2 * EDGE))\n : 0;\n const pos = rect\n ? {\n top: placement.top,\n left: Math.min(\n Math.max(EDGE, dir === \"rtl\" ? rect.left : rect.right - panelWidth),\n window.innerWidth - panelWidth - EDGE,\n ),\n }\n : null;\n\n useOutsideClick([triggerRef, panelRef], close, open);\n useEscapeKey(close, open);\n\n useFocusTrap(panelRef, {\n active: open && pos !== null,\n // The panel keeps a focus one of its own children has already taken. `FilterPopover`\n // autofocuses its text box on mount — that is what the press on \"Filter\" was FOR,\n // and it is a prop there precisely so the popover keeps it — and a trap that\n // unconditionally focused the container would take it straight back off again,\n // one effect later and invisibly.\n initialFocus: () => {\n const panel = panelRef.current;\n const active = document.activeElement;\n return panel?.contains(active) ? (active as HTMLElement) : panel;\n },\n });\n\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n // A caller's own handler first (0.32.2: RowActions stops Enter / Space at the panel);\n // it used to be dropped, overwritten by this one.\n onKeyDownProp?.(e);\n if (e.key !== \"Escape\") return;\n // `useEscapeKey` above listens on the document and still covers the case where\n // focus somehow is not in here. This handler is for the case that focus IS, which\n // is now the normal one — and it exists to STOP the key, because a popover opened\n // from inside a `Modal` (the calculator on an amount field is exactly that) would\n // otherwise have its Escape travel on up the React tree and close the dialog\n // underneath as well. Closing twice is harmless — `close` is a state setter, not a\n // caller's callback — so the two paths cannot double-fire anything.\n e.stopPropagation();\n close();\n };\n\n return (\n <>\n {trigger({ open, toggle, ref: triggerRef })}\n {open && pos &&\n createPortal(\n /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- the\n dialog container, per the APG dialog pattern: `onKeyDown` is Escape-to-close for\n whatever inside it holds focus. */\n <div\n // `...rest` first: the role, the id the trigger's `aria-controls` points at,\n // the `tabIndex` the trap needs and the measured position are what make this\n // a popover, and a caller reaching for a `data-tour` anchor must not be able\n // to remove one of them by accident.\n {...rest}\n ref={panelRef}\n role=\"dialog\"\n id={panelId}\n // The caller's own `aria-label` wins over {@link PopoverLabels.panel}, which\n // is the package's English fallback for an unnamed panel. Both name the same\n // element; the DOM spelling is the one being standardised on, so where both\n // are present it is the one that survives.\n aria-label={ariaLabel ?? text.panel}\n dir={rest.dir ?? dir}\n // Without it the panel cannot take focus at all, and the trap above would\n // silently do nothing on a panel whose children are not yet tabbable.\n tabIndex={-1}\n onKeyDown={handleKeyDown}\n style={{\n // Merged over the caller's, never replaced by it: these four are measured\n // against the trigger every scroll and resize, and a panel that dropped\n // them would paint at the top-left of the viewport.\n ...style,\n position: \"fixed\",\n top: pos.top,\n left: pos.left,\n width: panelWidth,\n maxHeight: placement.maxHeight,\n }}\n className={cn(\n \"z-50 overflow-y-auto rounded-md border border-[var(--border)] bg-[var(--bg-surface)] p-3 shadow-lg outline-none\",\n className,\n )}\n >\n {children(close)}\n </div>,\n document.body,\n )}\n </>\n );\n}\n"],"mappings":";AA4LI,mBAOM,KAPN;AA5LJ,SAAS,QAAQ,gBAAgB;AAEjC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,cAAc,uBAAuB;AAC9C,SAAS,oBAAoB;AAC7B,SAAS,UAAU,gBAAgC;AACnD,SAAS,UAAU;AACnB,SAAS,aAAa;AAEtB,SAAS,oBAAoB;AAatB,MAAM,yBAAwC;AAAA;AAAA;AAAA;AAAA,EAInD,OAAO;AACT;AAgCA,MAAM,gBAA2B;AAGjC,MAAM,OAAO;AAwCN,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,WAAW;AAAA,EACX,GAAG;AACL,GAAiB;AACf,QAAM,aAAa,OAA0B,IAAI;AACjD,QAAM,WAAW,OAAuB,IAAI;AAC5C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AAItC,QAAM,CAAC,KAAK,MAAM,IAAI,SAAoB,KAAK;AAC/C,QAAM,QAAQ,MAAM,QAAQ,KAAK;AACjC,QAAM,SAAS,MAAM;AACnB,WAAO,MAAM,WAAW,OAAO,CAAC;AAChC,YAAQ,CAAC,MAAM,CAAC,CAAC;AAAA,EACnB;AAGA,QAAM,OAAO,aAAa,WAAW,wBAAwB,MAAM;AAQnE,QAAM,YAAY,iBAAiB,YAAY,IAAI;AACnD,QAAM,OAAO,UAAU;AAEvB,QAAM,QAAQ,SAAS;AACvB,QAAM,aAAa,OACf,KAAK,IAAI,SAAS,OAAO,KAAK,GAAG,KAAK,IAAI,GAAG,OAAO,aAAa,IAAI,IAAI,CAAC,IAC1E;AACJ,QAAM,MAAM,OACR;AAAA,IACE,KAAK,UAAU;AAAA,IACf,MAAM,KAAK;AAAA,MACT,KAAK,IAAI,MAAM,QAAQ,QAAQ,KAAK,OAAO,KAAK,QAAQ,UAAU;AAAA,MAClE,OAAO,aAAa,aAAa;AAAA,IACnC;AAAA,EACF,IACA;AAEJ,kBAAgB,CAAC,YAAY,QAAQ,GAAG,OAAO,IAAI;AACnD,eAAa,OAAO,IAAI;AAExB,eAAa,UAAU;AAAA,IACrB,QAAQ,QAAQ,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMxB,cAAc,MAAM;AAClB,YAAM,QAAQ,SAAS;AACvB,YAAM,SAAS,SAAS;AACxB,aAAO,OAAO,SAAS,MAAM,IAAK,SAAyB;AAAA,IAC7D;AAAA,EACF,CAAC;AAED,QAAM,gBAAgB,CAAC,MAAqC;AAG1D,oBAAgB,CAAC;AACjB,QAAI,EAAE,QAAQ,SAAU;AAQxB,MAAE,gBAAgB;AAClB,UAAM;AAAA,EACR;AAEA,SACE,iCACG;AAAA,YAAQ,EAAE,MAAM,QAAQ,KAAK,WAAW,CAAC;AAAA,IACzC,QAAQ,OACP;AAAA;AAAA;AAAA;AAAA,MAIE;AAAA,QAAC;AAAA;AAAA,UAKE,GAAG;AAAA,UACJ,KAAK;AAAA,UACL,MAAK;AAAA,UACL,IAAI;AAAA,UAKJ,cAAY,aAAa,KAAK;AAAA,UAC9B,KAAK,KAAK,OAAO;AAAA,UAGjB,UAAU;AAAA,UACV,WAAW;AAAA,UACX,OAAO;AAAA;AAAA;AAAA;AAAA,YAIL,GAAG;AAAA,YACH,UAAU;AAAA,YACV,KAAK,IAAI;AAAA,YACT,MAAM,IAAI;AAAA,YACV,OAAO;AAAA,YACP,WAAW,UAAU;AAAA,UACvB;AAAA,UACA,WAAW;AAAA,YACT;AAAA,YACA;AAAA,UACF;AAAA,UAEC,mBAAS,KAAK;AAAA;AAAA,MACjB;AAAA,MACA,SAAS;AAAA,IACX;AAAA,KACJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/popover.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, KeyboardEvent, ReactNode, RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useAnchoredPanel } from \"../hooks/use-anchored-panel\";\nimport { useEscapeKey, useOutsideClick } from \"../hooks/use-dismiss\";\nimport { useFocusTrap } from \"../hooks/use-focus-trap\";\nimport { lengthPx, useRemPx, type RemLength } from \"../hooks/use-breakpoint\";\nimport { cn } from \"../lib/cn\";\nimport { dirOf } from \"../lib/direction\";\nimport type { Direction } from \"../lib/direction\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\n\n/** The panel's accessible name. A `role=\"dialog\"` with no name announces as \"dialog\"\n * and nothing else, which is worse than the unlabelled div it replaced — so it is a\n * prop with an English default, like every other string this package renders. Callers\n * should pass the control's own name (\"Filter\", \"Choose a date\"); the default is only\n * there so an unmigrated call site is still named. */\nexport interface PopoverLabels {\n panel: string;\n}\n\n// Exported since the kit grew one label tree (`UiKitLabels`, src/i18n): the complete\n// English reference a translator works from has to be able to name every namespace.\nexport const DEFAULT_POPOVER_LABELS: PopoverLabels = {\n // \"Pop-up\", not \"Popover\": the panel's accessible name is read to USERS, and\n // \"popover\" is a developer's word for it (reported by keksdose, 0.5.0). Only a\n // bare Popover falls back to this — the kit's own pickers name their panels.\n panel: \"Pop-up\",\n};\n\n/**\n * Exported, and extending the `<div>` attributes, so a consumer wrapping this — which is\n * what `DateField` and `FilterPopover` in this very package do — can hand anything\n * through to the PANEL: a `data-tour` anchor for the kit's own guided tour, a test id,\n * an `aria-describedby`, or an `aria-label` naming this particular popover.\n *\n * `children` is omitted from those attributes rather than inherited: here it is a render\n * prop taking the panel's `close`, not a `ReactNode`, so the two cannot be reconciled.\n */\nexport interface PopoverProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> {\n trigger: (state: { open: boolean; toggle: () => void; ref: RefObject<HTMLButtonElement | null> }) => ReactNode;\n children: (close: () => void) => ReactNode;\n /**\n * The panel's width: `\"18rem\"` (the default) grows with the text size — the panel\n * holds text, and a 288 px calendar at 150 % cut its own month off (0.32,\n * docs/text-size-harmonization.md §3.2). A plain number is px, fixed, as before.\n * Either way the panel is never wider than the viewport less 8 px a side: 18rem is\n * 432 px at Extra large, more than a 360 px phone has.\n */\n width?: number | RemLength;\n labels?: Partial<PopoverLabels>;\n /** Id for the panel, so a trigger wearing `role=\"combobox\"` can `aria-controls` it.\n * Required by ARIA on that role, and the panel is rendered here rather than by the\n * caller, so the id has to come in from outside. */\n panelId?: string;\n /** Extra classes for the PANEL. The trigger is the caller's own element. */\n className?: string;\n}\n\n/** 288 px at Normal, in rem so it follows the text size (§3.2). */\nconst POPOVER_WIDTH: RemLength = \"18rem\";\n\n/** The gap the panel keeps from either edge of the viewport, in px. */\nconst EDGE = 8;\n\n/**\n * Portal-rendered popover anchored under its trigger button. The panel is fixed\n * to the viewport (so table/overflow ancestors can't clip it) and re-aligns on\n * scroll/resize; it closes on outside-click and Escape.\n *\n * ## Trapped, not merely focused — and why, because it is the closer call\n *\n * The audit's finding was that this panel *\"portals out of the tab order with no role\n * and no focus move\"*: Tab from the trigger went straight past the panel into the page\n * behind it, so the calendar, the calculator keypad and every column filter in the kit\n * were mouse-only. Two shapes fix that, and a popover is not automatically a dialog, so\n * this was chosen rather than inherited:\n *\n * - **The light one** — move focus in on open, hand it back on close, and let Tab walk\n * OUT of the panel into whatever follows the trigger. That is the right shape for a\n * non-modal popover sitting inline in the document.\n * - **The trap** — Tab cycles inside the panel; Escape and a click outside are the\n * ways out. What is here.\n *\n * The deciding fact is the portal. This panel is rendered to the END of `<body>`, not\n * beside its trigger, so \"let Tab walk out\" does not mean \"carry on from the trigger\" —\n * it means land at the end of the document, several hundred controls from where the\n * user was. Tabbing out of it would be a worse place to arrive than the one the finding\n * complains about, and fixing that properly needs a sentinel-and-return dance that\n * nothing in this package has. The trap gets the user back to the trigger every time,\n * because `useFocusTrap` restores focus on close.\n *\n * And the panels this kit actually opens are self-contained: pick a date, tap out a\n * sum, narrow a column. Each of them commits and closes, and each already has two\n * documented ways out wired above — Escape and an outside press — so containment costs\n * a keyboard user nothing they had.\n *\n * **It stays non-modal in every other respect**, deliberately: no backdrop, no body\n * scroll lock, and no `aria-modal`, so the page behind is still readable by a screen\n * reader, still scrollable, and still clickable — clicking it is what dismisses this.\n * Only Tab is contained. A popover that took the page away as well would be a\n * {@link Modal}, and there is one of those.\n */\nexport function Popover({\n trigger,\n children,\n width = POPOVER_WIDTH,\n labels,\n panelId,\n className,\n style,\n \"aria-label\": ariaLabel,\n onKeyDown: onKeyDownProp,\n ...rest\n}: PopoverProps) {\n const triggerRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n const [open, setOpen] = useState(false);\n // The trigger's reading direction, read when it is pressed. The panel is portalled to\n // <body>, out of any `dir=\"rtl\"` subtree the trigger sits in, so without carrying it\n // across a right-to-left calendar or filter rendered left-to-right.\n const [dir, setDir] = useState<Direction>(\"ltr\");\n const close = () => setOpen(false);\n const toggle = () => {\n setDir(dirOf(triggerRef.current));\n setOpen((v) => !v);\n };\n // prop > `<UiKitProvider labels={{ popover }}>` > English. The provider's\n // `popover.panel` is only the last-resort name for a panel no caller named.\n const text = useKitLabels(\"popover\", DEFAULT_POPOVER_LABELS, labels);\n\n // Align the panel's END edge to the trigger's — right in LTR, left in RTL; it was\n // always `rect.right`, so an RTL panel hung off the wrong side — and clamp it\n // horizontally; the\n // hook owns the vertical half — below by default, flipped above (and height-capped)\n // when that is where the room is, e.g. once a mobile keyboard has eaten the bottom\n // of the screen (feedback #135). It re-measures on scroll/resize.\n const placement = useAnchoredPanel(triggerRef, open);\n const rect = placement.rect;\n // In px for the arithmetic below, at the text size in force; capped at the viewport.\n const remPx = useRemPx();\n const panelWidth = rect\n ? Math.min(lengthPx(width, remPx), Math.max(0, window.innerWidth - 2 * EDGE))\n : 0;\n const pos = rect\n ? {\n // Flipped above, the panel hangs from its bottom edge, so one shorter than its\n // cap still meets the trigger (keksdose K1); the edge not in use is cleared.\n top: placement.above ? undefined : placement.top,\n bottom: placement.above ? placement.bottom : undefined,\n left: Math.min(\n Math.max(EDGE, dir === \"rtl\" ? rect.left : rect.right - panelWidth),\n window.innerWidth - panelWidth - EDGE,\n ),\n }\n : null;\n\n useOutsideClick([triggerRef, panelRef], close, open);\n useEscapeKey(close, open);\n\n useFocusTrap(panelRef, {\n active: open && pos !== null,\n // The panel keeps a focus one of its own children has already taken. `FilterPopover`\n // autofocuses its text box on mount — that is what the press on \"Filter\" was FOR,\n // and it is a prop there precisely so the popover keeps it — and a trap that\n // unconditionally focused the container would take it straight back off again,\n // one effect later and invisibly.\n initialFocus: () => {\n const panel = panelRef.current;\n const active = document.activeElement;\n return panel?.contains(active) ? (active as HTMLElement) : panel;\n },\n });\n\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n // A caller's own handler first (0.32.2: RowActions stops Enter / Space at the panel);\n // it used to be dropped, overwritten by this one.\n onKeyDownProp?.(e);\n if (e.key !== \"Escape\") return;\n // `useEscapeKey` above listens on the document and still covers the case where\n // focus somehow is not in here. This handler is for the case that focus IS, which\n // is now the normal one — and it exists to STOP the key, because a popover opened\n // from inside a `Modal` (the calculator on an amount field is exactly that) would\n // otherwise have its Escape travel on up the React tree and close the dialog\n // underneath as well. Closing twice is harmless — `close` is a state setter, not a\n // caller's callback — so the two paths cannot double-fire anything.\n e.stopPropagation();\n close();\n };\n\n return (\n <>\n {trigger({ open, toggle, ref: triggerRef })}\n {open && pos &&\n createPortal(\n /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- the\n dialog container, per the APG dialog pattern: `onKeyDown` is Escape-to-close for\n whatever inside it holds focus. */\n <div\n // `...rest` first: the role, the id the trigger's `aria-controls` points at,\n // the `tabIndex` the trap needs and the measured position are what make this\n // a popover, and a caller reaching for a `data-tour` anchor must not be able\n // to remove one of them by accident.\n {...rest}\n ref={panelRef}\n role=\"dialog\"\n id={panelId}\n // The caller's own `aria-label` wins over {@link PopoverLabels.panel}, which\n // is the package's English fallback for an unnamed panel. Both name the same\n // element; the DOM spelling is the one being standardised on, so where both\n // are present it is the one that survives.\n aria-label={ariaLabel ?? text.panel}\n dir={rest.dir ?? dir}\n // Without it the panel cannot take focus at all, and the trap above would\n // silently do nothing on a panel whose children are not yet tabbable.\n tabIndex={-1}\n onKeyDown={handleKeyDown}\n style={{\n // Merged over the caller's, never replaced by it: these are measured\n // against the trigger every scroll and resize, and a panel that dropped\n // them would paint at the top-left of the viewport.\n ...style,\n position: \"fixed\",\n top: pos.top,\n bottom: pos.bottom,\n left: pos.left,\n width: panelWidth,\n maxHeight: placement.maxHeight,\n }}\n className={cn(\n \"z-50 overflow-y-auto rounded-md border border-[var(--border)] bg-[var(--bg-surface)] p-3 shadow-lg outline-none\",\n className,\n )}\n >\n {children(close)}\n </div>,\n document.body,\n )}\n </>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,16 +1,17 @@
1
1
  import 'react';
2
2
  import 'lucide-react';
3
- export { cO as DEFAULT_ROW_ACTIONS_LABELS, eO as RowAction, eP as RowActionList, eQ as RowActions, eR as RowActionsCollapse, eS as RowActionsColumnOptions, eT as RowActionsLabels, eU as RowActionsProps, eV as RowActionsSize, fY as rowActionsColumn } from '../delete-account-setting-Dy4kJgUm.js';
4
- import '../data-table-labels-kpAAVi6g.js';
3
+ import './write-lock.js';
4
+ export { cP as DEFAULT_ROW_ACTIONS_LABELS, eZ as RowAction, e_ as RowActionList, e$ as RowActionTone, f0 as RowActions, f1 as RowActionsCollapse, f2 as RowActionsColumnOptions, f3 as RowActionsLabels, f4 as RowActionsProps, f5 as RowActionsSize, g9 as rowActionsColumn } from '../delete-account-setting-DAgGBrbn.js';
5
5
  import './tooltip.js';
6
- import '../hooks/use-anchored-rect.js';
7
- import '../lib/clipping.js';
6
+ import '../data-table-labels-BJ5JE_X2.js';
8
7
  import './mini-calendar.js';
9
8
  import './calendar-heatmap.js';
10
9
  import './popover.js';
11
10
  import '../hooks/use-breakpoint.js';
12
11
  import './chip.js';
13
12
  import './field-parts.js';
13
+ import '../hooks/use-anchored-rect.js';
14
+ import '../lib/clipping.js';
14
15
  import './field-sync.js';
15
16
  import '../wizard/types.js';
16
17
  import '../tour/tour.js';
@@ -52,7 +53,6 @@ import './error-boundary.js';
52
53
  import './authed-image.js';
53
54
  import './image-grid.js';
54
55
  import './lightbox.js';
55
- import './write-lock.js';
56
56
  import '../auth/sign-in-form.js';
57
57
  import '../landing/access.js';
58
58
  import '../landing/landing-labels.js';
@@ -81,6 +81,7 @@ import '../admin/admin-parts.js';
81
81
  import '../admin/admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import './appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -21,6 +21,19 @@ function glyphOf(icon) {
21
21
  const Icon = icon;
22
22
  return /* @__PURE__ */ jsx(Icon, {});
23
23
  }
24
+ const MENU_GLYPH_TONE = {
25
+ info: "text-info",
26
+ warning: "text-warning"
27
+ };
28
+ function stateAttrs(action) {
29
+ return {
30
+ "aria-expanded": action.expanded,
31
+ "aria-controls": action.expanded === void 0 ? void 0 : action.controls
32
+ };
33
+ }
34
+ function toneAttr(tone) {
35
+ return { "data-tone": tone === void 0 || tone === "default" ? void 0 : tone };
36
+ }
24
37
  function RowActions({
25
38
  actions,
26
39
  name,
@@ -28,6 +41,8 @@ function RowActions({
28
41
  size = "xs",
29
42
  menuWidth = "15rem",
30
43
  className,
44
+ glyphSize,
45
+ tooltipSide,
31
46
  labels: labelsProp
32
47
  }) {
33
48
  const labels = useKitLabels("rowActions", DEFAULT_ROW_ACTIONS_LABELS, labelsProp);
@@ -36,37 +51,65 @@ function RowActions({
36
51
  if (entries.length === 0) return null;
37
52
  const asMenu = collapse === "menu" || collapse === "auto" && large && entries.length > 1;
38
53
  if (!asMenu) {
39
- const strip = /* @__PURE__ */ jsx("div", { "data-slot": "row-actions", className: cn("inline-flex items-center gap-0.5", className), children: entries.map((action) => /* @__PURE__ */ jsx(InlineAction, { action, size }, action.key ?? action.label)) });
54
+ const strip = /* @__PURE__ */ jsx("div", { "data-slot": "row-actions", className: cn("inline-flex items-center gap-0.5", className), children: entries.map((action) => /* @__PURE__ */ jsx(
55
+ InlineAction,
56
+ {
57
+ action,
58
+ size,
59
+ glyphSize,
60
+ tooltipSide
61
+ },
62
+ action.key ?? action.label
63
+ )) });
40
64
  return large ? strip : /* @__PURE__ */ jsx(CompactControls, { children: strip });
41
65
  }
42
66
  const title = name ? labels.actionsFor(name) : labels.actions;
43
67
  const busy = entries.some((action) => action.pending);
44
- return /* @__PURE__ */ jsx(
45
- Popover,
46
- {
47
- width: menuWidth,
48
- "aria-label": title,
49
- className: "p-1",
50
- trigger: ({ open, toggle, ref }) => /* @__PURE__ */ jsx(
51
- IconButton,
68
+ const anchored = entries.filter((action) => action.dataTour);
69
+ return (
70
+ // The "⋯" in a box of its own, the inline strip's slot: the tour anchors of the
71
+ // collapsed actions lie over the button in it (see `dataTour`), and `className`
72
+ // places the box as it places the strip.
73
+ /* @__PURE__ */ jsxs("div", { "data-slot": "row-actions", className: cn("relative inline-flex", className), children: [
74
+ /* @__PURE__ */ jsx(
75
+ Popover,
52
76
  {
53
- ref,
54
- size,
55
- label: title,
56
- labelVisible: false,
57
- "aria-haspopup": "dialog",
58
- "aria-expanded": open,
59
- pending: busy,
60
- stopPropagation: true,
61
- onClick: toggle,
62
- className,
63
- children: /* @__PURE__ */ jsx(MoreHorizontal, {})
77
+ width: menuWidth,
78
+ "aria-label": title,
79
+ className: "p-1",
80
+ trigger: ({ open, toggle, ref }) => /* @__PURE__ */ jsx(
81
+ IconButton,
82
+ {
83
+ ref,
84
+ size,
85
+ label: title,
86
+ labelVisible: false,
87
+ "aria-haspopup": "dialog",
88
+ "aria-expanded": open,
89
+ pending: busy,
90
+ stopPropagation: true,
91
+ onClick: toggle,
92
+ children: /* @__PURE__ */ jsx(MoreHorizontal, {})
93
+ }
94
+ ),
95
+ onClick: stopClick,
96
+ onKeyDown: stopActivationKeys,
97
+ children: (close) => /* @__PURE__ */ jsx("ul", { "data-slot": "row-actions-menu", className: "space-y-0.5", children: entries.map((action) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(MenuAction, { action, close }) }, action.key ?? action.label)) })
64
98
  }
65
99
  ),
66
- onClick: stopClick,
67
- onKeyDown: stopActivationKeys,
68
- children: (close) => /* @__PURE__ */ jsx("ul", { "data-slot": "row-actions-menu", className: "space-y-0.5", children: entries.map((action) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(MenuAction, { action, close }) }, action.key ?? action.label)) })
69
- }
100
+ anchored.map((action) => (
101
+ // The More cell's TourAnchor: decoration over the button, which keeps every tap.
102
+ /* @__PURE__ */ jsx(
103
+ "span",
104
+ {
105
+ "aria-hidden": true,
106
+ "data-tour": action.dataTour,
107
+ className: "pointer-events-none absolute inset-0"
108
+ },
109
+ action.key ?? action.label
110
+ )
111
+ ))
112
+ ] })
70
113
  );
71
114
  }
72
115
  function stopClick(e) {
@@ -83,8 +126,17 @@ function useActionHref(action) {
83
126
  function MenuAction({ action, close }) {
84
127
  const href = useActionHref(action);
85
128
  const glyph = glyphOf(action.icon);
129
+ const glyphTone = action.tone === void 0 ? void 0 : MENU_GLYPH_TONE[action.tone];
86
130
  const words = /* @__PURE__ */ jsxs(Fragment, { children: [
87
- glyph && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "flex shrink-0 [&_svg]:size-4", children: glyph }),
131
+ glyph && /* @__PURE__ */ jsx(
132
+ "span",
133
+ {
134
+ "aria-hidden": true,
135
+ "data-tone": glyphTone ? action.tone : void 0,
136
+ className: cn("flex shrink-0 [&_svg]:size-4", glyphTone),
137
+ children: glyph
138
+ }
139
+ ),
88
140
  action.label
89
141
  ] });
90
142
  const tone = action.tone === "danger" ? "danger" : void 0;
@@ -112,11 +164,13 @@ function MenuAction({ action, close }) {
112
164
  return /* @__PURE__ */ jsx(
113
165
  Button,
114
166
  {
167
+ ...stateAttrs(action),
115
168
  type: "button",
116
169
  variant: "ghost",
117
170
  size: "sm",
118
171
  stretch: true,
119
172
  tone,
173
+ pressed: action.pressed,
120
174
  disabledReason: action.disabledReason,
121
175
  commit: action.commit,
122
176
  pending: action.pending,
@@ -127,20 +181,30 @@ function MenuAction({ action, close }) {
127
181
  }
128
182
  );
129
183
  }
130
- function InlineAction({ action, size }) {
184
+ function InlineAction({
185
+ action,
186
+ size,
187
+ glyphSize,
188
+ tooltipSide
189
+ }) {
131
190
  const href = useActionHref(action);
132
191
  const glyph = glyphOf(action.icon);
133
- const tone = action.tone === "danger" ? "danger" : void 0;
192
+ const iconTone = action.tone === "default" ? void 0 : action.tone;
193
+ const textTone = action.tone === "danger" || action.tone === "muted" ? action.tone : void 0;
194
+ const marks = { "data-tour": action.dataTour, ...toneAttr(action.tone) };
134
195
  if (glyph) {
135
196
  if (href !== void 0) {
136
197
  return /* @__PURE__ */ jsx(
137
198
  IconButton,
138
199
  {
200
+ ...marks,
139
201
  href,
140
202
  external: action.external,
141
203
  size,
204
+ glyphSize,
142
205
  label: action.label,
143
- tone,
206
+ tooltipSide,
207
+ tone: iconTone,
144
208
  disabled: action.disabled,
145
209
  stopPropagation: true,
146
210
  onClick: () => action.onSelect?.(),
@@ -151,9 +215,14 @@ function InlineAction({ action, size }) {
151
215
  return /* @__PURE__ */ jsx(
152
216
  IconButton,
153
217
  {
218
+ ...marks,
219
+ ...stateAttrs(action),
154
220
  size,
221
+ glyphSize,
155
222
  label: action.label,
156
- tone,
223
+ tooltipSide,
224
+ tone: iconTone,
225
+ pressed: action.pressed,
157
226
  disabledReason: action.disabledReason,
158
227
  commit: action.commit,
159
228
  pending: action.pending,
@@ -174,15 +243,31 @@ function InlineAction({ action, size }) {
174
243
  }
175
244
  };
176
245
  if (href !== void 0) {
177
- return /* @__PURE__ */ jsx(Button, { href, external: action.external, variant: "ghost", size: "sm", tone, disabled: action.disabled, ...stop, children: action.label });
246
+ return /* @__PURE__ */ jsx(
247
+ Button,
248
+ {
249
+ ...marks,
250
+ href,
251
+ external: action.external,
252
+ variant: "ghost",
253
+ size: "sm",
254
+ tone: textTone,
255
+ disabled: action.disabled,
256
+ ...stop,
257
+ children: action.label
258
+ }
259
+ );
178
260
  }
179
261
  return /* @__PURE__ */ jsx(
180
262
  Button,
181
263
  {
264
+ ...marks,
265
+ ...stateAttrs(action),
182
266
  type: "button",
183
267
  variant: "ghost",
184
268
  size: "sm",
185
- tone,
269
+ tone: textTone,
270
+ pressed: action.pressed,
186
271
  disabledReason: action.disabledReason,
187
272
  commit: action.commit,
188
273
  pending: action.pending,
@@ -202,6 +287,8 @@ function rowActionsColumn({
202
287
  name,
203
288
  collapse,
204
289
  size,
290
+ glyphSize,
291
+ tooltipSide,
205
292
  headerText,
206
293
  labels,
207
294
  column
@@ -210,7 +297,18 @@ function rowActionsColumn({
210
297
  key,
211
298
  header: /* @__PURE__ */ jsx(RowActionsHeader, { text: headerText, labels }),
212
299
  headerText,
213
- cell: (row) => /* @__PURE__ */ jsx(RowActions, { actions: actions(row), name: name?.(row), collapse, size, labels }),
300
+ cell: (row) => /* @__PURE__ */ jsx(
301
+ RowActions,
302
+ {
303
+ actions: actions(row),
304
+ name: name?.(row),
305
+ collapse,
306
+ size,
307
+ glyphSize,
308
+ tooltipSide,
309
+ labels
310
+ }
311
+ ),
214
312
  className: "w-px whitespace-nowrap text-end",
215
313
  headClassName: "w-px",
216
314
  noRowLink: true,