@eifi1/ui-kit 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/README.md +219 -0
  2. package/dist/components/account-settings.d.ts +71 -0
  3. package/dist/components/account-settings.js +159 -0
  4. package/dist/components/account-settings.js.map +1 -0
  5. package/dist/components/alert-banner.d.ts +20 -0
  6. package/dist/components/alert-banner.js +39 -0
  7. package/dist/components/alert-banner.js.map +1 -0
  8. package/dist/components/amount-input.d.ts +117 -0
  9. package/dist/components/amount-input.js +243 -0
  10. package/dist/components/amount-input.js.map +1 -0
  11. package/dist/components/calculator.d.ts +42 -0
  12. package/dist/components/calculator.js +161 -0
  13. package/dist/components/calculator.js.map +1 -0
  14. package/dist/components/chart.d.ts +58 -0
  15. package/dist/components/chart.js +158 -0
  16. package/dist/components/chart.js.map +1 -0
  17. package/dist/components/combobox-core.d.ts +77 -0
  18. package/dist/components/combobox-core.js +263 -0
  19. package/dist/components/combobox-core.js.map +1 -0
  20. package/dist/components/combobox.d.ts +141 -0
  21. package/dist/components/combobox.js +550 -0
  22. package/dist/components/combobox.js.map +1 -0
  23. package/dist/components/currency-select.d.ts +37 -0
  24. package/dist/components/currency-select.js +131 -0
  25. package/dist/components/currency-select.js.map +1 -0
  26. package/dist/components/data-table-filter-popover.d.ts +28 -0
  27. package/dist/components/data-table-filter-popover.js +201 -0
  28. package/dist/components/data-table-filter-popover.js.map +1 -0
  29. package/dist/components/data-table-filters.d.ts +5 -0
  30. package/dist/components/data-table-filters.js +118 -0
  31. package/dist/components/data-table-filters.js.map +1 -0
  32. package/dist/components/data-table-labels.d.ts +33 -0
  33. package/dist/components/data-table-labels.js +53 -0
  34. package/dist/components/data-table-labels.js.map +1 -0
  35. package/dist/components/data-table-pagination.d.ts +18 -0
  36. package/dist/components/data-table-pagination.js +92 -0
  37. package/dist/components/data-table-pagination.js.map +1 -0
  38. package/dist/components/data-table-sort.d.ts +23 -0
  39. package/dist/components/data-table-sort.js +55 -0
  40. package/dist/components/data-table-sort.js.map +1 -0
  41. package/dist/components/data-table.d.ts +5 -0
  42. package/dist/components/data-table.js +926 -0
  43. package/dist/components/data-table.js.map +1 -0
  44. package/dist/components/date-picker.d.ts +85 -0
  45. package/dist/components/date-picker.js +253 -0
  46. package/dist/components/date-picker.js.map +1 -0
  47. package/dist/components/dropdown.d.ts +98 -0
  48. package/dist/components/dropdown.js +122 -0
  49. package/dist/components/dropdown.js.map +1 -0
  50. package/dist/components/entity-combobox.d.ts +49 -0
  51. package/dist/components/entity-combobox.js +115 -0
  52. package/dist/components/entity-combobox.js.map +1 -0
  53. package/dist/components/file-dropzone.d.ts +18 -0
  54. package/dist/components/file-dropzone.js +101 -0
  55. package/dist/components/file-dropzone.js.map +1 -0
  56. package/dist/components/full-bleed-dialog.d.ts +57 -0
  57. package/dist/components/full-bleed-dialog.js +68 -0
  58. package/dist/components/full-bleed-dialog.js.map +1 -0
  59. package/dist/components/grouped-picker.d.ts +37 -0
  60. package/dist/components/grouped-picker.js +87 -0
  61. package/dist/components/grouped-picker.js.map +1 -0
  62. package/dist/components/hover-menu.d.ts +16 -0
  63. package/dist/components/hover-menu.js +113 -0
  64. package/dist/components/hover-menu.js.map +1 -0
  65. package/dist/components/mini-calendar.d.ts +33 -0
  66. package/dist/components/mini-calendar.js +133 -0
  67. package/dist/components/mini-calendar.js.map +1 -0
  68. package/dist/components/modal.d.ts +76 -0
  69. package/dist/components/modal.js +156 -0
  70. package/dist/components/modal.js.map +1 -0
  71. package/dist/components/multi-entity-combobox.d.ts +47 -0
  72. package/dist/components/multi-entity-combobox.js +116 -0
  73. package/dist/components/multi-entity-combobox.js.map +1 -0
  74. package/dist/components/multi-select.d.ts +39 -0
  75. package/dist/components/multi-select.js +132 -0
  76. package/dist/components/multi-select.js.map +1 -0
  77. package/dist/components/number-input.d.ts +74 -0
  78. package/dist/components/number-input.js +114 -0
  79. package/dist/components/number-input.js.map +1 -0
  80. package/dist/components/numpad-sheet.d.ts +45 -0
  81. package/dist/components/numpad-sheet.js +102 -0
  82. package/dist/components/numpad-sheet.js.map +1 -0
  83. package/dist/components/picker-sheet.d.ts +44 -0
  84. package/dist/components/picker-sheet.js +78 -0
  85. package/dist/components/picker-sheet.js.map +1 -0
  86. package/dist/components/popover.d.ts +20 -0
  87. package/dist/components/popover.js +46 -0
  88. package/dist/components/popover.js.map +1 -0
  89. package/dist/components/search-field.d.ts +52 -0
  90. package/dist/components/search-field.js +64 -0
  91. package/dist/components/search-field.js.map +1 -0
  92. package/dist/components/settings-fields.d.ts +39 -0
  93. package/dist/components/settings-fields.js +17 -0
  94. package/dist/components/settings-fields.js.map +1 -0
  95. package/dist/components/swipeable-row.d.ts +60 -0
  96. package/dist/components/swipeable-row.js +115 -0
  97. package/dist/components/swipeable-row.js.map +1 -0
  98. package/dist/components/toggle-group.d.ts +34 -0
  99. package/dist/components/toggle-group.js +90 -0
  100. package/dist/components/toggle-group.js.map +1 -0
  101. package/dist/components/tooltip.d.ts +86 -0
  102. package/dist/components/tooltip.js +171 -0
  103. package/dist/components/tooltip.js.map +1 -0
  104. package/dist/components/ui.d.ts +239 -0
  105. package/dist/components/ui.js +512 -0
  106. package/dist/components/ui.js.map +1 -0
  107. package/dist/components/use-mobile-reveal.d.ts +31 -0
  108. package/dist/components/use-mobile-reveal.js +33 -0
  109. package/dist/components/use-mobile-reveal.js.map +1 -0
  110. package/dist/components/use-table-state.d.ts +54 -0
  111. package/dist/components/use-table-state.js +153 -0
  112. package/dist/components/use-table-state.js.map +1 -0
  113. package/dist/components/user-avatar.d.ts +23 -0
  114. package/dist/components/user-avatar.js +33 -0
  115. package/dist/components/user-avatar.js.map +1 -0
  116. package/dist/components/wizard-stepper.d.ts +14 -0
  117. package/dist/components/wizard-stepper.js +27 -0
  118. package/dist/components/wizard-stepper.js.map +1 -0
  119. package/dist/data-table-filters-aXyf0Xub.d.ts +261 -0
  120. package/dist/feedback/feedback-attachment.d.ts +63 -0
  121. package/dist/feedback/feedback-attachment.js +160 -0
  122. package/dist/feedback/feedback-attachment.js.map +1 -0
  123. package/dist/feedback/feedback-dialog.d.ts +72 -0
  124. package/dist/feedback/feedback-dialog.js +109 -0
  125. package/dist/feedback/feedback-dialog.js.map +1 -0
  126. package/dist/feedback/feedback-inbox.d.ts +226 -0
  127. package/dist/feedback/feedback-inbox.js +315 -0
  128. package/dist/feedback/feedback-inbox.js.map +1 -0
  129. package/dist/hooks/use-anchored-panel.d.ts +80 -0
  130. package/dist/hooks/use-anchored-panel.js +71 -0
  131. package/dist/hooks/use-anchored-panel.js.map +1 -0
  132. package/dist/hooks/use-anchored-rect.d.ts +24 -0
  133. package/dist/hooks/use-anchored-rect.js +40 -0
  134. package/dist/hooks/use-anchored-rect.js.map +1 -0
  135. package/dist/hooks/use-body-scroll-lock.d.ts +9 -0
  136. package/dist/hooks/use-body-scroll-lock.js +41 -0
  137. package/dist/hooks/use-body-scroll-lock.js.map +1 -0
  138. package/dist/hooks/use-close-transition.d.ts +41 -0
  139. package/dist/hooks/use-close-transition.js +36 -0
  140. package/dist/hooks/use-close-transition.js.map +1 -0
  141. package/dist/hooks/use-dismiss.d.ts +34 -0
  142. package/dist/hooks/use-dismiss.js +41 -0
  143. package/dist/hooks/use-dismiss.js.map +1 -0
  144. package/dist/hooks/use-media-query.d.ts +8 -0
  145. package/dist/hooks/use-media-query.js +18 -0
  146. package/dist/hooks/use-media-query.js.map +1 -0
  147. package/dist/hooks/use-overlay-history.d.ts +10 -0
  148. package/dist/hooks/use-overlay-history.js +99 -0
  149. package/dist/hooks/use-overlay-history.js.map +1 -0
  150. package/dist/hooks/use-row-swipe.d.ts +30 -0
  151. package/dist/hooks/use-row-swipe.js +128 -0
  152. package/dist/hooks/use-row-swipe.js.map +1 -0
  153. package/dist/index.d.ts +75 -0
  154. package/dist/index.js +90 -0
  155. package/dist/index.js.map +1 -0
  156. package/dist/lib/calc.d.ts +89 -0
  157. package/dist/lib/calc.js +180 -0
  158. package/dist/lib/calc.js.map +1 -0
  159. package/dist/lib/cn.d.ts +6 -0
  160. package/dist/lib/cn.js +7 -0
  161. package/dist/lib/cn.js.map +1 -0
  162. package/dist/lib/dates.d.ts +84 -0
  163. package/dist/lib/dates.js +108 -0
  164. package/dist/lib/dates.js.map +1 -0
  165. package/dist/lib/logger.d.ts +17 -0
  166. package/dist/lib/logger.js +30 -0
  167. package/dist/lib/logger.js.map +1 -0
  168. package/dist/lib/safe-storage.d.ts +23 -0
  169. package/dist/lib/safe-storage.js +20 -0
  170. package/dist/lib/safe-storage.js.map +1 -0
  171. package/dist/search/command-palette.d.ts +71 -0
  172. package/dist/search/command-palette.js +198 -0
  173. package/dist/search/command-palette.js.map +1 -0
  174. package/dist/shell/app-shell.d.ts +64 -0
  175. package/dist/shell/app-shell.js +237 -0
  176. package/dist/shell/app-shell.js.map +1 -0
  177. package/dist/shell/option-switcher-menu.d.ts +24 -0
  178. package/dist/shell/option-switcher-menu.js +44 -0
  179. package/dist/shell/option-switcher-menu.js.map +1 -0
  180. package/dist/shell/role-switcher.d.ts +19 -0
  181. package/dist/shell/role-switcher.js +28 -0
  182. package/dist/shell/role-switcher.js.map +1 -0
  183. package/dist/shell/top-bar.d.ts +15 -0
  184. package/dist/shell/top-bar.js +29 -0
  185. package/dist/shell/top-bar.js.map +1 -0
  186. package/dist/shell/topbar-action-menu.d.ts +44 -0
  187. package/dist/shell/topbar-action-menu.js +49 -0
  188. package/dist/shell/topbar-action-menu.js.map +1 -0
  189. package/dist/shell/topbar-controls.d.ts +54 -0
  190. package/dist/shell/topbar-controls.js +152 -0
  191. package/dist/shell/topbar-controls.js.map +1 -0
  192. package/dist/theme/chart-palette.d.ts +60 -0
  193. package/dist/theme/chart-palette.js +79 -0
  194. package/dist/theme/chart-palette.js.map +1 -0
  195. package/dist/theme/palette-presets.d.ts +39 -0
  196. package/dist/theme/palette-presets.js +440 -0
  197. package/dist/theme/palette-presets.js.map +1 -0
  198. package/dist/theme/palette-store.d.ts +45 -0
  199. package/dist/theme/palette-store.js +52 -0
  200. package/dist/theme/palette-store.js.map +1 -0
  201. package/dist/theme/theme-store.d.ts +57 -0
  202. package/dist/theme/theme-store.js +96 -0
  203. package/dist/theme/theme-store.js.map +1 -0
  204. package/dist/tour/tour.d.ts +104 -0
  205. package/dist/tour/tour.js +351 -0
  206. package/dist/tour/tour.js.map +1 -0
  207. package/dist/wizard/stepper-nav.d.ts +30 -0
  208. package/dist/wizard/stepper-nav.js +146 -0
  209. package/dist/wizard/stepper-nav.js.map +1 -0
  210. package/dist/wizard/types.d.ts +124 -0
  211. package/dist/wizard/types.js +24 -0
  212. package/dist/wizard/types.js.map +1 -0
  213. package/dist/wizard/use-wizard.d.ts +6 -0
  214. package/dist/wizard/use-wizard.js +222 -0
  215. package/dist/wizard/use-wizard.js.map +1 -0
  216. package/dist/wizard/validation.d.ts +23 -0
  217. package/dist/wizard/validation.js +13 -0
  218. package/dist/wizard/validation.js.map +1 -0
  219. package/dist/wizard/wizard-context.d.ts +19 -0
  220. package/dist/wizard/wizard-context.js +15 -0
  221. package/dist/wizard/wizard-context.js.map +1 -0
  222. package/dist/wizard/wizard-step.d.ts +11 -0
  223. package/dist/wizard/wizard-step.js +12 -0
  224. package/dist/wizard/wizard-step.js.map +1 -0
  225. package/dist/wizard/wizard-summary.d.ts +18 -0
  226. package/dist/wizard/wizard-summary.js +41 -0
  227. package/dist/wizard/wizard-summary.js.map +1 -0
  228. package/package.json +87 -0
  229. package/tokens.css +254 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/use-overlay-history.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\n/**\n * Back closes the overlay, not the page (Keksdose feedback #172).\n *\n * On a phone the Back gesture is the universal \"dismiss this\" — so a dialog or a\n * full-screen search surface that ignores it doesn't merely fail to close: the\n * navigation lands somewhere else entirely, and on a route with nothing beneath it\n * that means leaving the app with the overlay still notionally open.\n *\n * The mechanism is one throwaway, SAME-URL history entry pushed while the overlay is\n * up. Back then pops a same-document entry — no route change, no re-render of the\n * page under it — and this hook turns that pop into `onClose()`. Same trick the\n * transactions page already uses for its unsaved-edit trap\n * (`use-soft-leave-guard.ts`); the two coexist because they use different marker\n * keys AND because `handlePop` only claims a pop that actually consumed OUR entry\n * — a foreign sentinel stacked on top of ours pops back ONTO us, and an overlay\n * that closed on that would vanish mid-edit (feedback #424/#426).\n *\n * Not a URL parameter, deliberately. Encoding \"a dialog is open\" in the address bar\n * makes it shareable and restorable, which is wrong for the overlays this covers —\n * nobody wants to send someone a link that opens a confirm dialog — and it would\n * fight the page-owned `?row=` / `f.*` params that ARE meaningful state.\n *\n * ## Ordering\n *\n * The stack is module-level because correctness here is about order ACROSS\n * instances: every mounted overlay hears the same `popstate`, but only the one whose\n * entry was actually popped may close. Overlays nest and unwind last-in-first-out —\n * a dialog opened from a search surface closes before the surface — so the top of\n * this stack is by construction the entry the browser just popped.\n *\n * ## Not undoing a real navigation\n *\n * The cleanup pops our entry when the overlay closes some other way (a button,\n * Escape, a backdrop click) so history doesn't silently accumulate dead entries. It\n * first checks that our marker is still the CURRENT entry: if the user followed a\n * link while the overlay was open, the router has pushed on top of us, and a blind\n * `history.back()` there would undo their navigation.\n *\n * ## The traversal is deferred\n *\n * And it is *scheduled* rather than issued, because `history.go()` lands a task\n * later while everything this module knows is written synchronously. See\n * {@link Unwind}: an overlay opening inside that window adopts the entry instead\n * of stacking a second one on it, which is what makes the hook survive React\n * StrictMode's mount → clean up → mount (steering-design feedback #49).\n */\nconst SENTINEL_KEY = \"__hbUiOverlayHistory\";\n\ninterface OverlayEntry {\n id: string;\n close: () => void;\n}\n\nconst stack: OverlayEntry[] = [];\n\n/**\n * The sentinels we have PUSHED and not yet unwound, oldest first.\n *\n * The stack above tracks live overlays; this tracks live history entries, and the\n * two come apart in exactly one case — which is the bug this list exists for.\n * Nested overlays that close in the same commit unwind parent-first, so the dialog's\n * cleanup runs while the sheet's entry is still on top of its own. It cannot call\n * `history.back()` there (that would consume the SHEET's entry, and the sheet is\n * about to consume it itself), so its entry was simply abandoned: a husk with no\n * live owner sitting between the user and the page.\n *\n * The user paid for it on the next Back press. It popped the husk, `handlePop` found\n * an empty stack and returned, and nothing visible happened — Back had to be pressed\n * twice to leave the page.\n *\n * So an abandoned entry is recorded as `dead` instead, and whichever overlay DOES\n * get to unwind pays the debt with a single `history.go(-n)`. `href` is what keeps\n * that safe: only dead entries pushed at the address we are still sitting at are\n * unwound, so a router navigation that landed between two sentinels is never\n * reversed on the user's behalf.\n */\ninterface PushedEntry {\n id: string;\n href: string;\n /** Its overlay is gone but the entry is not — someone above owes this pop. */\n dead: boolean;\n}\nconst pushed: PushedEntry[] = [];\n\n/** Entries a real Back press already consumed. Their overlay's cleanup still has to\n * run, and it must not mistake \"my entry is not current\" for \"my entry is buried\"\n * — it has no entry left at all. */\nconst consumed = new Set<string>();\n\n/** Pops WE caused (a cleanup unwinding its own entries) — their `popstate` is\n * bookkeeping, not a user gesture, and must not close a second overlay. A\n * `history.go(-n)` traversal fires exactly ONE popstate however far it travels. */\nlet pendingProgrammatic = 0;\nlet listening = false;\nlet nextId = 0;\n\n/**\n * The unwind a cleanup has asked for and not yet issued.\n *\n * **`history.go()` is asynchronous and everything above is written\n * synchronously**, which is the seam the whole of this block exists to close.\n * The traversal lands a task later, so an overlay that opens in the *same* tick\n * as one that closed used to push its sentinel on top of the entry that was\n * about to be popped — and the pop then took the NEW entry. The overlay left on\n * screen had no entry of its own (so Back no longer closed it) and the old one\n * stayed behind as a husk between the user and their page.\n *\n * That is not an exotic race. **React StrictMode remounts every effect** — mount,\n * clean up, mount again, all in one commit — so in development *every* dialog\n * anyone opened left a husk behind, the position drifted one entry further from\n * the page on each open, and the Back press that was meant to leave the page was\n * spent on something invisible instead (steering-design feedback #49).\n *\n * So the traversal is deferred by a task, and an overlay mounting inside that\n * window **adopts** the entry instead of pushing a second one (see the effect).\n * Adoption is `replaceState` on an entry that is already there: no traversal, no\n * window in which the count can be wrong, and the StrictMode remount costs\n * exactly nothing.\n */\ninterface Unwind {\n timer: ReturnType<typeof setTimeout>;\n /** The sentinel on the entry to leave — the top of `entries`. */\n id: string;\n href: string;\n /** That entry plus the abandoned ones directly beneath it, oldest first. */\n entries: PushedEntry[];\n}\nlet unwind: Unwind | null = null;\n\n/** Issue the deferred traversal, if the ground has not moved under it.\n *\n * A real Back press or a router navigation during the deferred task leaves us\n * somewhere that is not the entry we meant to leave, and going back from there\n * would take something that is not ours. The entries are then simply forgotten\n * rather than handed back as `dead`: a forgotten entry costs one dead Back\n * press, while an over-counted one costs the user a navigation they did make. */\nfunction runUnwind(): void {\n const job = unwind;\n unwind = null;\n if (!job) return;\n if (currentSentinel() !== job.id || currentHref() !== job.href) return;\n pendingProgrammatic += 1;\n window.history.go(-job.entries.length);\n}\n\nfunction currentHref(): string {\n return typeof window === \"undefined\" ? \"\" : window.location.href;\n}\n\nfunction forgetPushed(id: string): void {\n const at = pushed.findIndex((p) => p.id === id);\n if (at >= 0) pushed.splice(at, 1);\n}\n\nfunction currentSentinel(): string | null {\n const state = typeof window !== \"undefined\" ? window.history.state : null;\n if (!state || typeof state !== \"object\") return null;\n const id = (state as Record<string, unknown>)[SENTINEL_KEY];\n return typeof id === \"string\" ? id : null;\n}\n\nfunction handlePop() {\n if (pendingProgrammatic > 0) {\n pendingProgrammatic -= 1;\n return;\n }\n // Nothing open → an ordinary navigation, which is none of our business.\n const top = stack[stack.length - 1];\n if (!top) return;\n // We landed back ON the top overlay's own entry, so that entry was NOT what the\n // pop consumed — something stacked ABOVE it was (Keksdose feedback #424/#426).\n // The app layers a second same-URL sentinel of its own on top of ours for the\n // unsaved-edit trap (`use-soft-leave-guard.ts`), and it pops that sentinel every\n // time the form goes back to clean — twice-toggling one field was enough to\n // close the whole dialog. A pop is only ours when the entry we came to rest on\n // is no longer ours: either the layer below (a different overlay's marker) or\n // the plain page entry (none at all).\n if (currentSentinel() === top.id) return;\n stack.pop();\n // The browser consumed this entry, so the overlay's own cleanup has nothing left\n // to unwind and nothing to record as owed.\n consumed.add(top.id);\n forgetPushed(top.id);\n top.close();\n}\n\nfunction ensureListening() {\n if (listening || typeof window === \"undefined\") return;\n window.addEventListener(\"popstate\", handlePop);\n listening = true;\n}\n\n/**\n * Make the platform Back gesture dismiss an overlay.\n *\n * @param open whether the overlay is currently showing. Components that mount\n * only while open (the common case) pass `true`.\n * @param onClose the overlay's own close handler — the same one Escape calls.\n */\nexport function useOverlayHistory(open: boolean, onClose: () => void): void {\n // The listener fires outside React's render, so it reads the latest handler\n // through a ref rather than through the closure the effect captured.\n const closeRef = useRef(onClose);\n useEffect(() => {\n closeRef.current = onClose;\n });\n\n useEffect(() => {\n if (!open || typeof window === \"undefined\") return;\n ensureListening();\n const id = `overlay-${(nextId += 1)}`;\n const entry: OverlayEntry = { id, close: () => closeRef.current() };\n stack.push(entry);\n const prev = window.history.state;\n const marked = { ...(prev && typeof prev === \"object\" ? prev : {}), [SENTINEL_KEY]: id };\n // An entry that is on its way out and that we are still standing on is ours\n // to take over: same URL, same slot, only the marker changes. Pushing a\n // second one on top of it is what used to leave a husk on every StrictMode\n // remount — see {@link Unwind}.\n const adopting = unwind && unwind.id === currentSentinel() && unwind.href === currentHref();\n if (adopting && unwind) {\n clearTimeout(unwind.timer);\n const inherited = unwind.entries;\n unwind = null;\n // Preserve react-router's own `{usr,key,idx}` — we only swap the marker,\n // and the entry we are swapping it on is the one the router is already on.\n window.history.replaceState(marked, \"\");\n // The abandoned ones beneath come back owed; ours takes the top slot.\n for (const owed of inherited.slice(0, -1)) pushed.push({ ...owed, dead: true });\n pushed.push({ id, href: currentHref(), dead: false });\n } else {\n // A push DESTROYS every entry ahead of the one we are on, so anything we\n // still had recorded above our own position is gone. Kept, those would be\n // counted into a later `history.go(-n)` and the overshoot would come out\n // of a navigation the user actually made. Where we cannot tell which of\n // ours are beneath us — we are standing on a plain page entry, or on a\n // husk nobody tracks — none of them can be proven to be, so none survive.\n const under = pushed.findIndex((p) => p.id === currentSentinel());\n pushed.splice(under + 1);\n // Preserve react-router's own `{usr,key,idx}` — we only tack a marker on, and\n // the URL is unchanged, so the router treats popping this as a no-op re-render.\n window.history.pushState(marked, \"\");\n pushed.push({ id, href: currentHref(), dead: false });\n }\n return () => {\n const at = stack.indexOf(entry);\n if (at >= 0) stack.splice(at, 1);\n\n // A Back press already took this entry; there is nothing to unwind or to owe.\n if (consumed.delete(id)) return;\n\n const mine = pushed.findIndex((p) => p.id === id);\n // Only unwind an entry that is still THE current one. If a router push landed\n // on top of it, going back would undo the user's navigation instead — and if\n // a SIBLING overlay's sentinel landed on top, that overlay is about to unwind\n // and is the one that can take ours with it.\n if (currentSentinel() !== id) {\n if (mine >= 0) pushed[mine].dead = true;\n return;\n }\n\n // Ours plus every abandoned entry lying directly beneath it at this address.\n // One traversal, so one popstate, so one programmatic pop to absorb.\n const here = currentHref();\n let steps = 1;\n while (steps <= mine && pushed[mine - steps].dead && pushed[mine - steps].href === here) {\n steps += 1;\n }\n const leaving = pushed.splice(mine - (steps - 1), steps);\n // Scheduled, not issued: an overlay opening in this same tick takes the\n // entry over instead, which is what makes the hook survive a StrictMode\n // remount. An unwind already waiting is dropped rather than merged — it\n // was the entry ABOVE ours and something has since moved off it, so its\n // count can no longer be proven, and an over-counted traversal costs a\n // navigation the user made while an under-counted one costs one dead Back.\n if (unwind) clearTimeout(unwind.timer);\n unwind = { timer: setTimeout(runUnwind, 0), id, href: here, entries: leaving };\n };\n }, [open]);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,30 @@
1
+ import { PointerEvent } from 'react';
2
+
3
+ interface SwipeStage {
4
+ /** Drag magnitude (px) at which this stage arms. Stages must be sorted ascending. */
5
+ threshold: number;
6
+ onCommit: () => void;
7
+ }
8
+ interface RowSwipeOptions {
9
+ enabled: boolean;
10
+ leftStages?: SwipeStage[];
11
+ rightStages?: SwipeStage[];
12
+ maxDrag?: number;
13
+ }
14
+ interface RowSwipeReturn {
15
+ dx: number;
16
+ dragging: boolean;
17
+ /** Highest armed stage index for the current drag (-1 when none armed). */
18
+ armedRightIndex: number;
19
+ armedLeftIndex: number;
20
+ handlers: {
21
+ onPointerDown: (e: PointerEvent) => void;
22
+ onPointerMove: (e: PointerEvent) => void;
23
+ onPointerUp: (e: PointerEvent) => void;
24
+ onPointerCancel: (e: PointerEvent) => void;
25
+ };
26
+ consumeClick: () => boolean;
27
+ }
28
+ declare function useRowSwipe({ enabled, leftStages, rightStages, maxDrag, }: RowSwipeOptions): RowSwipeReturn;
29
+
30
+ export { type RowSwipeOptions, type RowSwipeReturn, type SwipeStage, useRowSwipe };
@@ -0,0 +1,128 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ const CLICK_SWALLOW_PX = 24;
3
+ const EMPTY_SIDE_RUBBER_PX = 28;
4
+ function rubberBand(overshoot, limit) {
5
+ const eased = limit * (1 - Math.exp(-Math.abs(overshoot) / limit));
6
+ return Math.sign(overshoot) * eased;
7
+ }
8
+ function lastArmed(stages, magnitude) {
9
+ if (!stages) return -1;
10
+ let idx = -1;
11
+ for (let i = 0; i < stages.length; i++) {
12
+ if (magnitude >= stages[i].threshold) idx = i;
13
+ else break;
14
+ }
15
+ return idx;
16
+ }
17
+ function useRowSwipe({
18
+ enabled,
19
+ leftStages,
20
+ rightStages,
21
+ maxDrag = 252
22
+ }) {
23
+ const [dx, setDx] = useState(0);
24
+ const [dragging, setDragging] = useState(false);
25
+ const startX = useRef(0);
26
+ const startY = useRef(0);
27
+ const axis = useRef("none");
28
+ const pointerId = useRef(null);
29
+ const dxRef = useRef(0);
30
+ const swallowClick = useRef(false);
31
+ const leftRef = useRef(leftStages);
32
+ const rightRef = useRef(rightStages);
33
+ useEffect(() => {
34
+ leftRef.current = leftStages;
35
+ rightRef.current = rightStages;
36
+ });
37
+ const reset = useCallback(() => {
38
+ setDx(0);
39
+ dxRef.current = 0;
40
+ setDragging(false);
41
+ startX.current = 0;
42
+ startY.current = 0;
43
+ axis.current = "none";
44
+ pointerId.current = null;
45
+ }, []);
46
+ const onPointerDown = useCallback(
47
+ (e) => {
48
+ if (!enabled) return;
49
+ if (e.pointerType === "mouse" && e.button !== 0) return;
50
+ pointerId.current = e.pointerId;
51
+ startX.current = e.clientX;
52
+ startY.current = e.clientY;
53
+ axis.current = "none";
54
+ swallowClick.current = false;
55
+ setDragging(true);
56
+ },
57
+ [enabled]
58
+ );
59
+ const onPointerMove = useCallback(
60
+ (e) => {
61
+ if (pointerId.current !== e.pointerId) return;
62
+ const rawDx = e.clientX - startX.current;
63
+ const rawDy = e.clientY - startY.current;
64
+ if (axis.current === "none") {
65
+ if (Math.abs(rawDx) < 8 && Math.abs(rawDy) < 8) return;
66
+ axis.current = Math.abs(rawDx) > Math.abs(rawDy) ? "x" : "y";
67
+ if (axis.current === "x") {
68
+ e.currentTarget.setPointerCapture?.(e.pointerId);
69
+ } else {
70
+ reset();
71
+ return;
72
+ }
73
+ }
74
+ if (axis.current !== "x") return;
75
+ if (Math.abs(rawDx) >= CLICK_SWALLOW_PX) swallowClick.current = true;
76
+ const hasLeft = (leftRef.current?.length ?? 0) > 0;
77
+ const hasRight = (rightRef.current?.length ?? 0) > 0;
78
+ let next;
79
+ if (rawDx > 0) {
80
+ next = hasRight ? Math.min(maxDrag, rawDx) : rubberBand(rawDx, EMPTY_SIDE_RUBBER_PX);
81
+ } else if (rawDx < 0) {
82
+ next = hasLeft ? Math.max(-maxDrag, rawDx) : rubberBand(rawDx, EMPTY_SIDE_RUBBER_PX);
83
+ } else {
84
+ next = 0;
85
+ }
86
+ dxRef.current = next;
87
+ setDx(next);
88
+ },
89
+ [maxDrag, reset]
90
+ );
91
+ const onPointerUp = useCallback(
92
+ (e) => {
93
+ if (pointerId.current !== e.pointerId) return;
94
+ const finalDx = dxRef.current;
95
+ const wasDragX = axis.current === "x";
96
+ reset();
97
+ if (!wasDragX) return;
98
+ if (finalDx > 0) {
99
+ const i = lastArmed(rightRef.current, finalDx);
100
+ if (i >= 0) rightRef.current[i].onCommit();
101
+ } else if (finalDx < 0) {
102
+ const i = lastArmed(leftRef.current, -finalDx);
103
+ if (i >= 0) leftRef.current[i].onCommit();
104
+ }
105
+ },
106
+ [reset]
107
+ );
108
+ const onPointerCancel = useCallback(() => reset(), [reset]);
109
+ const consumeClick = useCallback(() => {
110
+ if (swallowClick.current) {
111
+ swallowClick.current = false;
112
+ return true;
113
+ }
114
+ return false;
115
+ }, []);
116
+ return {
117
+ dx,
118
+ dragging,
119
+ armedRightIndex: lastArmed(rightStages, dx > 0 ? dx : 0),
120
+ armedLeftIndex: lastArmed(leftStages, dx < 0 ? -dx : 0),
121
+ handlers: { onPointerDown, onPointerMove, onPointerUp, onPointerCancel },
122
+ consumeClick
123
+ };
124
+ }
125
+ export {
126
+ useRowSwipe
127
+ };
128
+ //# sourceMappingURL=use-row-swipe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/use-row-swipe.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { PointerEvent } from \"react\";\n\n// ---------- Mobile swipe gesture ----------\n//\n// Multi-stage half-swipe affordance (feedback #103, extended #9): drag a row\n// horizontally to commit an action; how FAR you drag picks which action, so a\n// single gesture can walk a chain of increasingly involved outcomes (e.g. on a\n// cleared row, a short left-swipe un-clears it, further un-accepts it, further\n// still deletes it). We track via Pointer Events, lock the gesture to the X axis\n// only after the user has moved more horizontally than vertically, and clamp the\n// drag to `maxDrag` so the row can't be pulled across the whole viewport.\n//\n// Each side is an ordered list of stages sorted by ascending `threshold` (px of\n// drag magnitude). On release the LAST stage whose threshold the drag passed is\n// committed; while dragging, `armedRightIndex`/`armedLeftIndex` report which\n// stage is currently armed so the reveal panel can preview its label/colour.\n\nexport interface SwipeStage {\n /** Drag magnitude (px) at which this stage arms. Stages must be sorted ascending. */\n threshold: number;\n onCommit: () => void;\n}\n\n/** Below this much horizontal travel a gesture is treated as a clumsy click, not\n * a swipe: the row still follows the pointer, but the click that follows is NOT\n * swallowed. Without it, `swallowClick` was set the instant the axis lock\n * resolved to \"x\" — 8px — so a slightly-draggy click on a control INSIDE a\n * swipeable row silently did nothing, which is a poor trade on rows whose\n * content is itself interactive (Keksdose feedback #144: budget rows edit their\n * assigned amount and their name by clicking them). */\nconst CLICK_SWALLOW_PX = 24;\n\n/** How far a drag may travel toward a side that has no actions. Zero — the old\n * behaviour — makes the row completely inert in that direction, which reads as\n * \"this row is not swipeable at all\" rather than \"there is nothing on this\n * side\"; that is exactly the report behind Keksdose feedback #144 (\"tried, but\n * there is nothing swipeable\"). A short, resisted travel that snaps back is the\n * conventional answer and the one that answers the question the drag asked. */\nconst EMPTY_SIDE_RUBBER_PX = 28;\n\n/** Resistance applied past a bound: the row keeps moving, at a fraction of the\n * pointer, so the gesture feels answered without ever looking committed. */\nfunction rubberBand(overshoot: number, limit: number): number {\n const eased = limit * (1 - Math.exp(-Math.abs(overshoot) / limit));\n return Math.sign(overshoot) * eased;\n}\n\nexport interface RowSwipeOptions {\n enabled: boolean;\n // Ordered stages per direction (ascending threshold). A longer drag commits a\n // later — usually more involved — stage. An empty/undefined side means that\n // direction rubber-bands a little and snaps back, committing nothing.\n leftStages?: SwipeStage[];\n rightStages?: SwipeStage[];\n maxDrag?: number;\n}\n\nexport interface RowSwipeReturn {\n dx: number;\n dragging: boolean;\n /** Highest armed stage index for the current drag (-1 when none armed). */\n armedRightIndex: number;\n armedLeftIndex: number;\n handlers: {\n onPointerDown: (e: PointerEvent) => void;\n onPointerMove: (e: PointerEvent) => void;\n onPointerUp: (e: PointerEvent) => void;\n onPointerCancel: (e: PointerEvent) => void;\n };\n consumeClick: () => boolean;\n}\n\n/** The highest index whose threshold the magnitude has reached (-1 if none).\n * Stages are ascending, so we can stop at the first one not yet reached. */\nfunction lastArmed(stages: SwipeStage[] | undefined, magnitude: number): number {\n if (!stages) return -1;\n let idx = -1;\n for (let i = 0; i < stages.length; i++) {\n if (magnitude >= stages[i].threshold) idx = i;\n else break;\n }\n return idx;\n}\n\nexport function useRowSwipe({\n enabled,\n leftStages,\n rightStages,\n maxDrag = 252,\n}: RowSwipeOptions): RowSwipeReturn {\n const [dx, setDx] = useState(0);\n const [dragging, setDragging] = useState(false);\n const startX = useRef(0);\n const startY = useRef(0);\n const axis = useRef<\"none\" | \"x\" | \"y\">(\"none\");\n const pointerId = useRef<number | null>(null);\n const dxRef = useRef(0);\n // True after the user has dragged horizontally far enough that the following\n // synthetic click should be swallowed (so a swipe doesn't also expand the row).\n const swallowClick = useRef(false);\n\n // Keep the latest stage arrays in refs so the memoised pointer handlers always\n // commit against the current plan rather than a stale closure — the arrays and\n // their onCommit closures are rebuilt on every render. Written in an effect (not\n // during render) so it satisfies react-hooks/refs; pointer events fire after the\n // commit, so the refs are always current by the time a gesture reads them.\n const leftRef = useRef(leftStages);\n const rightRef = useRef(rightStages);\n useEffect(() => {\n leftRef.current = leftStages;\n rightRef.current = rightStages;\n });\n\n const reset = useCallback(() => {\n setDx(0);\n dxRef.current = 0;\n setDragging(false);\n startX.current = 0;\n startY.current = 0;\n axis.current = \"none\";\n pointerId.current = null;\n }, []);\n\n const onPointerDown = useCallback(\n (e: PointerEvent) => {\n if (!enabled) return;\n if (e.pointerType === \"mouse\" && e.button !== 0) return;\n pointerId.current = e.pointerId;\n startX.current = e.clientX;\n startY.current = e.clientY;\n axis.current = \"none\";\n swallowClick.current = false;\n setDragging(true);\n },\n [enabled],\n );\n\n const onPointerMove = useCallback(\n (e: PointerEvent) => {\n if (pointerId.current !== e.pointerId) return;\n const rawDx = e.clientX - startX.current;\n const rawDy = e.clientY - startY.current;\n if (axis.current === \"none\") {\n if (Math.abs(rawDx) < 8 && Math.abs(rawDy) < 8) return;\n axis.current = Math.abs(rawDx) > Math.abs(rawDy) ? \"x\" : \"y\";\n if (axis.current === \"x\") {\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n } else {\n // Vertical scroll wins — bail out of the gesture entirely.\n reset();\n return;\n }\n }\n if (axis.current !== \"x\") return;\n if (Math.abs(rawDx) >= CLICK_SWALLOW_PX) swallowClick.current = true;\n // Toward a side that HAS actions the row tracks the pointer up to maxDrag.\n // Toward an empty one it rubber-bands a little and snaps back on release —\n // inert would be indistinguishable from \"not swipeable\".\n const hasLeft = (leftRef.current?.length ?? 0) > 0;\n const hasRight = (rightRef.current?.length ?? 0) > 0;\n let next: number;\n if (rawDx > 0) {\n next = hasRight ? Math.min(maxDrag, rawDx) : rubberBand(rawDx, EMPTY_SIDE_RUBBER_PX);\n } else if (rawDx < 0) {\n next = hasLeft ? Math.max(-maxDrag, rawDx) : rubberBand(rawDx, EMPTY_SIDE_RUBBER_PX);\n } else {\n next = 0;\n }\n dxRef.current = next;\n setDx(next);\n },\n [maxDrag, reset],\n );\n\n const onPointerUp = useCallback(\n (e: PointerEvent) => {\n if (pointerId.current !== e.pointerId) return;\n const finalDx = dxRef.current;\n const wasDragX = axis.current === \"x\";\n reset();\n if (!wasDragX) return;\n if (finalDx > 0) {\n const i = lastArmed(rightRef.current, finalDx);\n if (i >= 0) rightRef.current![i].onCommit();\n } else if (finalDx < 0) {\n const i = lastArmed(leftRef.current, -finalDx);\n if (i >= 0) leftRef.current![i].onCommit();\n }\n },\n [reset],\n );\n\n const onPointerCancel = useCallback(() => reset(), [reset]);\n\n const consumeClick = useCallback(() => {\n if (swallowClick.current) {\n swallowClick.current = false;\n return true;\n }\n return false;\n }, []);\n\n return {\n dx,\n dragging,\n armedRightIndex: lastArmed(rightStages, dx > 0 ? dx : 0),\n armedLeftIndex: lastArmed(leftStages, dx < 0 ? -dx : 0),\n handlers: { onPointerDown, onPointerMove, onPointerUp, onPointerCancel },\n consumeClick,\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,75 @@
1
+ export { commitExpression, evaluateExpression, formatResult, isBareAmount, looksLikeExpression, sanitizeLive, splitLeadingSign } from './lib/calc.js';
2
+ export { cn } from './lib/cn.js';
3
+ export { logger, setStoreLog } from './lib/logger.js';
4
+ export { useMediaQuery } from './hooks/use-media-query.js';
5
+ export { useBodyScrollLock } from './hooks/use-body-scroll-lock.js';
6
+ export { AnchorRect, useAnchoredRect } from './hooks/use-anchored-rect.js';
7
+ export { AnchoredPanel, AnchoredPanelOptions, ViewportBox, anchoredPanelPlacement, useAnchoredPanel, useVisualViewport } from './hooks/use-anchored-panel.js';
8
+ export { useEscapeKey, useOutsideClick } from './hooks/use-dismiss.js';
9
+ export { useOverlayHistory } from './hooks/use-overlay-history.js';
10
+ export { OVERLAY_EXIT_MS, useCloseTransition } from './hooks/use-close-transition.js';
11
+ export { RowSwipeOptions, RowSwipeReturn, SwipeStage, useRowSwipe } from './hooks/use-row-swipe.js';
12
+ export { CHART_COLORS, HEATMAP_HEX, HeatStops, PALETTE_HEX, lerpHex, paletteFor, textOn } from './theme/chart-palette.js';
13
+ export { ALTERNATIVE_PRESETS, DEFAULT_PRESET, IMPRINT_PRESET, PALETTES, PalettePreset, TOKEN_VARS, TokenSet, applyTokenSet, clearTokenSet, presetById } from './theme/palette-presets.js';
14
+ export { ThemeMode, ThemePreference, ThemeState, ThemeStore, applyPersistedTheme, createThemeStore } from './theme/theme-store.js';
15
+ export { PaletteStore, applyPersistedPalette, createPaletteStore } from './theme/palette-store.js';
16
+ export { Button, ButtonVariant, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, EmptyState, FIELD_BASE, FIELD_DISPLAY, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, FIELD_WRITABLE_LOOK, FLOATING_INPUT_CLASS, FLOATING_LABEL_CLASS, FLOATING_LABEL_STATIC, FieldChevron, FieldHint, FieldLabel, FloatingField, IconButton, Input, PHONE_QUERY, Select, Spinner, Tabs, TabsProps, Textarea, buttonClasses } from './components/ui.js';
17
+ export { SearchField, SearchFieldProps } from './components/search-field.js';
18
+ export { DropdownPanel, DropdownSearchHeader, useDropdown, useDropdownSearch } from './components/dropdown.js';
19
+ export { Popover } from './components/popover.js';
20
+ export { SwipeAction, SwipeableRow, SwipeableRowProps } from './components/swipeable-row.js';
21
+ export { CalculatorButton, CalculatorButtonLabels } from './components/calculator.js';
22
+ export { NumberPadSheet, NumberPadSheetLabels } from './components/numpad-sheet.js';
23
+ export { NumberInput } from './components/number-input.js';
24
+ export { CURRENCIES, CurrencyFlag, CurrencyOption, CurrencySelect, getCurrency } from './components/currency-select.js';
25
+ export { AmountInput } from './components/amount-input.js';
26
+ export { Combobox, InlineEntityCombobox } from './components/combobox.js';
27
+ export { PickerSheet, SHEET_ROW_CLASS } from './components/picker-sheet.js';
28
+ export { EntityCombobox, EntityComboboxProps } from './components/entity-combobox.js';
29
+ export { MultiEntityCombobox, MultiEntityComboboxProps } from './components/multi-entity-combobox.js';
30
+ export { MultiSelect, MultiSelectOption } from './components/multi-select.js';
31
+ export { Tooltip, TooltipPlacement, TooltipSize, TooltipViewport, placeTooltip } from './components/tooltip.js';
32
+ export { UserAvatar, UserAvatarProps, avatarInitials } from './components/user-avatar.js';
33
+ export { LanguageSetting, LanguageSettingProps, ThemeSetting, ThemeSettingProps } from './components/settings-fields.js';
34
+ export { PasswordSetting, PasswordSettingLabels, ProfileSetting, ProfileSettingLabels, TwoFactorSetting, TwoFactorSettingLabels } from './components/account-settings.js';
35
+ export { AlertBanner, AlertTone, alertFrameClass, toneFrameClass } from './components/alert-banner.js';
36
+ export { ToggleGroup } from './components/toggle-group.js';
37
+ export { WizardStepper } from './components/wizard-stepper.js';
38
+ export { HoverMenu } from './components/hover-menu.js';
39
+ export { Modal, ModalProps, useBackdropClose } from './components/modal.js';
40
+ export { FullBleedDialog } from './components/full-bleed-dialog.js';
41
+ export { GroupedPicker, PickerGroup } from './components/grouped-picker.js';
42
+ export { FileDropzone } from './components/file-dropzone.js';
43
+ export { MiniCalendar, MiniCalendarProps } from './components/mini-calendar.js';
44
+ export { DatePicker, DatePickerProps, DateRangePicker, DateRangePickerPreset, DateRangePickerProps } from './components/date-picker.js';
45
+ export { ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartSeriesConfig, ChartTooltip, ChartTooltipContent, useChart } from './components/chart.js';
46
+ export { DEFAULT_DATA_TABLE_LABELS, DataTableLabels, resolveDataTableLabels } from './components/data-table-labels.js';
47
+ export { SortDir, SortState, decodeSorts, encodeSorts, nextSorts, normalizeSorts } from './components/data-table-sort.js';
48
+ export { C as ColumnFilter, D as DataTable, a as DataTableColumn, b as DataTableProps, F as FilterState, c as FilterValue, S as ServerPagination, d as decodeFilterValue, e as decodeFilterValueOfType, f as defaultFilterState, g as encodeFilterValue, i as isFilterActive, r as resolveFilter, h as rowMatches } from './data-table-filters-aXyf0Xub.js';
49
+ export { PAGE_SIZE_OPTIONS, Pagination } from './components/data-table-pagination.js';
50
+ export { FilterPopover } from './components/data-table-filter-popover.js';
51
+ export { LanguageMenu, LanguageOption, PaletteMenu, TOPBAR_MENU_ITEM_CLASS, TOPBAR_TRIGGER_CLASS, ThemeToggle } from './shell/topbar-controls.js';
52
+ export { TopBar } from './shell/top-bar.js';
53
+ export { AppShell, AppShellNavItem, AppShellSubItem } from './shell/app-shell.js';
54
+ export { OptionSwitcherMenu, OptionSwitcherOption } from './shell/option-switcher-menu.js';
55
+ export { RoleSwitcher, RoleSwitcherProps } from './shell/role-switcher.js';
56
+ export { TopBarActionMenu, TopBarMenuEntry } from './shell/topbar-action-menu.js';
57
+ export { DEFAULT_ATTACHMENT_ACCEPT, DEFAULT_MAX_ATTACHMENT_BYTES, FeedbackAttachmentField, pastedName } from './feedback/feedback-attachment.js';
58
+ export { FeedbackAttachmentLabels, FeedbackCategoryOption, FeedbackDialog, FeedbackDialogLabels, FeedbackSubmission } from './feedback/feedback-dialog.js';
59
+ export { FEEDBACK_CATEGORY_META, FEEDBACK_CATEGORY_ORDER, FEEDBACK_STATUS_META, FEEDBACK_STATUS_ORDER, FeedbackCategory, FeedbackCategoryBadge, FeedbackDetail, FeedbackDetailSection, FeedbackNoteAttachment, FeedbackNoteEditor, FeedbackProse, FeedbackStatus, FeedbackStatusBadge, FeedbackStatusTransitions, feedbackCategoryRank, nextFeedbackStatus, selectableFeedbackStatuses, visibleFeedbackStatuses } from './feedback/feedback-inbox.js';
60
+ export { DEFAULT_WIZARD_LABELS, FieldErrors, StepStatus, SummaryItem, SummarySection, UseWizardOptions, UseWizardReturn, ValidateResult, WizardLabels, WizardStepConfig, resolveWizardLabels } from './wizard/types.js';
61
+ export { useWizard } from './wizard/use-wizard.js';
62
+ export { WizardContextProvider, WizardContextValue, useWizardContext } from './wizard/wizard-context.js';
63
+ export { RequiredFieldSpec, requiredFieldsValidator } from './wizard/validation.js';
64
+ export { WizardStep } from './wizard/wizard-step.js';
65
+ export { WizardSummary } from './wizard/wizard-summary.js';
66
+ export { StepperNav } from './wizard/stepper-nav.js';
67
+ export { TourLabels, TourPlacement, TourProvider, TourStep, useTour, useTourOptional } from './tour/tour.js';
68
+ export { CommandItem, CommandPalette, CommandPaletteLabels, useCommandKey } from './search/command-palette.js';
69
+ export { ComboOption } from './components/combobox-core.js';
70
+ import 'clsx';
71
+ import 'zustand';
72
+ import 'react';
73
+ import 'zustand/middleware';
74
+ import 'recharts';
75
+ import 'lucide-react';
package/dist/index.js ADDED
@@ -0,0 +1,90 @@
1
+ export * from "./lib/calc.js";
2
+ import { cn } from "./lib/cn.js";
3
+ import { logger, setStoreLog } from "./lib/logger.js";
4
+ import { useMediaQuery } from "./hooks/use-media-query.js";
5
+ import { useBodyScrollLock } from "./hooks/use-body-scroll-lock.js";
6
+ import { useAnchoredRect } from "./hooks/use-anchored-rect.js";
7
+ import { useAnchoredPanel, anchoredPanelPlacement, useVisualViewport } from "./hooks/use-anchored-panel.js";
8
+ import { useEscapeKey, useOutsideClick } from "./hooks/use-dismiss.js";
9
+ import { useOverlayHistory } from "./hooks/use-overlay-history.js";
10
+ import { useCloseTransition, OVERLAY_EXIT_MS } from "./hooks/use-close-transition.js";
11
+ import { useRowSwipe } from "./hooks/use-row-swipe.js";
12
+ export * from "./theme/chart-palette.js";
13
+ export * from "./theme/palette-presets.js";
14
+ export * from "./theme/theme-store.js";
15
+ export * from "./theme/palette-store.js";
16
+ export * from "./components/ui.js";
17
+ export * from "./components/search-field.js";
18
+ export * from "./components/dropdown.js";
19
+ export * from "./components/popover.js";
20
+ import { SwipeableRow } from "./components/swipeable-row.js";
21
+ export * from "./components/calculator.js";
22
+ export * from "./components/numpad-sheet.js";
23
+ export * from "./components/number-input.js";
24
+ export * from "./components/currency-select.js";
25
+ export * from "./components/amount-input.js";
26
+ export * from "./components/combobox.js";
27
+ export * from "./components/picker-sheet.js";
28
+ export * from "./components/entity-combobox.js";
29
+ export * from "./components/multi-entity-combobox.js";
30
+ export * from "./components/multi-select.js";
31
+ export * from "./components/tooltip.js";
32
+ export * from "./components/user-avatar.js";
33
+ export * from "./components/settings-fields.js";
34
+ export * from "./components/account-settings.js";
35
+ export * from "./components/alert-banner.js";
36
+ export * from "./components/toggle-group.js";
37
+ export * from "./components/wizard-stepper.js";
38
+ export * from "./components/hover-menu.js";
39
+ export * from "./components/modal.js";
40
+ export * from "./components/full-bleed-dialog.js";
41
+ export * from "./components/grouped-picker.js";
42
+ export * from "./components/file-dropzone.js";
43
+ export * from "./components/mini-calendar.js";
44
+ export * from "./components/date-picker.js";
45
+ export * from "./components/chart.js";
46
+ export * from "./components/data-table-labels.js";
47
+ export * from "./components/data-table-sort.js";
48
+ export * from "./components/data-table-filters.js";
49
+ export * from "./components/data-table-pagination.js";
50
+ import { FilterPopover } from "./components/data-table-filter-popover.js";
51
+ import { DataTable } from "./components/data-table.js";
52
+ export * from "./shell/topbar-controls.js";
53
+ export * from "./shell/top-bar.js";
54
+ export * from "./shell/app-shell.js";
55
+ export * from "./shell/option-switcher-menu.js";
56
+ export * from "./shell/role-switcher.js";
57
+ export * from "./shell/topbar-action-menu.js";
58
+ export * from "./feedback/feedback-attachment.js";
59
+ export * from "./feedback/feedback-dialog.js";
60
+ export * from "./feedback/feedback-inbox.js";
61
+ export * from "./wizard/types.js";
62
+ export * from "./wizard/use-wizard.js";
63
+ export * from "./wizard/wizard-context.js";
64
+ export * from "./wizard/validation.js";
65
+ export * from "./wizard/wizard-step.js";
66
+ export * from "./wizard/wizard-summary.js";
67
+ export * from "./wizard/stepper-nav.js";
68
+ export * from "./tour/tour.js";
69
+ export * from "./search/command-palette.js";
70
+ export {
71
+ DataTable,
72
+ FilterPopover,
73
+ OVERLAY_EXIT_MS,
74
+ SwipeableRow,
75
+ anchoredPanelPlacement,
76
+ cn,
77
+ logger,
78
+ setStoreLog,
79
+ useAnchoredPanel,
80
+ useAnchoredRect,
81
+ useBodyScrollLock,
82
+ useCloseTransition,
83
+ useEscapeKey,
84
+ useMediaQuery,
85
+ useOutsideClick,
86
+ useOverlayHistory,
87
+ useRowSwipe,
88
+ useVisualViewport
89
+ };
90
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// @eifi1/ui-kit — shared design-system barrel.\n//\n// Domain-free UI + theme surface shared across apps (lead app: Keksdose). Consumers import\n// components/theme from \"@eifi1/ui-kit\" and the token stylesheet from \"@eifi1/ui-kit/tokens.css\".\n\n// ── lib ────────────────────────────────────────────────────────────────────\nexport * from \"./lib/calc\";\nexport { cn } from \"./lib/cn\";\nexport { logger, setStoreLog } from \"./lib/logger\";\n// Date helpers are also available via the \"@eifi1/ui-kit/dates\" subpath.\n\n// ── hooks ────────────────────────────────────────────────────────────────────\nexport { useMediaQuery } from \"./hooks/use-media-query\";\nexport { useBodyScrollLock } from \"./hooks/use-body-scroll-lock\";\nexport { useAnchoredRect } from \"./hooks/use-anchored-rect\";\nexport type { AnchorRect } from \"./hooks/use-anchored-rect\";\nexport { useAnchoredPanel, anchoredPanelPlacement, useVisualViewport } from \"./hooks/use-anchored-panel\";\nexport type {\n AnchoredPanel,\n AnchoredPanelOptions,\n ViewportBox,\n} from \"./hooks/use-anchored-panel\";\nexport { useEscapeKey, useOutsideClick } from \"./hooks/use-dismiss\";\nexport { useOverlayHistory } from \"./hooks/use-overlay-history\";\nexport { useCloseTransition, OVERLAY_EXIT_MS } from \"./hooks/use-close-transition\";\nexport { useRowSwipe } from \"./hooks/use-row-swipe\";\nexport type { SwipeStage, RowSwipeOptions, RowSwipeReturn } from \"./hooks/use-row-swipe\";\n\n// ── theme / palettes ─────────────────────────────────────────────────────────\nexport * from \"./theme/chart-palette\";\nexport * from \"./theme/palette-presets\";\nexport * from \"./theme/theme-store\";\nexport * from \"./theme/palette-store\";\n\n// ── components ───────────────────────────────────────────────────────────────\nexport * from \"./components/ui\";\nexport * from \"./components/search-field\";\nexport * from \"./components/dropdown\";\nexport * from \"./components/popover\";\nexport { SwipeableRow } from \"./components/swipeable-row\";\nexport type { SwipeAction, SwipeableRowProps } from \"./components/swipeable-row\";\nexport * from \"./components/calculator\";\nexport * from \"./components/numpad-sheet\";\nexport * from \"./components/number-input\";\nexport * from \"./components/currency-select\";\nexport * from \"./components/amount-input\";\nexport * from \"./components/combobox\";\nexport * from \"./components/picker-sheet\";\nexport * from \"./components/entity-combobox\";\nexport * from \"./components/multi-entity-combobox\";\nexport * from \"./components/multi-select\";\nexport * from \"./components/tooltip\";\nexport * from \"./components/user-avatar\";\nexport * from \"./components/settings-fields\";\nexport * from \"./components/account-settings\";\nexport * from \"./components/alert-banner\";\nexport * from \"./components/toggle-group\";\nexport * from \"./components/wizard-stepper\";\nexport * from \"./components/hover-menu\";\nexport * from \"./components/modal\";\nexport * from \"./components/full-bleed-dialog\";\nexport * from \"./components/grouped-picker\";\nexport * from \"./components/file-dropzone\";\nexport * from \"./components/mini-calendar\";\nexport * from \"./components/date-picker\";\nexport * from \"./components/chart\";\n\n// ── data-table suite ─────────────────────────────────────────────────────────\nexport * from \"./components/data-table-labels\";\nexport * from \"./components/data-table-sort\";\nexport * from \"./components/data-table-filters\";\nexport * from \"./components/data-table-pagination\";\nexport { FilterPopover } from \"./components/data-table-filter-popover\";\n// data-table re-exports SortState internally; export the rest explicitly to\n// avoid a duplicate SortState star-export (it comes from data-table-sort).\nexport { DataTable } from \"./components/data-table\";\nexport type { DataTableColumn, DataTableProps, ServerPagination, FilterState } from \"./components/data-table\";\n\n// ── shell (composable app chrome) ────────────────────────────────────────────\nexport * from \"./shell/topbar-controls\";\nexport * from \"./shell/top-bar\";\nexport * from \"./shell/app-shell\";\nexport * from \"./shell/option-switcher-menu\";\nexport * from \"./shell/role-switcher\";\nexport * from \"./shell/topbar-action-menu\";\n\n// ── feedback ─────────────────────────────────────────────────────────────────\n// The form somebody files a report with, and the parts an inbox is built from:\n// the status vocabulary both apps share value for value, the policy about what a\n// row may move to from where it stands, and the look of a status control, a\n// category badge and an opened report. The app still wires its own API, columns\n// and strings — see the note at the top of `feedback-inbox.tsx` for what is\n// deliberately left to it.\nexport * from \"./feedback/feedback-attachment\";\nexport * from \"./feedback/feedback-dialog\";\nexport * from \"./feedback/feedback-inbox\";\n\n// ── multi-step wizard ────────────────────────────────────────────────────────\n// The engine (step machine, validators, collected data), the chrome around it\n// (indicator + nav bar + cancel confirm) and the review step it ends on.\n// `components/wizard-stepper` next door is a different, much smaller thing: a bare\n// step indicator for a two-step import flow, with no engine.\n//\n// Two modules were removed here rather than kept as stock. `wizard-field` held a\n// `WizardField`/`WizardSelectField` pair that had never been rendered in any app —\n// a field group nothing was built from, which this comment used to advertise. And\n// `use-rhf-wizard-step` was the only thing in the package that touched\n// react-hook-form, for a hook neither consumer called: keeping it meant an optional\n// peer, a dev dependency and a type import in every consumer's typecheck, bought\n// for nobody. A design system may carry stock; untested stock nothing has ever\n// rendered is not stock, it is a liability with an export.\nexport * from \"./wizard/types\";\nexport * from \"./wizard/use-wizard\";\nexport * from \"./wizard/wizard-context\";\nexport * from \"./wizard/validation\";\nexport * from \"./wizard/wizard-step\";\nexport * from \"./wizard/wizard-summary\";\nexport * from \"./wizard/stepper-nav\";\n\n// ── guided tours ─────────────────────────────────────────────────────────────\nexport * from \"./tour/tour\";\n\n// ── command palette / global search ──────────────────────────────────────────\nexport * from \"./search/command-palette\";\n"],"mappings":"AAMA,cAAc;AACd,SAAS,UAAU;AACnB,SAAS,QAAQ,mBAAmB;AAIpC,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AAEhC,SAAS,kBAAkB,wBAAwB,yBAAyB;AAM5E,SAAS,cAAc,uBAAuB;AAC9C,SAAS,yBAAyB;AAClC,SAAS,oBAAoB,uBAAuB;AACpD,SAAS,mBAAmB;AAI5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,oBAAoB;AAE7B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,qBAAqB;AAG9B,SAAS,iBAAiB;AAI1B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AASd,cAAc;AACd,cAAc;AACd,cAAc;AAgBd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AAGd,cAAc;","names":[]}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * A tiny, dependency-free arithmetic evaluator for the numeric input fields.
3
+ * It lets users type a calculation (e.g. "12.50+3.20" or "100/3") straight into
4
+ * an amount field, or build one in the calculator popover, and have it resolve
5
+ * to a number. Deliberately NOT backed by `eval`/`Function` — it's a hand-rolled
6
+ * tokenizer + recursive-descent parser over a closed grammar (digits, decimals,
7
+ * `+ - * /`, unary minus, parentheses), so no arbitrary code can run.
8
+ */
9
+ /** Evaluate an arithmetic string, returning `null` if it isn't a valid sum.
10
+ * Accepts the on-screen operator glyphs (`× ÷ −`) and comma decimals too. */
11
+ declare function evaluateExpression(expr: string): number | null;
12
+ /** True when `s` carries arithmetic beyond a single leading minus — i.e. a plain
13
+ * number (or a lone negative like "-5") is NOT an expression, but "12+5" is.
14
+ * Used to decide whether a field's text should be evaluated on commit.
15
+ *
16
+ * The operator set is the one {@link evaluateExpression} accepts, on-screen glyphs
17
+ * included: the calculator keypad inserts `× ÷ −`, and a `−` that this said was not
18
+ * arithmetic went on to be read as a plain number — where the digits-only fallback
19
+ * in {@link commitExpression} silently ate it, turning "-42−−50" (= 8) into a bare
20
+ * 8 with no record that a sum had happened at all. */
21
+ declare function looksLikeExpression(s: string): boolean;
22
+ /** Render an evaluated number back to a field-friendly string, trimming binary
23
+ * float noise (so 0.1 + 0.2 reads "0.3", not "0.30000000000000004").
24
+ *
25
+ * FIXED notation, always, and that is the whole point of the function rather than a
26
+ * detail of it. This used to end in `parseFloat(n.toFixed(10)).toString()`, and
27
+ * `Number.prototype.toString` switches to EXPONENT notation below 1e-6 and at or
28
+ * above 1e21 — an alphabet containing `e`, `+` and `-`. Neither function that reads a
29
+ * field's text back accepts it: {@link tokenize} rejects `e` as an unknown character
30
+ * and {@link sanitizeLive} deletes it. So {@link commitExpression} fell through to
31
+ * its digits-only cleanup, which stripped the `e` and CONCATENATED the mantissa to
32
+ * the exponent. A field committed its own output as a different number:
33
+ *
34
+ * "1/10000000" -> "1e-7" -> "17" (AmountInput: "6")
35
+ * "999999999999*999999999999" -> "9.99999999998e+23" -> "9.9999999999823"
36
+ *
37
+ * Both controls commit on blur AND on Enter, so Enter-then-click-away was enough on
38
+ * its own, it was silent, and it was in a money field.
39
+ *
40
+ * The invariant, which the test asserts directly rather than by example:
41
+ * **`commitExpression(formatResult(n))` equals `formatResult(n)` for every finite
42
+ * `n`.** Anything rendered here has to survive being read back. */
43
+ declare function formatResult(n: number): string;
44
+ /** The single "on blur / Enter" transform a numeric field applies: evaluate the
45
+ * text if it's an expression, otherwise normalise it to a bare number string
46
+ * (comma → dot, dropping stray characters, non-leading minuses and anything after
47
+ * a second decimal point).
48
+ *
49
+ * The last clause is the backstop for Keksdose live #201. A field the user typed in
50
+ * cannot reach here malformed any more — {@link sanitizeLive} runs on every change,
51
+ * including a paste — but a value handed in from elsewhere (a recurring rule, an
52
+ * import, a test) still can, and "1.2.3" is not an expression (no operator), so it
53
+ * used to fall straight through unchanged and be read as NaN. Here the text is CUT
54
+ * at the second dot ("1.2") rather than joined ("1.23"): with no keystroke to
55
+ * ignore there is nothing to say which reading was meant, and the shorter one
56
+ * cannot silently invent a bigger number. */
57
+ declare function commitExpression(raw: string): string;
58
+ /** The live, per-keystroke transform a numeric field applies: comma → dot and
59
+ * drop anything that isn't a digit, decimal point, arithmetic operator (incl.
60
+ * the on-screen `× ÷` glyphs) or paren — so a typed calculation survives
61
+ * keystroke-by-keystroke but stray characters can't be entered. A lone leading
62
+ * `-` is kept — in a plain field it is simply a negative amount; in a field whose
63
+ * caller owns the sign it is read off again by {@link splitLeadingSign}. The text
64
+ * is resolved to a plain number on blur/Enter via {@link commitExpression}. */
65
+ declare function sanitizeLive(raw: string): string;
66
+ /** A bare, unsigned amount: digits with at most one decimal separator and nothing
67
+ * else — "42", "42.", ".5", "0,5". NOT "" (an empty field has no figure to put a
68
+ * sign in front of), not "-42", not "12+5". It is the only shape a DISPLAYED sign
69
+ * prefix makes sense in front of; an expression carries its own arithmetic and has
70
+ * to survive keystroke-for-keystroke. */
71
+ declare function isBareAmount(s: string): boolean;
72
+ /**
73
+ * Split a LEADING sign off a plain signed number (Keksdose live #201 rework):
74
+ * `"-42"` → `{ sign: "-", rest: "42" }`, everything else → `{ sign: "", rest: text }`.
75
+ *
76
+ * Only in front of a bare amount, because a minus with arithmetic after it IS
77
+ * arithmetic: "12-30" is a subtraction, and "-12+5" is -7, not -(12+5). Those come
78
+ * back untouched and are resolved by {@link commitExpression} on blur — at which
79
+ * point the RESULT is a plain signed number and this function does get to speak.
80
+ *
81
+ * A lone "-" splits (rest ""): the sign is a statement about the direction the
82
+ * moment it is typed, before any digit exists.
83
+ */
84
+ declare function splitLeadingSign(text: string): {
85
+ sign: "" | "-" | "+";
86
+ rest: string;
87
+ };
88
+
89
+ export { commitExpression, evaluateExpression, formatResult, isBareAmount, looksLikeExpression, sanitizeLive, splitLeadingSign };