@eifi1/ui-kit 0.33.0 → 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 (161) hide show
  1. package/README.md +3 -3
  2. package/dist/account/delete-account-setting.d.ts +2 -1
  3. package/dist/admin/admin-action-confirm.d.ts +2 -1
  4. package/dist/admin/invitations-panel.d.ts +2 -1
  5. package/dist/admin/plan-change.d.ts +2 -1
  6. package/dist/admin/plan-change.js +3 -1
  7. package/dist/admin/plan-change.js.map +1 -1
  8. package/dist/admin/reviewer-scope-editor.d.ts +2 -1
  9. package/dist/admin/role-select.d.ts +2 -1
  10. package/dist/admin/transfer-ownership-dialog.d.ts +2 -1
  11. package/dist/admin/user-roster.d.ts +2 -1
  12. package/dist/auth/register-form.d.ts +2 -1
  13. package/dist/billing/billing-banner.d.ts +2 -1
  14. package/dist/billing/billing-labels.d.ts +2 -1
  15. package/dist/billing/billing-lock.d.ts +2 -1
  16. package/dist/billing/billing-overview.d.ts +2 -1
  17. package/dist/billing/billing-standing.d.ts +2 -1
  18. package/dist/billing/checkout-processing.d.ts +2 -1
  19. package/dist/billing/pay-page.d.ts +11 -6
  20. package/dist/billing/pay-page.js.map +1 -1
  21. package/dist/billing/plan-card.d.ts +2 -1
  22. package/dist/billing/plan-limit-notice.d.ts +2 -1
  23. package/dist/billing/plan-limit-toast.d.ts +2 -1
  24. package/dist/billing/subscription-actions.d.ts +2 -1
  25. package/dist/billing/subscription-status-chip.d.ts +2 -1
  26. package/dist/chart.d.ts +2 -1
  27. package/dist/components/amount-input.d.ts +2 -1
  28. package/dist/components/autocomplete.js +2 -2
  29. package/dist/components/autocomplete.js.map +1 -1
  30. package/dist/components/button-group.d.ts +2 -1
  31. package/dist/components/calculator.d.ts +2 -1
  32. package/dist/components/combobox-core.js +2 -1
  33. package/dist/components/combobox-core.js.map +1 -1
  34. package/dist/components/combobox.js +2 -2
  35. package/dist/components/combobox.js.map +1 -1
  36. package/dist/components/confirm-dialog.d.ts +2 -1
  37. package/dist/components/copy-button.d.ts +2 -1
  38. package/dist/components/danger-confirm.d.ts +2 -1
  39. package/dist/components/dropdown.js +9 -1
  40. package/dist/components/dropdown.js.map +1 -1
  41. package/dist/components/facing-pair.d.ts +2 -1
  42. package/dist/components/file-button.d.ts +2 -1
  43. package/dist/components/file-dropzone.d.ts +2 -1
  44. package/dist/components/form-actions.d.ts +2 -1
  45. package/dist/components/full-bleed-dialog.js.map +1 -1
  46. package/dist/components/iban-input.d.ts +2 -1
  47. package/dist/components/language-select.d.ts +2 -1
  48. package/dist/components/legal-page.d.ts +2 -1
  49. package/dist/components/legal.d.ts +2 -1
  50. package/dist/components/modal.js.map +1 -1
  51. package/dist/components/money-field.d.ts +2 -1
  52. package/dist/components/number-field.d.ts +9 -1
  53. package/dist/components/number-field.js.map +1 -1
  54. package/dist/components/number-input.d.ts +29 -2
  55. package/dist/components/number-input.js +27 -12
  56. package/dist/components/number-input.js.map +1 -1
  57. package/dist/components/numpad-sheet.d.ts +2 -1
  58. package/dist/components/phone-input.d.ts +2 -1
  59. package/dist/components/popover.js +6 -2
  60. package/dist/components/popover.js.map +1 -1
  61. package/dist/components/row-actions.d.ts +2 -1
  62. package/dist/components/series-chart.d.ts +2 -1
  63. package/dist/components/settings-fields.d.ts +2 -1
  64. package/dist/components/share-card.d.ts +2 -1
  65. package/dist/components/skeleton.d.ts +10 -2
  66. package/dist/components/skeleton.js.map +1 -1
  67. package/dist/components/text-link.d.ts +2 -1
  68. package/dist/components/time-input.d.ts +2 -1
  69. package/dist/components/ui.d.ts +2 -1
  70. package/dist/{delete-account-setting-DiPX4Vue.d.ts → delete-account-setting-DAgGBrbn.d.ts} +44 -7
  71. package/dist/feedback/feedback-attachment.d.ts +2 -1
  72. package/dist/feedback/feedback-context.d.ts +2 -1
  73. package/dist/feedback/feedback-crash.d.ts +2 -1
  74. package/dist/feedback/feedback-dialog.d.ts +2 -1
  75. package/dist/feedback/feedback-inbox.d.ts +2 -1
  76. package/dist/feedback/feedback-labels.d.ts +2 -1
  77. package/dist/feedback/feedback-menu.d.ts +2 -1
  78. package/dist/feedback/feedback-record.d.ts +2 -1
  79. package/dist/feedback/feedback-row-detail.d.ts +2 -1
  80. package/dist/feedback/feedback-status-undo.d.ts +2 -1
  81. package/dist/feedback/feedback-submit.d.ts +2 -1
  82. package/dist/feedback/feedback-swipe.d.ts +2 -1
  83. package/dist/feedback/feedback-table.d.ts +2 -1
  84. package/dist/feedback/feedback-thread.d.ts +3 -2
  85. package/dist/feedback/feedback-thread.js +5 -1
  86. package/dist/feedback/feedback-thread.js.map +1 -1
  87. package/dist/feedback.d.ts +2 -1
  88. package/dist/hooks/use-anchored-panel.d.ts +37 -7
  89. package/dist/hooks/use-anchored-panel.js +6 -7
  90. package/dist/hooks/use-anchored-panel.js.map +1 -1
  91. package/dist/hooks/use-file-drop.d.ts +2 -1
  92. package/dist/hooks/use-focus-trap.d.ts +8 -0
  93. package/dist/hooks/use-focus-trap.js +17 -11
  94. package/dist/hooks/use-focus-trap.js.map +1 -1
  95. package/dist/i18n/defaults.d.ts +2 -1
  96. package/dist/i18n/german.d.ts +2 -1
  97. package/dist/i18n/german.js +4 -1
  98. package/dist/i18n/german.js.map +1 -1
  99. package/dist/i18n/kit-labels.d.ts +2 -1
  100. package/dist/i18n/languages.d.ts +2 -1
  101. package/dist/i18n/locales/de-CH.d.ts +2 -1
  102. package/dist/i18n/locales/en.d.ts +2 -1
  103. package/dist/i18n/locales/es.d.ts +2 -1
  104. package/dist/i18n/locales/es.js +2 -1
  105. package/dist/i18n/locales/es.js.map +1 -1
  106. package/dist/i18n/locales/fr.d.ts +2 -1
  107. package/dist/i18n/locales/fr.js +3 -1
  108. package/dist/i18n/locales/fr.js.map +1 -1
  109. package/dist/i18n/locales/hu.d.ts +2 -1
  110. package/dist/i18n/locales/hu.js +4 -2
  111. package/dist/i18n/locales/hu.js.map +1 -1
  112. package/dist/i18n/locales/it.d.ts +2 -1
  113. package/dist/i18n/locales/it.js +2 -1
  114. package/dist/i18n/locales/it.js.map +1 -1
  115. package/dist/i18n/locales/zh.d.ts +2 -1
  116. package/dist/i18n/locales/zh.js +1 -0
  117. package/dist/i18n/locales/zh.js.map +1 -1
  118. package/dist/i18n/plurals.d.ts +20 -1
  119. package/dist/i18n/plurals.js +11 -0
  120. package/dist/i18n/plurals.js.map +1 -1
  121. package/dist/i18n/review.d.ts +2 -1
  122. package/dist/index.d.ts +2 -2
  123. package/dist/index.js.map +1 -1
  124. package/dist/landing/landing-actions.d.ts +2 -1
  125. package/dist/landing/landing-sections.d.ts +2 -1
  126. package/dist/landing/public-header.d.ts +2 -1
  127. package/dist/landing/routing.d.ts +2 -1
  128. package/dist/rhf/fields.d.ts +2 -1
  129. package/dist/rhf/form.d.ts +2 -1
  130. package/dist/rhf.d.ts +2 -1
  131. package/dist/settings/settings-section.d.ts +2 -1
  132. package/dist/shell/app-shell.d.ts +2 -1
  133. package/dist/shell/top-bar-brand.d.ts +2 -1
  134. package/dist/shell.d.ts +2 -1
  135. package/dist/wizard/stepper-nav.d.ts +2 -1
  136. package/dist/wizard.d.ts +2 -1
  137. package/package.json +1 -1
  138. package/src/admin/plan-change.tsx +35 -7
  139. package/src/billing/pay-page.ts +9 -5
  140. package/src/components/autocomplete.tsx +5 -2
  141. package/src/components/combobox-core.tsx +5 -2
  142. package/src/components/combobox.tsx +4 -2
  143. package/src/components/dropdown.tsx +12 -2
  144. package/src/components/full-bleed-dialog.tsx +6 -0
  145. package/src/components/modal.tsx +5 -0
  146. package/src/components/number-field.tsx +7 -0
  147. package/src/components/number-input.tsx +71 -11
  148. package/src/components/popover.tsx +6 -2
  149. package/src/components/skeleton.tsx +10 -2
  150. package/src/feedback/feedback-thread.tsx +20 -1
  151. package/src/hooks/use-anchored-panel.ts +60 -14
  152. package/src/hooks/use-focus-trap.ts +55 -12
  153. package/src/i18n/german.ts +4 -1
  154. package/src/i18n/locales/es.ts +2 -1
  155. package/src/i18n/locales/fr.ts +3 -1
  156. package/src/i18n/locales/hu.ts +4 -2
  157. package/src/i18n/locales/it.ts +2 -1
  158. package/src/i18n/locales/zh.ts +1 -0
  159. package/src/i18n/plurals.ts +29 -0
  160. package/src/index.ts +3 -1
  161. package/tokens.css +18 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/use-focus-trap.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\n/**\n * Keep keyboard focus inside an open overlay, and give it back when it closes.\n *\n * Extracted from `Modal`, which was the ONLY component in this package that did any of\n * this. Every other overlay — the full-bleed dialog a phone's row editor opens, the\n * picker sheet, the numpad, the tour card — declared `role=\"dialog\" aria-modal=\"true\"`\n * and then left focus on the page behind it. That is the worst of the two possible\n * bugs: assistive technology hides the page because of `aria-modal`, while the user's\n * focus is still down there in the part that is now hidden, so Tab walks through\n * controls the screen reader refuses to describe.\n *\n * What this does beyond Modal's original copy:\n *\n * - **Recomputes the tabbable list on every Tab**, not once when the overlay opens.\n * These panels have conditionally-rendered contents — a search box that appears\n * once options load, a \"clear\" button that exists only while a filter is set — and\n * a list captured at open time traps focus against elements that have since gone.\n * - **Guards the restore target.** The element focused before opening may have left\n * the DOM by the time the overlay closes (the row that owned the button was\n * filtered away). Calling `.focus()` on a detached node silently drops focus to\n * `<body>`, which for a keyboard user means losing their place in the page\n * entirely. When the target is gone, focus falls back to the nearest thing that\n * still exists rather than to nothing.\n * - **Nests.** An overlay can open another — a picker inside a dialog. Only the\n * innermost trap handles a keystroke, and closing it hands control back to the one\n * underneath. Innermost is decided by DOM containment rather than by mount order;\n * see the note on `TRAP_ATTR` for why the obvious stack is wrong.\n * `use-overlay-history.ts` reasons about the same nesting problem for the back\n * button; this is the focus half of it.\n */\n\n/**\n * Tabbable descendants, in DOM order.\n *\n * Deliberately the same selector `Modal` used, rather than a more thorough one that\n * also walks `inert`, `visibility: hidden` and open `<details>`: this is the set that\n * has been in production across three apps, and widening it here would change which\n * element receives focus in overlays nobody asked me to change the behaviour of.\n */\nconst FOCUSABLE =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Marks a container whose trap is currently active.\n *\n * \"Which trap is innermost?\" is answered by DOM CONTAINMENT, not by mount order. The\n * obvious implementation — a module-level stack pushed in the effect — is wrong, and\n * wrong in a way that passes a casual reading: React runs CHILD effects before PARENT\n * effects, so a nested overlay pushes first and the outer one ends up on top of the\n * stack, claiming to be innermost. The outer trap then handles the keystroke and wraps\n * focus to its own first element, jumping the user out of the picker they had open.\n *\n * An attribute on the live DOM cannot disagree with the DOM.\n */\nconst TRAP_ATTR = \"data-focus-trap\";\n\nexport interface FocusTrapOptions {\n /** Trap only while this is true. Mount-time `true` is the common case. */\n active?: boolean;\n /** Return focus to whatever had it before the trap engaged. Default `true`. */\n restoreFocus?: boolean;\n /**\n * What to focus when the trap engages. Default is the container itself, which is\n * what `Modal` does and why: focusing the first FIELD pops the software keyboard on\n * a phone the moment a dialog opens, before the user has asked to type.\n * The container needs `tabIndex={-1}` for this to work.\n */\n initialFocus?: \"container\" | \"first\" | (() => HTMLElement | null);\n}\n\nexport function useFocusTrap(\n containerRef: React.RefObject<HTMLElement | null>,\n { active = true, restoreFocus = true, initialFocus = \"container\" }: FocusTrapOptions = {},\n): void {\n // Latest-ref so a caller passing an inline `initialFocus` thunk — which every caller\n // will — does not tear the trap down and re-engage it on each render, stealing focus\n // back to the top of the panel mid-interaction.\n const optsRef = useRef({ restoreFocus, initialFocus });\n useEffect(() => {\n optsRef.current = { restoreFocus, initialFocus };\n });\n\n useEffect(() => {\n if (!active) return;\n const container = containerRef.current;\n if (!container) return;\n\n container.setAttribute(TRAP_ATTR, \"\");\n const previouslyFocused = document.activeElement as HTMLElement | null;\n\n /** Is `node` inside a trap nested within this one? */\n const inNestedTrap = (node: Element | null) => {\n if (!node) return false;\n const owner = node.closest(`[${TRAP_ATTR}]`);\n return owner !== null && owner !== container;\n };\n\n const tabbables = () =>\n Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n // A tabbable inside a NESTED open overlay belongs to that overlay's trap, not\n // to this one — otherwise the outer list spans both panels and Tab from the\n // inner panel's last control wraps to the OUTER panel's first.\n (el) => !inNestedTrap(el),\n );\n\n const { initialFocus: how } = optsRef.current;\n if (typeof how === \"function\") {\n (how() ?? container).focus();\n } else if (how === \"first\") {\n (tabbables()[0] ?? container).focus();\n } else {\n container.focus();\n }\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"Tab\") return;\n // Only the innermost trap acts. The event bubbles through every ancestor\n // container, so without this an outer dialog and an inner picker both handle one\n // Tab and fight over where focus lands.\n if (inNestedTrap(document.activeElement)) return;\n\n // Recomputed per keystroke — see the note at the top of this file.\n const items = tabbables();\n if (items.length === 0) {\n e.preventDefault();\n container.focus();\n return;\n }\n const first = items[0];\n const last = items[items.length - 1];\n const activeEl = document.activeElement;\n if (e.shiftKey && (activeEl === first || activeEl === container)) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && activeEl === last) {\n e.preventDefault();\n first.focus();\n }\n };\n\n // On the container, not the document: a portalled child panel (an open dropdown\n // rendered to <body>) is outside this subtree and manages its own focus, which is\n // the behaviour Modal's comment describes and consumers already depend on.\n container.addEventListener(\"keydown\", onKeyDown);\n\n return () => {\n container.removeEventListener(\"keydown\", onKeyDown);\n container.removeAttribute(TRAP_ATTR);\n\n if (!optsRef.current.restoreFocus) return;\n // `isConnected` is the whole point: restoring to a node that has been removed\n // sends focus to <body> and the user loses their place entirely. When the\n // original is gone, leave focus where the browser put it rather than moving it\n // somewhere arbitrary — a wrong guess is worse than no move.\n if (previouslyFocused?.isConnected) previouslyFocused.focus?.();\n };\n }, [active, containerRef]);\n}\n"],"mappings":";AAAA,SAAS,WAAW,cAAc;AAyClC,MAAM,YACJ;AAcF,MAAM,YAAY;AAgBX,SAAS,aACd,cACA,EAAE,SAAS,MAAM,eAAe,MAAM,eAAe,YAAY,IAAsB,CAAC,GAClF;AAIN,QAAM,UAAU,OAAO,EAAE,cAAc,aAAa,CAAC;AACrD,YAAU,MAAM;AACd,YAAQ,UAAU,EAAE,cAAc,aAAa;AAAA,EACjD,CAAC;AAED,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAEhB,cAAU,aAAa,WAAW,EAAE;AACpC,UAAM,oBAAoB,SAAS;AAGnC,UAAM,eAAe,CAAC,SAAyB;AAC7C,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,QAAQ,KAAK,QAAQ,IAAI,SAAS,GAAG;AAC3C,aAAO,UAAU,QAAQ,UAAU;AAAA,IACrC;AAEA,UAAM,YAAY,MAChB,MAAM,KAAK,UAAU,iBAA8B,SAAS,CAAC,EAAE;AAAA;AAAA;AAAA;AAAA,MAI7D,CAAC,OAAO,CAAC,aAAa,EAAE;AAAA,IAC1B;AAEF,UAAM,EAAE,cAAc,IAAI,IAAI,QAAQ;AACtC,QAAI,OAAO,QAAQ,YAAY;AAC7B,OAAC,IAAI,KAAK,WAAW,MAAM;AAAA,IAC7B,WAAW,QAAQ,SAAS;AAC1B,OAAC,UAAU,EAAE,CAAC,KAAK,WAAW,MAAM;AAAA,IACtC,OAAO;AACL,gBAAU,MAAM;AAAA,IAClB;AAEA,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,MAAO;AAIrB,UAAI,aAAa,SAAS,aAAa,EAAG;AAG1C,YAAM,QAAQ,UAAU;AACxB,UAAI,MAAM,WAAW,GAAG;AACtB,UAAE,eAAe;AACjB,kBAAU,MAAM;AAChB;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,CAAC;AACrB,YAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,YAAM,WAAW,SAAS;AAC1B,UAAI,EAAE,aAAa,aAAa,SAAS,aAAa,YAAY;AAChE,UAAE,eAAe;AACjB,aAAK,MAAM;AAAA,MACb,WAAW,CAAC,EAAE,YAAY,aAAa,MAAM;AAC3C,UAAE,eAAe;AACjB,cAAM,MAAM;AAAA,MACd;AAAA,IACF;AAKA,cAAU,iBAAiB,WAAW,SAAS;AAE/C,WAAO,MAAM;AACX,gBAAU,oBAAoB,WAAW,SAAS;AAClD,gBAAU,gBAAgB,SAAS;AAEnC,UAAI,CAAC,QAAQ,QAAQ,aAAc;AAKnC,UAAI,mBAAmB,YAAa,mBAAkB,QAAQ;AAAA,IAChE;AAAA,EACF,GAAG,CAAC,QAAQ,YAAY,CAAC;AAC3B;","names":[]}
1
+ {"version":3,"sources":["../../src/hooks/use-focus-trap.ts"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState } from \"react\";\n\n/**\n * Keep keyboard focus inside an open overlay, and give it back when it closes.\n *\n * Extracted from `Modal`, which was the ONLY component in this package that did any of\n * this. Every other overlay — the full-bleed dialog a phone's row editor opens, the\n * picker sheet, the numpad, the tour card — declared `role=\"dialog\" aria-modal=\"true\"`\n * and then left focus on the page behind it. That is the worst of the two possible\n * bugs: assistive technology hides the page because of `aria-modal`, while the user's\n * focus is still down there in the part that is now hidden, so Tab walks through\n * controls the screen reader refuses to describe.\n *\n * What this does beyond Modal's original copy:\n *\n * - **Recomputes the tabbable list on every Tab**, not once when the overlay opens.\n * These panels have conditionally-rendered contents — a search box that appears\n * once options load, a \"clear\" button that exists only while a filter is set — and\n * a list captured at open time traps focus against elements that have since gone.\n * - **Guards the restore target.** The element focused before opening may have left\n * the DOM by the time the overlay closes (the row that owned the button was\n * filtered away). Calling `.focus()` on a detached node silently drops focus to\n * `<body>`, which for a keyboard user means losing their place in the page\n * entirely. When the target is gone, focus falls back to the nearest thing that\n * still exists rather than to nothing.\n * - **Leaves an app's own autofocus alone** (0.33.1). React honours a child's\n * `autoFocus` while it commits, and this hook's effect runs AFTER that commit, so\n * focusing the container there took focus straight back off the field the app had\n * asked for. See `initialFocus`.\n * - **Nests.** An overlay can open another — a picker inside a dialog. Only the\n * innermost trap handles a keystroke, and closing it hands control back to the one\n * underneath. Innermost is decided by DOM containment rather than by mount order;\n * see the note on `TRAP_ATTR` for why the obvious stack is wrong.\n * `use-overlay-history.ts` reasons about the same nesting problem for the back\n * button; this is the focus half of it.\n */\n\n/**\n * Tabbable descendants, in DOM order.\n *\n * Deliberately the same selector `Modal` used, rather than a more thorough one that\n * also walks `inert`, `visibility: hidden` and open `<details>`: this is the set that\n * has been in production across three apps, and widening it here would change which\n * element receives focus in overlays nobody asked me to change the behaviour of.\n */\nconst FOCUSABLE =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Marks a container whose trap is currently active.\n *\n * \"Which trap is innermost?\" is answered by DOM CONTAINMENT, not by mount order. The\n * obvious implementation — a module-level stack pushed in the effect — is wrong, and\n * wrong in a way that passes a casual reading: React runs CHILD effects before PARENT\n * effects, so a nested overlay pushes first and the outer one ends up on top of the\n * stack, claiming to be innermost. The outer trap then handles the keystroke and wraps\n * focus to its own first element, jumping the user out of the picker they had open.\n *\n * An attribute on the live DOM cannot disagree with the DOM.\n */\nconst TRAP_ATTR = \"data-focus-trap\";\n\nexport interface FocusTrapOptions {\n /** Trap only while this is true. Mount-time `true` is the common case. */\n active?: boolean;\n /** Return focus to whatever had it before the trap engaged. Default `true`. */\n restoreFocus?: boolean;\n /**\n * What to focus when the trap engages. Default is the container itself, which is\n * what `Modal` does and why: focusing the first FIELD pops the software keyboard on\n * a phone the moment a dialog opens, before the user has asked to type.\n * The container needs `tabIndex={-1}` for this to work.\n *\n * `\"container\"` and `\"first\"` both step aside when focus is ALREADY inside the\n * container, on something other than the container itself. That is a child the app\n * focused on purpose — an `autoFocus`, which React honours before this hook's effect\n * runs — and the keyboard it brings up is the one the user was promised. Moving focus\n * off it lost keksdose's create card its amount field the instant it got it, and on a\n * phone the numpad sheet with it, which shows only while the field is focused (live\n * #402). A function is the caller's explicit choice and is always followed.\n */\n initialFocus?: \"container\" | \"first\" | (() => HTMLElement | null);\n}\n\n/** Whatever has focus right now; nothing on the server, where there is no document. */\nfunction focusedElement(): Element | null {\n return typeof document === \"undefined\" ? null : document.activeElement;\n}\n\nexport function useFocusTrap(\n containerRef: React.RefObject<HTMLElement | null>,\n { active = true, restoreFocus = true, initialFocus = \"container\" }: FocusTrapOptions = {},\n): void {\n // Latest-ref so a caller passing an inline `initialFocus` thunk — which every caller\n // will — does not tear the trap down and re-engage it on each render, stealing focus\n // back to the top of the panel mid-interaction. Written in a LAYOUT effect so that\n // the trap's cleanup, which runs after the layout effects of the same commit, reads\n // this render's `active` rather than the last one's — see the cleanup for why.\n const optsRef = useRef({ active, restoreFocus, initialFocus });\n useLayoutEffect(() => {\n optsRef.current = { active, restoreFocus, initialFocus };\n });\n\n // Where focus was BEFORE the trap engaged: the element it is given back to on close.\n //\n // Read DURING the render that turns the trap on, and that timing is the point. React\n // honours a child's `autoFocus` while it commits that render, before any effect runs,\n // so the effect below asking `document.activeElement` would get the child, inside the\n // overlay. Restoring to that on close aimed focus at the dialog that was closing —\n // already out of the DOM, so focus fell to <body> rather than going back to the\n // button that opened it.\n //\n // State adjusted during render (the pattern `Modal` uses for `open`) rather than a\n // ref written there: set once per opening and cleared on release, so a re-render\n // while open cannot replace it with something inside, and StrictMode's second render\n // and second effect both see the same element. `undefined` means \"not captured\";\n // `null` is a capture that found nothing.\n const [opener, setOpener] = useState<Element | null | undefined>(undefined);\n if (active && opener === undefined) setOpener(focusedElement());\n else if (!active && opener !== undefined) setOpener(undefined);\n\n useEffect(() => {\n if (!active) return;\n const container = containerRef.current;\n if (!container) return;\n\n container.setAttribute(TRAP_ATTR, \"\");\n\n /** Is `node` inside a trap nested within this one? */\n const inNestedTrap = (node: Element | null) => {\n if (!node) return false;\n const owner = node.closest(`[${TRAP_ATTR}]`);\n return owner !== null && owner !== container;\n };\n\n const tabbables = () =>\n Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n // A tabbable inside a NESTED open overlay belongs to that overlay's trap, not\n // to this one — otherwise the outer list spans both panels and Tab from the\n // inner panel's last control wraps to the OUTER panel's first.\n (el) => !inNestedTrap(el),\n );\n\n const { initialFocus: how } = optsRef.current;\n const focused = document.activeElement;\n // A child that already has focus was given it on purpose — see `initialFocus`.\n const childHasFocus = focused !== container && container.contains(focused);\n if (typeof how === \"function\") {\n (how() ?? container).focus();\n } else if (!childHasFocus) {\n (how === \"first\" ? (tabbables()[0] ?? container) : container).focus();\n }\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"Tab\") return;\n // Only the innermost trap acts. The event bubbles through every ancestor\n // container, so without this an outer dialog and an inner picker both handle one\n // Tab and fight over where focus lands.\n if (inNestedTrap(document.activeElement)) return;\n\n // Recomputed per keystroke — see the note at the top of this file.\n const items = tabbables();\n if (items.length === 0) {\n e.preventDefault();\n container.focus();\n return;\n }\n const first = items[0];\n const last = items[items.length - 1];\n const activeEl = document.activeElement;\n if (e.shiftKey && (activeEl === first || activeEl === container)) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && activeEl === last) {\n e.preventDefault();\n first.focus();\n }\n };\n\n // On the container, not the document: a portalled child panel (an open dropdown\n // rendered to <body>) is outside this subtree and manages its own focus, which is\n // the behaviour Modal's comment describes and consumers already depend on.\n container.addEventListener(\"keydown\", onKeyDown);\n\n return () => {\n container.removeEventListener(\"keydown\", onKeyDown);\n container.removeAttribute(TRAP_ATTR);\n\n // Only when the trap is really being released: the container has left the page,\n // or `active` has turned false. StrictMode runs this cleanup once more right after\n // mounting, with the overlay still up, and then the effect again — and React does\n // not re-apply `autoFocus` for that second run. Restoring here would pull focus\n // out of the autofocused field for good: live #402 again, in development.\n if (container.isConnected && optsRef.current.active) return;\n if (!optsRef.current.restoreFocus) return;\n // `isConnected` is the whole point: restoring to a node that has been removed\n // sends focus to <body> and the user loses their place entirely. When the\n // original is gone, leave focus where the browser put it rather than moving it\n // somewhere arbitrary — a wrong guess is worse than no move.\n if (opener?.isConnected) (opener as HTMLElement).focus?.();\n };\n }, [active, containerRef, opener]);\n}\n"],"mappings":";AAAA,SAAS,WAAW,iBAAiB,QAAQ,gBAAgB;AA6C7D,MAAM,YACJ;AAcF,MAAM,YAAY;AAyBlB,SAAS,iBAAiC;AACxC,SAAO,OAAO,aAAa,cAAc,OAAO,SAAS;AAC3D;AAEO,SAAS,aACd,cACA,EAAE,SAAS,MAAM,eAAe,MAAM,eAAe,YAAY,IAAsB,CAAC,GAClF;AAMN,QAAM,UAAU,OAAO,EAAE,QAAQ,cAAc,aAAa,CAAC;AAC7D,kBAAgB,MAAM;AACpB,YAAQ,UAAU,EAAE,QAAQ,cAAc,aAAa;AAAA,EACzD,CAAC;AAgBD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAqC,MAAS;AAC1E,MAAI,UAAU,WAAW,OAAW,WAAU,eAAe,CAAC;AAAA,WACrD,CAAC,UAAU,WAAW,OAAW,WAAU,MAAS;AAE7D,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAEhB,cAAU,aAAa,WAAW,EAAE;AAGpC,UAAM,eAAe,CAAC,SAAyB;AAC7C,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,QAAQ,KAAK,QAAQ,IAAI,SAAS,GAAG;AAC3C,aAAO,UAAU,QAAQ,UAAU;AAAA,IACrC;AAEA,UAAM,YAAY,MAChB,MAAM,KAAK,UAAU,iBAA8B,SAAS,CAAC,EAAE;AAAA;AAAA;AAAA;AAAA,MAI7D,CAAC,OAAO,CAAC,aAAa,EAAE;AAAA,IAC1B;AAEF,UAAM,EAAE,cAAc,IAAI,IAAI,QAAQ;AACtC,UAAM,UAAU,SAAS;AAEzB,UAAM,gBAAgB,YAAY,aAAa,UAAU,SAAS,OAAO;AACzE,QAAI,OAAO,QAAQ,YAAY;AAC7B,OAAC,IAAI,KAAK,WAAW,MAAM;AAAA,IAC7B,WAAW,CAAC,eAAe;AACzB,OAAC,QAAQ,UAAW,UAAU,EAAE,CAAC,KAAK,YAAa,WAAW,MAAM;AAAA,IACtE;AAEA,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,MAAO;AAIrB,UAAI,aAAa,SAAS,aAAa,EAAG;AAG1C,YAAM,QAAQ,UAAU;AACxB,UAAI,MAAM,WAAW,GAAG;AACtB,UAAE,eAAe;AACjB,kBAAU,MAAM;AAChB;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,CAAC;AACrB,YAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,YAAM,WAAW,SAAS;AAC1B,UAAI,EAAE,aAAa,aAAa,SAAS,aAAa,YAAY;AAChE,UAAE,eAAe;AACjB,aAAK,MAAM;AAAA,MACb,WAAW,CAAC,EAAE,YAAY,aAAa,MAAM;AAC3C,UAAE,eAAe;AACjB,cAAM,MAAM;AAAA,MACd;AAAA,IACF;AAKA,cAAU,iBAAiB,WAAW,SAAS;AAE/C,WAAO,MAAM;AACX,gBAAU,oBAAoB,WAAW,SAAS;AAClD,gBAAU,gBAAgB,SAAS;AAOnC,UAAI,UAAU,eAAe,QAAQ,QAAQ,OAAQ;AACrD,UAAI,CAAC,QAAQ,QAAQ,aAAc;AAKnC,UAAI,QAAQ,YAAa,CAAC,OAAuB,QAAQ;AAAA,IAC3D;AAAA,EACF,GAAG,CAAC,QAAQ,cAAc,MAAM,CAAC;AACnC;","names":[]}
@@ -1,4 +1,4 @@
1
- import { fJ as UiKitLabels } from '../delete-account-setting-DiPX4Vue.js';
1
+ import { fJ as UiKitLabels } from '../delete-account-setting-DAgGBrbn.js';
2
2
  import 'react';
3
3
  import '../components/tooltip.js';
4
4
  import '../hooks/use-anchored-rect.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 './plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -1,4 +1,4 @@
1
- import { fJ as UiKitLabels } from '../delete-account-setting-DiPX4Vue.js';
1
+ import { fJ as UiKitLabels } from '../delete-account-setting-DAgGBrbn.js';
2
2
  import 'react';
3
3
  import '../components/tooltip.js';
4
4
  import '../hooks/use-anchored-rect.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 './plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -1462,7 +1462,10 @@ Die vollst\xE4ndige Postanschrift wird betroffenen Personen und Aufsichtsbeh\xF6
1462
1462
  keptBeta: "Die Beta beh\xE4lt ihr Ende; nur der Tarif wurde ge\xE4ndert.",
1463
1463
  untilDone: (date) => `Kostenlos bis ${date}.`,
1464
1464
  overLimit: "\xDCber dem Limit des neuen Tarifs: Nichts wird entfernt, neue Eintr\xE4ge sind gesperrt.",
1465
- usageOf: (used, limit, dimension) => `${used} von ${limit} ${dimension}`
1465
+ // 0.33.1: the app's word heads the line. After "von" it would want the dative
1466
+ // plural ("von 5 Objekten"), which no app's `one`/`other` form carries; as a
1467
+ // heading it is the nominative, in the form for the limit ("Objekt: 1 von 1").
1468
+ usageOf: (used, limit, dimension) => `${dimension}: ${used} von ${limit}`
1466
1469
  },
1467
1470
  // 0.32.0 — docs/text-size-harmonization.md §6: the text-size and contrast settings in
1468
1471
  // the "Darstellung" group.