@eifi1/ui-kit 0.17.0 → 0.19.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 (210) hide show
  1. package/README.md +83 -4
  2. package/dist/chart.d.ts +13 -1
  3. package/dist/components/account-chips.d.ts +153 -0
  4. package/dist/components/account-chips.js +135 -0
  5. package/dist/components/account-chips.js.map +1 -0
  6. package/dist/components/account-settings-labels.d.ts +10 -2
  7. package/dist/components/account-settings-labels.js +3 -0
  8. package/dist/components/account-settings-labels.js.map +1 -1
  9. package/dist/components/amount-input.d.ts +13 -1
  10. package/dist/components/button-group.d.ts +13 -1
  11. package/dist/components/calculator.d.ts +13 -1
  12. package/dist/components/chip.d.ts +25 -1
  13. package/dist/components/chip.js +143 -6
  14. package/dist/components/chip.js.map +1 -1
  15. package/dist/components/confirm-dialog.d.ts +42 -1
  16. package/dist/components/confirm-dialog.js +36 -5
  17. package/dist/components/confirm-dialog.js.map +1 -1
  18. package/dist/components/copy-button.d.ts +13 -1
  19. package/dist/components/danger-confirm.d.ts +55 -4
  20. package/dist/components/danger-confirm.js +67 -30
  21. package/dist/components/danger-confirm.js.map +1 -1
  22. package/dist/components/data-table-cells.d.ts +1 -1
  23. package/dist/components/data-table-filter-popover.d.ts +1 -1
  24. package/dist/components/data-table-filters.d.ts +1 -1
  25. package/dist/components/data-table.d.ts +1 -1
  26. package/dist/components/data-table.js +2 -0
  27. package/dist/components/data-table.js.map +1 -1
  28. package/dist/components/facing-pair.d.ts +13 -1
  29. package/dist/components/file-button.d.ts +13 -1
  30. package/dist/components/file-dropzone.d.ts +13 -1
  31. package/dist/components/floating-panel.d.ts +4 -1
  32. package/dist/components/floating-panel.js +15 -1
  33. package/dist/components/floating-panel.js.map +1 -1
  34. package/dist/components/form-actions.d.ts +13 -1
  35. package/dist/components/form-actions.js +3 -0
  36. package/dist/components/form-actions.js.map +1 -1
  37. package/dist/components/legal.d.ts +84 -0
  38. package/dist/components/legal.js +55 -0
  39. package/dist/components/legal.js.map +1 -0
  40. package/dist/components/money-field.d.ts +13 -1
  41. package/dist/components/number-field.d.ts +14 -2
  42. package/dist/components/number-field.js +23 -22
  43. package/dist/components/number-field.js.map +1 -1
  44. package/dist/components/number-input.d.ts +13 -1
  45. package/dist/components/numpad-sheet.d.ts +13 -1
  46. package/dist/components/passkeys-setting.d.ts +27 -1
  47. package/dist/components/passkeys-setting.js +31 -5
  48. package/dist/components/passkeys-setting.js.map +1 -1
  49. package/dist/components/progress-bar.d.ts +15 -1
  50. package/dist/components/progress-bar.js +8 -1
  51. package/dist/components/progress-bar.js.map +1 -1
  52. package/dist/components/reauth-dialog.d.ts +90 -0
  53. package/dist/components/reauth-dialog.js +158 -0
  54. package/dist/components/reauth-dialog.js.map +1 -0
  55. package/dist/components/series-chart.d.ts +13 -1
  56. package/dist/components/server-wake.d.ts +60 -0
  57. package/dist/components/server-wake.js +79 -0
  58. package/dist/components/server-wake.js.map +1 -0
  59. package/dist/components/settings-fields.d.ts +13 -1
  60. package/dist/components/share-card.d.ts +64 -0
  61. package/dist/components/share-card.js +350 -0
  62. package/dist/components/share-card.js.map +1 -0
  63. package/dist/components/status-dot.d.ts +2 -0
  64. package/dist/components/text-link.d.ts +13 -1
  65. package/dist/components/time-input.d.ts +13 -1
  66. package/dist/components/toast.d.ts +10 -1
  67. package/dist/components/toast.js +42 -2
  68. package/dist/components/toast.js.map +1 -1
  69. package/dist/components/toggle-group.js +5 -2
  70. package/dist/components/toggle-group.js.map +1 -1
  71. package/dist/components/translation-review-editor.d.ts +56 -0
  72. package/dist/components/translation-review-editor.js +159 -0
  73. package/dist/components/translation-review-editor.js.map +1 -0
  74. package/dist/components/translation-review-labels.d.ts +92 -0
  75. package/dist/components/translation-review-labels.js +67 -0
  76. package/dist/components/translation-review-labels.js.map +1 -0
  77. package/dist/components/translation-review-parts.d.ts +97 -0
  78. package/dist/components/translation-review-parts.js +163 -0
  79. package/dist/components/translation-review-parts.js.map +1 -0
  80. package/dist/components/translation-review.d.ts +94 -0
  81. package/dist/components/translation-review.js +344 -0
  82. package/dist/components/translation-review.js.map +1 -0
  83. package/dist/components/ui.d.ts +13 -1
  84. package/dist/components/ui.js +43 -28
  85. package/dist/components/ui.js.map +1 -1
  86. package/dist/components/use-table-state.d.ts +1 -1
  87. package/dist/components/user-avatar.d.ts +2 -0
  88. package/dist/components/write-lock.d.ts +68 -0
  89. package/dist/components/write-lock.js +33 -0
  90. package/dist/components/write-lock.js.map +1 -0
  91. package/dist/{data-table-CpkWUJBY.d.ts → data-table-3mi2R3so.d.ts} +19 -1
  92. package/dist/data-table.d.ts +1 -1
  93. package/dist/hooks/use-file-drop.d.ts +13 -1
  94. package/dist/hooks/use-overlay-history.js +16 -0
  95. package/dist/hooks/use-overlay-history.js.map +1 -1
  96. package/dist/i18n/defaults.d.ts +13 -1
  97. package/dist/i18n/defaults.js +15 -1
  98. package/dist/i18n/defaults.js.map +1 -1
  99. package/dist/i18n/german.d.ts +81 -0
  100. package/dist/i18n/{locales/de.js → german.js} +110 -9
  101. package/dist/i18n/german.js.map +1 -0
  102. package/dist/i18n/kit-labels.d.ts +13 -1
  103. package/dist/i18n/kit-labels.js +5 -0
  104. package/dist/i18n/kit-labels.js.map +1 -1
  105. package/dist/i18n/languages.d.ts +243 -0
  106. package/dist/i18n/languages.js +70 -0
  107. package/dist/i18n/languages.js.map +1 -0
  108. package/dist/i18n/locales/de-CH.d.ts +26 -8
  109. package/dist/i18n/locales/de-CH.js +7 -3
  110. package/dist/i18n/locales/de-CH.js.map +1 -1
  111. package/dist/i18n/locales/en.d.ts +13 -1
  112. package/dist/i18n/locales/en.js +8 -0
  113. package/dist/i18n/locales/en.js.map +1 -1
  114. package/dist/i18n/locales/es.d.ts +13 -1
  115. package/dist/i18n/locales/es.js +127 -27
  116. package/dist/i18n/locales/es.js.map +1 -1
  117. package/dist/i18n/locales/fr.d.ts +13 -1
  118. package/dist/i18n/locales/fr.js +101 -1
  119. package/dist/i18n/locales/fr.js.map +1 -1
  120. package/dist/i18n/locales/hu.d.ts +13 -1
  121. package/dist/i18n/locales/hu.js +105 -1
  122. package/dist/i18n/locales/hu.js.map +1 -1
  123. package/dist/i18n/locales/it.d.ts +13 -1
  124. package/dist/i18n/locales/it.js +134 -34
  125. package/dist/i18n/locales/it.js.map +1 -1
  126. package/dist/i18n/locales/zh.d.ts +13 -1
  127. package/dist/i18n/locales/zh.js +101 -1
  128. package/dist/i18n/locales/zh.js.map +1 -1
  129. package/dist/i18n/review.d.ts +102 -0
  130. package/dist/i18n/review.js +158 -0
  131. package/dist/i18n/review.js.map +1 -0
  132. package/dist/i18n/swiss.d.ts +3 -3
  133. package/dist/i18n/swiss.js.map +1 -1
  134. package/dist/index.d.ts +17 -4
  135. package/dist/index.js +97 -1
  136. package/dist/index.js.map +1 -1
  137. package/dist/{kit-labels-C4uB_zmg.d.ts → kit-labels-D1J2_jyD.d.ts} +236 -9
  138. package/dist/lib/floating-stack.d.ts +31 -0
  139. package/dist/lib/floating-stack.js +42 -0
  140. package/dist/lib/floating-stack.js.map +1 -0
  141. package/dist/lib/server-wake.d.ts +146 -0
  142. package/dist/lib/server-wake.js +122 -0
  143. package/dist/lib/server-wake.js.map +1 -0
  144. package/dist/lib/translation-review.d.ts +232 -0
  145. package/dist/lib/translation-review.js +194 -0
  146. package/dist/lib/translation-review.js.map +1 -0
  147. package/dist/rhf/fields.d.ts +13 -1
  148. package/dist/rhf/form.d.ts +13 -1
  149. package/dist/rhf.d.ts +13 -1
  150. package/dist/search/global-search.d.ts +3 -1
  151. package/dist/search/global-search.js.map +1 -1
  152. package/dist/shell/app-shell.d.ts +13 -1
  153. package/dist/shell/top-bar-brand.d.ts +13 -1
  154. package/dist/shell/topbar-action-menu.d.ts +2 -0
  155. package/dist/shell.d.ts +13 -1
  156. package/dist/wizard/stepper-nav.d.ts +13 -1
  157. package/dist/wizard.d.ts +13 -1
  158. package/package.json +1 -1
  159. package/src/components/account-chips.tsx +302 -0
  160. package/src/components/account-settings-labels.ts +16 -2
  161. package/src/components/chip.tsx +239 -6
  162. package/src/components/confirm-dialog.tsx +82 -7
  163. package/src/components/danger-confirm.tsx +118 -33
  164. package/src/components/data-table.tsx +20 -0
  165. package/src/components/floating-panel.tsx +19 -1
  166. package/src/components/form-actions.tsx +22 -3
  167. package/src/components/legal.tsx +159 -0
  168. package/src/components/number-field.tsx +32 -26
  169. package/src/components/passkeys-setting.tsx +68 -7
  170. package/src/components/progress-bar.tsx +21 -1
  171. package/src/components/reauth-dialog.tsx +273 -0
  172. package/src/components/server-wake.tsx +156 -0
  173. package/src/components/share-card.tsx +615 -0
  174. package/src/components/toast.tsx +72 -2
  175. package/src/components/toggle-group.tsx +5 -2
  176. package/src/components/translation-review-editor.tsx +261 -0
  177. package/src/components/translation-review-labels.ts +152 -0
  178. package/src/components/translation-review-parts.tsx +269 -0
  179. package/src/components/translation-review.tsx +502 -0
  180. package/src/components/ui.tsx +89 -32
  181. package/src/components/write-lock.tsx +96 -0
  182. package/src/hooks/use-overlay-history.ts +51 -0
  183. package/src/i18n/defaults.ts +14 -0
  184. package/src/i18n/{locales/de.ts → german.ts} +120 -13
  185. package/src/i18n/kit-labels.tsx +29 -2
  186. package/src/i18n/languages.ts +285 -0
  187. package/src/i18n/locales/de-CH.ts +17 -8
  188. package/src/i18n/locales/en.ts +8 -0
  189. package/src/i18n/locales/es.ts +128 -26
  190. package/src/i18n/locales/fr.ts +103 -0
  191. package/src/i18n/locales/hu.ts +107 -0
  192. package/src/i18n/locales/it.ts +135 -33
  193. package/src/i18n/locales/zh.ts +102 -0
  194. package/src/i18n/review.ts +193 -0
  195. package/src/i18n/swiss.ts +3 -3
  196. package/src/index.ts +86 -1
  197. package/src/lib/floating-stack.ts +66 -0
  198. package/src/lib/server-wake.ts +291 -0
  199. package/src/lib/translation-review.ts +465 -0
  200. package/src/search/global-search.tsx +3 -1
  201. package/dist/i18n/locales/de-CH-informal.d.ts +0 -61
  202. package/dist/i18n/locales/de-CH-informal.js +0 -8
  203. package/dist/i18n/locales/de-CH-informal.js.map +0 -1
  204. package/dist/i18n/locales/de-informal.d.ts +0 -82
  205. package/dist/i18n/locales/de-informal.js +0 -37
  206. package/dist/i18n/locales/de-informal.js.map +0 -1
  207. package/dist/i18n/locales/de.d.ts +0 -66
  208. package/dist/i18n/locales/de.js.map +0 -1
  209. package/src/i18n/locales/de-CH-informal.ts +0 -10
  210. package/src/i18n/locales/de-informal.ts +0 -61
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { AnchorHTMLAttributes, Ref, ReactElement, ButtonHTMLAttributes, ReactNode, SelectHTMLAttributes, InputHTMLAttributes, ComponentPropsWithoutRef, TextareaHTMLAttributes } from 'react';
2
+ import { AnchorHTMLAttributes, Ref, ReactElement, ButtonHTMLAttributes, ReactNode, SelectHTMLAttributes, ComponentPropsWithoutRef, InputHTMLAttributes, TextareaHTMLAttributes } from 'react';
3
3
  import { DataTableLabels } from './components/data-table-labels.js';
4
4
  import { MiniCalendarLabels, WeekDay } from './components/mini-calendar.js';
5
5
  import { CalendarHeatmapLabels } from './components/calendar-heatmap.js';
@@ -22,7 +22,7 @@ import { PasswordStrengthLabels } from './components/password-strength.js';
22
22
  import { DangerConfirmLabels } from './components/danger-confirm.js';
23
23
  import { SwatchPickerLabels } from './components/swatch-picker.js';
24
24
  import { IconPickerLabels } from './components/icon-picker.js';
25
- import { DialogFrameLabels } from './components/dialog-frame.js';
25
+ import { DialogFrameProps, DialogFrameLabels } from './components/dialog-frame.js';
26
26
  import { MeasuredGridLabels } from './components/measured-grid.js';
27
27
  import { f as FeedbackAttachmentFieldLabels, q as FeedbackDialogTextLabels } from './feedback-attachment-WFiXJ8We.js';
28
28
  import { FeedbackThreadLabels, FeedbackComposerLabels } from './feedback/feedback-thread.js';
@@ -42,6 +42,12 @@ import { ErrorBoundaryLabels } from './components/error-boundary.js';
42
42
  import { AuthedImageLabels } from './components/authed-image.js';
43
43
  import { ImageGridLabels } from './components/image-grid.js';
44
44
  import { LightboxLabels } from './components/lightbox.js';
45
+ import { WriteLockLabels } from './components/write-lock.js';
46
+ import { LegalLabels } from './components/legal.js';
47
+ import { RoleDefinition, AccountStateLabels } from './components/account-chips.js';
48
+ import { ReauthDialogLabels } from './components/reauth-dialog.js';
49
+ import { ServerWakeLabels } from './components/server-wake.js';
50
+ import { TranslationReviewLabels } from './components/translation-review-labels.js';
45
51
 
46
52
  type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "brand" | "link";
47
53
  type ButtonSize = "sm" | "md";
@@ -145,6 +151,13 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
145
151
  * DangerConfirm keeps its own.
146
152
  */
147
153
  disabledReason?: ReactNode;
154
+ /**
155
+ * This button COMMITS — it saves, creates, deletes. Under a locked
156
+ * {@link WriteLockProvider} it is disabled the `disabledReason` way, with the lock's
157
+ * reason (which wins over a `disabledReason` of its own). No provider, or an
158
+ * unlocked one: no effect. See {@link WriteLockProvider}.
159
+ */
160
+ commit?: boolean;
148
161
  /**
149
162
  * Busy — the save is in flight: a spinner, `aria-busy`, and no second submit.
150
163
  *
@@ -219,6 +232,7 @@ interface ButtonLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>,
219
232
  pending?: never;
220
233
  pressed?: never;
221
234
  disabledReason?: never;
235
+ commit?: never;
222
236
  }
223
237
  declare function Button(props: ButtonLinkProps): ReactElement;
224
238
  declare function Button(props: ButtonProps): ReactElement;
@@ -427,6 +441,10 @@ interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
427
441
  * reason nobody can see is not one. Wins over `disabled`, as on Button.
428
442
  */
429
443
  disabledReason?: ReactNode;
444
+ /** This action COMMITS — {@link Button}'s `commit`: under a locked
445
+ * {@link WriteLockProvider} it takes the `disabledReason` path with the lock's
446
+ * reason. No effect without a lock. */
447
+ commit?: boolean;
430
448
  /** The `<button>` element — a prop in React 19, as on {@link Button}. */
431
449
  ref?: Ref<HTMLButtonElement>;
432
450
  /** Only on the link form — see {@link IconButtonLinkProps}. */
@@ -470,6 +488,7 @@ interface IconButtonLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElemen
470
488
  form?: never;
471
489
  pressed?: never;
472
490
  disabledReason?: never;
491
+ commit?: never;
473
492
  disabledStyle?: never;
474
493
  pending?: never;
475
494
  }
@@ -1446,7 +1465,8 @@ type FormActionsPlacement = "inline" | "sticky" | "dialog";
1446
1465
  * md 768, lg 1024, xl 1280px), as `BulkActionBar`'s `variant` breakpoints are, and
1447
1466
  * resolved the same way, in JS: the sticky row is positioned by inline style, which a
1448
1467
  * `md:` class cannot reach. keksdose switched `placement` on its own media query at
1449
- * every long form for want of this.
1468
+ * every long form for want of this. The breakpoints are the VIEWPORT's, not the
1469
+ * container's: a form in a narrow pane on a wide screen resolves to `md`.
1450
1470
  */
1451
1471
  interface ResponsiveFormActionsPlacement {
1452
1472
  base: FormActionsPlacement;
@@ -1468,7 +1488,7 @@ type FormActionsStickyWithin = "viewport" | "container";
1468
1488
  * analytics, an `aria-describedby`. The props FormActions drives itself (type, click,
1469
1489
  * disabled, variant, form, children, the busy state) are not among them.
1470
1490
  */
1471
- type FormActionsSubmitProps = Omit<ButtonProps, "type" | "onClick" | "disabled" | "disabledReason" | "variant" | "form" | "children" | "aria-busy"> & {
1491
+ type FormActionsSubmitProps = Omit<ButtonProps, "type" | "onClick" | "disabled" | "disabledReason" | "variant" | "form" | "children" | "aria-busy" | "commit"> & {
1472
1492
  [key: `data-${string}`]: string | number | boolean | undefined;
1473
1493
  };
1474
1494
  /** The start-side action of {@link FormActionsProps.destructive}, as data. */
@@ -1515,6 +1535,18 @@ interface FormActionsProps extends Omit<ComponentPropsWithoutRef<"div">, "childr
1515
1535
  submitDisabled?: boolean;
1516
1536
  /** Why save is disabled — see Button's `disabledReason`. Keeps it focusable. */
1517
1537
  submitDisabledReason?: ReactNode;
1538
+ /**
1539
+ * Save is a commit: under a locked {@link WriteLockProvider} it is disabled with the
1540
+ * lock's reason (over `submitDisabledReason`), focusable, the reason in its tooltip —
1541
+ * Button's `commit`. The `{ label, onClick }` form of {@link destructive} is a
1542
+ * commit too and is locked with it; Cancel and `children` are not (closing a form
1543
+ * writes nothing). No provider, or an unlocked one: no effect. Default `false`.
1544
+ *
1545
+ * keksdose wrote `submitDisabled={lock.locked || …}` and
1546
+ * `submitDisabledReason={lock.locked ? lock.reason : undefined}` at each form; this is
1547
+ * that pair, read from the provider.
1548
+ */
1549
+ commit?: boolean;
1518
1550
  /** The save button's variant. Default `brand`; `danger` for a save that destroys
1519
1551
  * (kastlan's `destructive` flag on its own FormActions). */
1520
1552
  submitVariant?: ButtonVariant;
@@ -1545,7 +1577,8 @@ interface FormActionsProps extends Omit<ComponentPropsWithoutRef<"div">, "childr
1545
1577
  * from both sides, with the content scrolling past it in the gutters (keksdose wrote
1546
1578
  * `@max-md:-mx-3 @max-md:px-3` by hand). Pass the container's padding (`"0.75rem"`,
1547
1579
  * `12`, `"var(--pane-px)"`). Applied only while the resolved placement is sticky, so
1548
- * `placement={{ base: "sticky", md: "inline" }}` bleeds on the phone alone.
1580
+ * `placement={{ base: "sticky", md: "inline" }}` bleeds on the phone alone. The margin
1581
+ * is `calc(-1 * X)`; jsdom folds that to `calc(-X)`, so match it loosely in tests.
1549
1582
  */
1550
1583
  bleed?: string | number;
1551
1584
  /**
@@ -1591,7 +1624,187 @@ interface FormActionsProps extends Omit<ComponentPropsWithoutRef<"div">, "childr
1591
1624
  * The spinner is the kit's {@link Spinner}, drawn inside the button with `label={null}`
1592
1625
  * so the button's name stays its text; `aria-busy` is what says it is working.
1593
1626
  */
1594
- declare function FormActions({ onCancel, onSubmit, submitLabel, cancelLabel, pendingLabel, submitIcon: SubmitIcon, submitProps, pending, submitDisabled, submitDisabledReason, submitVariant, destructive, start: startSlot, align, placement: placementProp, stickyWithin, bleed, form, children, className, style, ...rest }: FormActionsProps): react.JSX.Element;
1627
+ declare function FormActions({ onCancel, onSubmit, submitLabel, cancelLabel, pendingLabel, submitIcon: SubmitIcon, submitProps, pending, submitDisabled, submitDisabledReason, commit, submitVariant, destructive, start: startSlot, align, placement: placementProp, stickyWithin, bleed, form, children, className, style, ...rest }: FormActionsProps): react.JSX.Element;
1628
+
1629
+ /**
1630
+ * Who holds one record, and the owner's every way of changing that — Kurvenschmiede's
1631
+ * `features/sharing/share-dialog.tsx` and keksdose's `features/budgets/budget-share-card.tsx`,
1632
+ * which are the same component twice (Kurvenschmiede's own doc comment says so).
1633
+ *
1634
+ * Presentational: the kit draws the rows, asks before a removal (the kit's
1635
+ * {@link useConfirm}), keeps the field and the busy state, and checks only that an
1636
+ * address is SHAPED like one. Everything that is policy stays with the app — who may
1637
+ * share, whether an address has an account (that is what makes a grant pending), what
1638
+ * a role permits, every request — through the callbacks, which may return a promise.
1639
+ */
1640
+ interface ShareCardLabels {
1641
+ /** ShareDialog's heading. */
1642
+ dialogTitle: string;
1643
+ /** ShareDialog's close button. */
1644
+ close: string;
1645
+ /** The add field's label. */
1646
+ email: string;
1647
+ /** The add field's label when `addWithoutEmail` lets it stay empty. */
1648
+ emailOptional: string;
1649
+ emailPlaceholder: string;
1650
+ /** Under the field when what was typed is not an address. */
1651
+ invalidEmail: string;
1652
+ /** Accessible name of the role choice beside the field. */
1653
+ role: string;
1654
+ /** Accessible name of one grantee's role choice. */
1655
+ roleOf: (name: string) => string;
1656
+ /** The add button. */
1657
+ add: string;
1658
+ /** Heading over the list. */
1659
+ whoHasAccess: string;
1660
+ /** The list is empty. */
1661
+ nobodyYet: string;
1662
+ /** Chip on a grant whose address has no account yet. */
1663
+ pending: string;
1664
+ /** A pending grant with no address — a link anybody holding it can redeem. */
1665
+ openInvite: string;
1666
+ copyLink: string;
1667
+ /** A candidate that is a team (its sublabel, and the line under a team row). */
1668
+ team: string;
1669
+ /** Under the field while a team is the one named. */
1670
+ teamHint: (name: string) => string;
1671
+ /** The remove button on a grantee. */
1672
+ remove: string;
1673
+ removeConfirm: (name: string) => string;
1674
+ /** The remove button on a pending grant. */
1675
+ revokePending: string;
1676
+ revokePendingConfirm: (name: string) => string;
1677
+ /** A callback rejected and no `formatError` says better. */
1678
+ failed: string;
1679
+ /** Shown while `loading`. */
1680
+ loading: string;
1681
+ }
1682
+ declare const DEFAULT_SHARE_CARD_LABELS: ShareCardLabels;
1683
+ /**
1684
+ * One role of the app's vocabulary: Kurvenschmiede's viewer/editor, keksdose's guest.
1685
+ *
1686
+ * The admin roster's {@link RoleDefinition} (label, tone, icon) plus the key it is
1687
+ * filed under — an ARRAY here rather than the keyed `RoleVocabulary` record,
1688
+ * because the order is the order of the role choice's segments.
1689
+ */
1690
+ type ShareRole = RoleDefinition & {
1691
+ key: string;
1692
+ /** What the role allows — under the role choice once chosen, and the chip's tooltip. */
1693
+ description?: string;
1694
+ };
1695
+ /** Somebody (or a team) who holds the record now. */
1696
+ interface ShareGrantee {
1697
+ id: string | number;
1698
+ name: string;
1699
+ /** The second line. A team row says {@link ShareCardLabels.team} instead. */
1700
+ email?: string | null;
1701
+ /** A {@link ShareRole.key}. */
1702
+ role: string;
1703
+ /** `team`: the grant reaches everybody in it (Kurvenschmiede). Default `person`. */
1704
+ kind?: "person" | "team";
1705
+ /** Shown, but neither its role nor its access can be changed here (an owner row). */
1706
+ locked?: boolean;
1707
+ /** The app's own line under the row — keksdose's per-guest key delivery. */
1708
+ extra?: ReactNode;
1709
+ }
1710
+ /** A grant waiting for its address to have an account — or, with no address, an
1711
+ * open invite link (keksdose). */
1712
+ interface SharePendingGrant {
1713
+ id: string | number;
1714
+ email?: string | null;
1715
+ /** A {@link ShareRole.key}; left out when the app has only one role. */
1716
+ role?: string;
1717
+ /** The invite link, offered with a copy button. */
1718
+ link?: string;
1719
+ }
1720
+ /** A completion offered while typing: a past grantee, or one of the sharer's teams. */
1721
+ interface ShareCandidate {
1722
+ id: string | number;
1723
+ name: string;
1724
+ /** A person's address: taking the row fills the field with it, and the grant is
1725
+ * then an ordinary address grant. */
1726
+ email?: string | null;
1727
+ /** `team` (or a person with no address): taking the row names THIS candidate, and
1728
+ * `onAdd` receives it instead of an address. */
1729
+ kind?: "person" | "team";
1730
+ icon?: ReactNode;
1731
+ }
1732
+ /** What the add button asks for. Exactly one of `email` / `candidate` is set, except
1733
+ * under `addWithoutEmail`, where both may be null (an open invite). */
1734
+ interface ShareAddRequest {
1735
+ email: string | null;
1736
+ candidate: ShareCandidate | null;
1737
+ /** A {@link ShareRole.key}. */
1738
+ role: string;
1739
+ }
1740
+ type MaybePromise = void | Promise<unknown>;
1741
+ interface SharePanelProps {
1742
+ grantees: ShareGrantee[];
1743
+ /** The role vocabulary, in display order. One role: no choice is offered and each
1744
+ * row shows its chip. */
1745
+ roles: ShareRole[];
1746
+ pending?: SharePendingGrant[];
1747
+ /** Completions for the field. Left out: the field is a plain address field. */
1748
+ candidates?: ShareCandidate[];
1749
+ /** The role a new grant starts with. Default: the first of `roles`. */
1750
+ defaultRole?: string;
1751
+ /** Resolve to clear the field; reject to keep it and show the error. Left out (or
1752
+ * `readOnly`): no add form. */
1753
+ onAdd?: (request: ShareAddRequest) => MaybePromise;
1754
+ /** Left out: roles are shown as chips. */
1755
+ onRoleChange?: (grantee: ShareGrantee, role: string) => MaybePromise;
1756
+ /** Called after the kit's confirm. Left out: no remove button. */
1757
+ onRemove?: (grantee: ShareGrantee) => MaybePromise;
1758
+ /** Called after the kit's confirm. Left out: no withdraw button. */
1759
+ onRevokePending?: (grant: SharePendingGrant) => MaybePromise;
1760
+ /** keksdose: an empty field is a request too — an invite link with no address. */
1761
+ addWithoutEmail?: boolean;
1762
+ /** Under the add field: whatever the app needs said there (Kurvenschmiede's "your
1763
+ * pending grant does not let anybody register"). A function receives the taken
1764
+ * candidate, so it can say something else while a team is named. */
1765
+ caption?: ReactNode | ((candidate: ShareCandidate | null) => ReactNode);
1766
+ /** Above everything — what sharing means here (keksdose's guest explanation). */
1767
+ intro?: ReactNode;
1768
+ /** The list is being fetched: a spinner instead of the rows. */
1769
+ loading?: boolean;
1770
+ /** The app's own error, shown in the error box (wins over a caught rejection). */
1771
+ error?: ReactNode;
1772
+ /** Turns a callback's rejection into words. Default {@link ShareCardLabels.failed}. */
1773
+ formatError?: (error: unknown) => ReactNode;
1774
+ /** Look, do not touch: no form, chips for roles, no remove buttons. Copying an
1775
+ * invite link still works. */
1776
+ readOnly?: boolean;
1777
+ /** Focus the field on mount — a dialog that exists to add somebody. */
1778
+ autoFocus?: boolean;
1779
+ labels?: Partial<ShareCardLabels>;
1780
+ }
1781
+ /**
1782
+ * The body both forms share: the add form, then the list. Exported for an app that
1783
+ * frames it itself (a settings section, a sheet).
1784
+ */
1785
+ declare function SharePanel({ grantees, roles, pending, candidates, defaultRole, onAdd, onRoleChange, onRemove, onRevokePending, addWithoutEmail, caption, intro, loading, error, formatError, readOnly, autoFocus, labels: labelsProp, }: SharePanelProps): react.JSX.Element;
1786
+ interface ShareCardProps extends SharePanelProps {
1787
+ /** A heading over the card. */
1788
+ title?: ReactNode;
1789
+ /** The Card's variant. Default `outline` — a block inside a section, as keksdose's
1790
+ * card under a budget row is. */
1791
+ variant?: CardProps["variant"];
1792
+ className?: string;
1793
+ }
1794
+ /** {@link SharePanel} in a card — keksdose's inline form, under the row it shares. */
1795
+ declare function ShareCard({ title, variant, className, ...panel }: ShareCardProps): react.JSX.Element;
1796
+ interface ShareDialogProps extends SharePanelProps, Pick<DialogFrameProps, "open" | "onClose" | "size" | "className"> {
1797
+ /** The heading. Default {@link ShareCardLabels.dialogTitle}. */
1798
+ title?: ReactNode;
1799
+ /** Under the heading — what is being shared (Kurvenschmiede passes its name). */
1800
+ description?: ReactNode;
1801
+ }
1802
+ /**
1803
+ * {@link SharePanel} in the kit's {@link DialogFrame} with a Close button —
1804
+ * Kurvenschmiede's form. Sharing commits as it goes, so there is nothing to save or
1805
+ * cancel: Close is the only action.
1806
+ */
1807
+ declare function ShareDialog({ open, onClose, size, className, title, description, ...panel }: ShareDialogProps): react.JSX.Element;
1595
1808
 
1596
1809
  /**
1597
1810
  * EVERY string the kit renders, as one typed tree — and an optional provider that
@@ -1806,6 +2019,19 @@ interface UiKitLabels {
1806
2019
  authedImage: AuthedImageLabels;
1807
2020
  imageGrid: ImageGridLabels;
1808
2021
  lightbox: LightboxLabels;
2022
+ /** 0.18.0: `WriteLockProvider`'s fallback reason, `AccountStateChip`, `SharePanel` /
2023
+ * `ShareCard` / `ShareDialog`, `ReauthDialog`. */
2024
+ writeLock: WriteLockLabels;
2025
+ accountState: AccountStateLabels;
2026
+ shareCard: ShareCardLabels;
2027
+ reauthDialog: ReauthDialogLabels;
2028
+ /** 0.18.0: `ServerWakeNotice` — keksdose's cold-start notice, for every app. */
2029
+ serverWake: ServerWakeLabels;
2030
+ /** 0.19.0: the translation-review parts (`TranslationReviewPanel`, its editor, chip,
2031
+ * progress, locale tabs and export) — keksdose's /translations, for every app. */
2032
+ translationReview: TranslationReviewLabels;
2033
+ /** 0.19.0: `LegalLinks`' navigation name — the legal pages' shell, for every app. */
2034
+ legal: LegalLabels;
1809
2035
  }
1810
2036
  /**
1811
2037
  * A label override, as deep as the labels go: an object of labels (a namespace, or a
@@ -1939,13 +2165,14 @@ declare function useKitFileLabels(prop?: Partial<FileLabels>): FileLabels;
1939
2165
  * The dot paths of every key `labels` does NOT supply — the ones a translated app is
1940
2166
  * still showing in English. Assert on it in the app's own test:
1941
2167
  *
1942
- * expect(missingKitLabels(de)).toEqual([]);
2168
+ * expect(missingKitLabels(labels, DEFAULT_UI_KIT_LABELS)).toEqual([]);
1943
2169
  *
1944
2170
  * `reference` is the complete tree to check against — pass
1945
2171
  * {@link DEFAULT_UI_KIT_LABELS} (src/i18n/defaults.ts). It is a parameter rather than an import
1946
2172
  * because the defaults live beside their components, and this module is imported BY
1947
- * those components.
2173
+ * those components. Left out (a test file is not type-checked), it throws saying so
2174
+ * rather than "Cannot convert undefined or null to object" (keksdose, 0.18).
1948
2175
  */
1949
2176
  declare function missingKitLabels(labels: UiKitLabelOverrides | undefined, reference: UiKitLabels): string[];
1950
2177
 
1951
- export { FIELD_DISPLAY as $, type AppShellLabels as A, Button as B, type CalculatorLabels as C, CopyButton as D, type CopyButtonLabels as E, type CopyButtonProps as F, type CurrencyLabels as G, DEFAULT_APP_SHELL_LABELS as H, DEFAULT_CALCULATOR_LABELS as I, DEFAULT_COMBOBOX_LABELS as J, DEFAULT_COMMON_LABELS as K, DEFAULT_COPY_BUTTON_LABELS as L, DEFAULT_CURRENCY_LABELS as M, DEFAULT_DATE_PICKER_LABELS as N, DEFAULT_FILE_LABELS as O, DEFAULT_FILE_PICKER_LABELS as P, DEFAULT_FORM_ACTIONS_LABELS as Q, DEFAULT_MULTI_SELECT_LABELS as R, DEFAULT_PASSWORD_REVEAL_LABELS as S, DEFAULT_PICKER_SHEET_LABELS as T, DEFAULT_SWIPEABLE_ROW_LABELS as U, DEFAULT_TABS_LABELS as V, DEFAULT_TOP_BAR_LABELS as W, type DatePickerLabels as X, EmptyState as Y, type EmptyStateProps as Z, FIELD_BASE as _, type ButtonClassesOptions as a, type UiKitLabels as a$, FIELD_FLOATING_PAD as a0, FIELD_INVALID as a1, FIELD_TRIGGER as a2, FIELD_WRITABLE_LOOK as a3, FLOATING_INPUT_CLASS as a4, FLOATING_LABEL_CLASS as a5, FLOATING_LABEL_STATIC as a6, FieldChevron as a7, type FieldChevronProps as a8, FieldHint as a9, type IconButtonToneColor as aA, type IconButtonVariant as aB, Input as aC, type InputProps as aD, type KitLinkComponent as aE, type KitLinkProps as aF, Label as aG, type LabelOverride as aH, type LabelProps as aI, type MultiSelectLabels as aJ, PHONE_QUERY as aK, type PasswordRevealLabels as aL, type PickerSheetLabels as aM, type ResponsiveFormActionsPlacement as aN, Select as aO, type SelectProps as aP, Spinner as aQ, type SpinnerProps as aR, type SwipeableRowLabels as aS, type TabItem as aT, Tabs as aU, type TabsLabels as aV, type TabsProps as aW, Textarea as aX, type TextareaProps as aY, type TopBarLabels as aZ, type UiKitLabelOverrides as a_, type FieldHintProps as aa, FieldLabel as ab, type FieldLabelProps as ac, FileButton as ad, type FileButtonProps as ae, type FileLabels as af, type FilePickerLabels as ag, type FileRejection as ah, type FileRejectionReason as ai, type FileScreenOptions as aj, FloatingField as ak, type FloatingFieldProps as al, FormActions as am, type FormActionsAlign as an, type FormActionsDestructive as ao, type FormActionsLabels as ap, type FormActionsPlacement as aq, type FormActionsProps as ar, type FormActionsStickyWithin as as, type FormActionsSubmitProps as at, IconButton as au, type IconButtonGlyphSize as av, type IconButtonLinkProps as aw, type IconButtonProps as ax, type IconButtonSize as ay, type IconButtonTone as az, type ButtonLinkProps as b, UiKitProvider as b0, type UiKitProviderProps as b1, type UseFilePickerOptions as b2, type UseFilePickerReturn as b3, buttonClasses as b4, formatFileSize as b5, matchesAccept as b6, missingKitLabels as b7, resolvePasswordRevealLabels as b8, useFilePicker as b9, useKitChartTooltipPlacement as ba, useKitFileLabels as bb, useKitLabelOverrides as bc, useKitLabels as bd, useKitLink as be, useKitLocale as bf, useKitWeekStart as bg, type FilePickHandler as bh, formatAccept as bi, judgePick as bj, screenFiles as bk, summariseRejections as bl, type ButtonProps as c, type ButtonSize as d, type ButtonTone as e, type ButtonVariant as f, Card as g, CardAction as h, type CardActionProps as i, CardContent as j, type CardContentProps as k, type CardDensity as l, CardDescription as m, type CardDescriptionProps as n, CardFooter as o, type CardFooterProps as p, CardHeader as q, type CardHeaderProps as r, type CardProps as s, CardTitle as t, type CardTitleLevel as u, type CardTitleProps as v, type CardTone as w, type ChartTooltipPlacement as x, type ComboboxLabels as y, type CommonLabels as z };
2178
+ export { FIELD_BASE as $, type AppShellLabels as A, Button as B, type CalculatorLabels as C, CopyButton as D, type CopyButtonLabels as E, type CopyButtonProps as F, type CurrencyLabels as G, DEFAULT_APP_SHELL_LABELS as H, DEFAULT_CALCULATOR_LABELS as I, DEFAULT_COMBOBOX_LABELS as J, DEFAULT_COMMON_LABELS as K, DEFAULT_COPY_BUTTON_LABELS as L, DEFAULT_CURRENCY_LABELS as M, DEFAULT_DATE_PICKER_LABELS as N, DEFAULT_FILE_LABELS as O, DEFAULT_FILE_PICKER_LABELS as P, DEFAULT_FORM_ACTIONS_LABELS as Q, DEFAULT_MULTI_SELECT_LABELS as R, DEFAULT_PASSWORD_REVEAL_LABELS as S, DEFAULT_PICKER_SHEET_LABELS as T, DEFAULT_SHARE_CARD_LABELS as U, DEFAULT_SWIPEABLE_ROW_LABELS as V, DEFAULT_TABS_LABELS as W, DEFAULT_TOP_BAR_LABELS as X, type DatePickerLabels as Y, EmptyState as Z, type EmptyStateProps as _, type ButtonClassesOptions as a, type SharePendingGrant as a$, FIELD_DISPLAY as a0, FIELD_FLOATING_PAD as a1, FIELD_INVALID as a2, FIELD_TRIGGER as a3, FIELD_WRITABLE_LOOK as a4, FLOATING_INPUT_CLASS as a5, FLOATING_LABEL_CLASS as a6, FLOATING_LABEL_STATIC as a7, FieldChevron as a8, type FieldChevronProps as a9, type IconButtonTone as aA, type IconButtonToneColor as aB, type IconButtonVariant as aC, Input as aD, type InputProps as aE, type KitLinkComponent as aF, type KitLinkProps as aG, Label as aH, type LabelOverride as aI, type LabelProps as aJ, type MultiSelectLabels as aK, PHONE_QUERY as aL, type PasswordRevealLabels as aM, type PickerSheetLabels as aN, type ResponsiveFormActionsPlacement as aO, Select as aP, type SelectProps as aQ, type ShareAddRequest as aR, type ShareCandidate as aS, ShareCard as aT, type ShareCardLabels as aU, type ShareCardProps as aV, ShareDialog as aW, type ShareDialogProps as aX, type ShareGrantee as aY, SharePanel as aZ, type SharePanelProps as a_, FieldHint as aa, type FieldHintProps as ab, FieldLabel as ac, type FieldLabelProps as ad, FileButton as ae, type FileButtonProps as af, type FileLabels as ag, type FilePickerLabels as ah, type FileRejection as ai, type FileRejectionReason as aj, type FileScreenOptions as ak, FloatingField as al, type FloatingFieldProps as am, FormActions as an, type FormActionsAlign as ao, type FormActionsDestructive as ap, type FormActionsLabels as aq, type FormActionsPlacement as ar, type FormActionsProps as as, type FormActionsStickyWithin as at, type FormActionsSubmitProps as au, IconButton as av, type IconButtonGlyphSize as aw, type IconButtonLinkProps as ax, type IconButtonProps as ay, type IconButtonSize as az, type ButtonLinkProps as b, type ShareRole as b0, Spinner as b1, type SpinnerProps as b2, type SwipeableRowLabels as b3, type TabItem as b4, Tabs as b5, type TabsLabels as b6, type TabsProps as b7, Textarea as b8, type TextareaProps as b9, type TopBarLabels as ba, type UiKitLabelOverrides as bb, type UiKitLabels as bc, UiKitProvider as bd, type UiKitProviderProps as be, type UseFilePickerOptions as bf, type UseFilePickerReturn as bg, buttonClasses as bh, formatFileSize as bi, matchesAccept as bj, missingKitLabels as bk, resolvePasswordRevealLabels as bl, useFilePicker as bm, useKitChartTooltipPlacement as bn, useKitFileLabels as bo, useKitLabelOverrides as bp, useKitLabels as bq, useKitLink as br, useKitLocale as bs, useKitWeekStart as bt, type FilePickHandler as bu, formatAccept as bv, judgePick as bw, screenFiles as bx, summariseRejections as by, type ButtonProps as c, type ButtonSize as d, type ButtonTone as e, type ButtonVariant as f, Card as g, CardAction as h, type CardActionProps as i, CardContent as j, type CardContentProps as k, type CardDensity as l, CardDescription as m, type CardDescriptionProps as n, CardFooter as o, type CardFooterProps as p, CardHeader as q, type CardHeaderProps as r, type CardProps as s, CardTitle as t, type CardTitleLevel as u, type CardTitleProps as v, type CardTone as w, type ChartTooltipPlacement as x, type ComboboxLabels as y, type CommonLabels as z };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * What floats over the bottom of the screen right now, so a notice that comes and goes
3
+ * can sit above it instead of on it.
4
+ *
5
+ * kastlan 0.18: its offline pill is a `bottom-start` FloatingActionButton, and
6
+ * `ServerWakeNotice` sits in the same corner — right after a break both show at once
7
+ * (the photo outbox syncs while the first reads wake the server), one over the other.
8
+ * The FAB's `offset` cannot help: the notice comes and goes, and on a phone it is
9
+ * nearly as wide as the screen, so a FAB in EITHER corner can be under it.
10
+ *
11
+ * Measured, not declared: a FloatingActionButton registers its fixed element while it
12
+ * is mounted, and a notice asks {@link clearanceAbove} for the height it has to clear —
13
+ * whatever offset, `extended` label or `hidden` state the controls are in. A control
14
+ * that is `hidden` measures 0 × 0 and is skipped, so kastlan's pill, which stays
15
+ * mounted and hidden while there is nothing to report, costs the notice nothing.
16
+ *
17
+ * Internal: not exported from the barrel.
18
+ */
19
+ /** Track `element` until the returned function is called. Size changes — a label that
20
+ * grows, `hidden` toggling — notify the subscribers too. */
21
+ declare function registerFloating(element: HTMLElement): () => void;
22
+ /** Hear every registration, removal and size change. */
23
+ declare function subscribeFloating(listener: () => void): () => void;
24
+ /**
25
+ * The distance from the viewport's bottom edge to the top of the highest visible
26
+ * floating control that shares a column with `element` — what `element` has to clear —
27
+ * or `null` when nothing is under it.
28
+ */
29
+ declare function clearanceAbove(element: HTMLElement | null): number | null;
30
+
31
+ export { clearanceAbove, registerFloating, subscribeFloating };
@@ -0,0 +1,42 @@
1
+ "use client";
2
+ const occupants = /* @__PURE__ */ new Set();
3
+ const listeners = /* @__PURE__ */ new Set();
4
+ let observer = null;
5
+ function notify() {
6
+ for (const listener of listeners) listener();
7
+ }
8
+ function registerFloating(element) {
9
+ occupants.add(element);
10
+ if (observer === null && typeof ResizeObserver !== "undefined") observer = new ResizeObserver(notify);
11
+ observer?.observe(element);
12
+ notify();
13
+ return () => {
14
+ occupants.delete(element);
15
+ observer?.unobserve(element);
16
+ notify();
17
+ };
18
+ }
19
+ function subscribeFloating(listener) {
20
+ listeners.add(listener);
21
+ return () => {
22
+ listeners.delete(listener);
23
+ };
24
+ }
25
+ function clearanceAbove(element) {
26
+ if (!element) return null;
27
+ const own = element.getBoundingClientRect();
28
+ let top = Infinity;
29
+ for (const other of occupants) {
30
+ const box = other.getBoundingClientRect();
31
+ if (box.width === 0 || box.height === 0) continue;
32
+ if (box.right <= own.left || box.left >= own.right) continue;
33
+ top = Math.min(top, box.top);
34
+ }
35
+ return top === Infinity ? null : Math.max(0, window.innerHeight - top);
36
+ }
37
+ export {
38
+ clearanceAbove,
39
+ registerFloating,
40
+ subscribeFloating
41
+ };
42
+ //# sourceMappingURL=floating-stack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/floating-stack.ts"],"sourcesContent":["/**\n * What floats over the bottom of the screen right now, so a notice that comes and goes\n * can sit above it instead of on it.\n *\n * kastlan 0.18: its offline pill is a `bottom-start` FloatingActionButton, and\n * `ServerWakeNotice` sits in the same corner — right after a break both show at once\n * (the photo outbox syncs while the first reads wake the server), one over the other.\n * The FAB's `offset` cannot help: the notice comes and goes, and on a phone it is\n * nearly as wide as the screen, so a FAB in EITHER corner can be under it.\n *\n * Measured, not declared: a FloatingActionButton registers its fixed element while it\n * is mounted, and a notice asks {@link clearanceAbove} for the height it has to clear —\n * whatever offset, `extended` label or `hidden` state the controls are in. A control\n * that is `hidden` measures 0 × 0 and is skipped, so kastlan's pill, which stays\n * mounted and hidden while there is nothing to report, costs the notice nothing.\n *\n * Internal: not exported from the barrel.\n */\n\nconst occupants = new Set<HTMLElement>();\nconst listeners = new Set<() => void>();\nlet observer: ResizeObserver | null = null;\n\nfunction notify() {\n for (const listener of listeners) listener();\n}\n\n/** Track `element` until the returned function is called. Size changes — a label that\n * grows, `hidden` toggling — notify the subscribers too. */\nexport function registerFloating(element: HTMLElement): () => void {\n occupants.add(element);\n if (observer === null && typeof ResizeObserver !== \"undefined\") observer = new ResizeObserver(notify);\n observer?.observe(element);\n notify();\n return () => {\n occupants.delete(element);\n observer?.unobserve(element);\n notify();\n };\n}\n\n/** Hear every registration, removal and size change. */\nexport function subscribeFloating(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\n/**\n * The distance from the viewport's bottom edge to the top of the highest visible\n * floating control that shares a column with `element` — what `element` has to clear —\n * or `null` when nothing is under it.\n */\nexport function clearanceAbove(element: HTMLElement | null): number | null {\n if (!element) return null;\n const own = element.getBoundingClientRect();\n let top = Infinity;\n for (const other of occupants) {\n const box = other.getBoundingClientRect();\n if (box.width === 0 || box.height === 0) continue;\n if (box.right <= own.left || box.left >= own.right) continue;\n top = Math.min(top, box.top);\n }\n return top === Infinity ? null : Math.max(0, window.innerHeight - top);\n}\n"],"mappings":";AAmBA,MAAM,YAAY,oBAAI,IAAiB;AACvC,MAAM,YAAY,oBAAI,IAAgB;AACtC,IAAI,WAAkC;AAEtC,SAAS,SAAS;AAChB,aAAW,YAAY,UAAW,UAAS;AAC7C;AAIO,SAAS,iBAAiB,SAAkC;AACjE,YAAU,IAAI,OAAO;AACrB,MAAI,aAAa,QAAQ,OAAO,mBAAmB,YAAa,YAAW,IAAI,eAAe,MAAM;AACpG,YAAU,QAAQ,OAAO;AACzB,SAAO;AACP,SAAO,MAAM;AACX,cAAU,OAAO,OAAO;AACxB,cAAU,UAAU,OAAO;AAC3B,WAAO;AAAA,EACT;AACF;AAGO,SAAS,kBAAkB,UAAkC;AAClE,YAAU,IAAI,QAAQ;AACtB,SAAO,MAAM;AACX,cAAU,OAAO,QAAQ;AAAA,EAC3B;AACF;AAOO,SAAS,eAAe,SAA4C;AACzE,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,MAAM,QAAQ,sBAAsB;AAC1C,MAAI,MAAM;AACV,aAAW,SAAS,WAAW;AAC7B,UAAM,MAAM,MAAM,sBAAsB;AACxC,QAAI,IAAI,UAAU,KAAK,IAAI,WAAW,EAAG;AACzC,QAAI,IAAI,SAAS,IAAI,QAAQ,IAAI,QAAQ,IAAI,MAAO;AACpD,UAAM,KAAK,IAAI,KAAK,IAAI,GAAG;AAAA,EAC7B;AACA,SAAO,QAAQ,WAAW,OAAO,KAAK,IAAI,GAAG,OAAO,cAAc,GAAG;AACvE;","names":[]}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * "Is the server asleep, or is the app broken?" — the cold-start watchdog, lifted
3
+ * from keksdose (feedback #199) so kastlan and Kurvenschmiede get it too.
4
+ *
5
+ * All three backends run on Cloud Run with `--min-instances=0`: after an idle
6
+ * period the container is gone and the first request has to start it, which takes
7
+ * several seconds and occasionally more. Nothing in the UI said so, and none of the
8
+ * axios instances has a timeout, so the honest experience was a spinner that looked
9
+ * stuck. This counts the in-flight requests a cold start would actually delay and
10
+ * escalates in two steps, so a merely slow response says "still loading" and only a
11
+ * genuinely long wait blames the sleeping server.
12
+ *
13
+ * Framework-free on purpose (no zustand, no axios, no React): a counter, two timers
14
+ * and a listener set, read by `useServerWakeStage` through `useSyncExternalStore`.
15
+ * The adapters below take the app's axios instance / fetch as an ARGUMENT, so the
16
+ * kit never imports either.
17
+ *
18
+ * WHAT ARMS IT, and why the default is narrow:
19
+ * - reads (GET) — what every page does on open — always;
20
+ * - writes only when the app says so (`watchReadsAnd(/\/auth\/login\b/)`): the
21
+ * auth POSTs that gate the app are the only writes a user waits on before the
22
+ * app exists at all, so a cold start there looks exactly like a broken login.
23
+ * An UPLOAD must never be watched — a statement PDF or a photo takes seconds on a
24
+ * perfectly warm server, and "the server is waking up" during a 4 s upload would
25
+ * simply be false. With the default that holds by itself (an upload is a POST);
26
+ * an app widening `shouldWatch` keeps its upload routes out of it.
27
+ *
28
+ * Nor a DOWNLOAD, for the same reason (kastlan 0.18): axios answers only once the
29
+ * whole body is in, so a generated PDF or a large photo — a GET — ran past both
30
+ * thresholds on a warm server, and held the count for every read beside it. The
31
+ * default filter skips a request whose `responseType` is `"blob"`, `"arraybuffer"` or
32
+ * `"stream"`; a custom filter gets the request as its third argument to do the same.
33
+ * (`wrapFetch` ends the wait at the headers, so a download through it is harmless.)
34
+ *
35
+ * It never arms while the browser is offline, whatever `shouldWatch` says: that state
36
+ * has its own, truthful indicator, and a request that never left the device is not
37
+ * evidence of anything about the server.
38
+ */
39
+ /** `slow` = longer than a warm server ever takes; `waking` = long enough that a cold
40
+ * start is the only sensible explanation. */
41
+ type ServerWakeStage = "idle" | "slow" | "waking";
42
+ /** Decides which requests count. `method` arrives lower-cased (`"get"` when the
43
+ * request named none — axios's and fetch's default); `url` as the client had it;
44
+ * `request` is what the adapter saw — for axios the request config itself, so
45
+ * `responseType` or the app's own fields can decide. */
46
+ type ServerWakeFilter = (method: string, url: string, request: ServerWakeRequest) => boolean;
47
+ interface ServerWakeOptions {
48
+ /** Past this, a warm round trip is out of the question (a cold database query on a
49
+ * free tier can still take a second, so it is not tighter). Default 2000 ms. */
50
+ slowMs?: number;
51
+ /** Past this, the container was almost certainly scaled to zero. Default 7000 ms. */
52
+ wakingMs?: number;
53
+ /** Which requests count. Default {@link watchReadsAnd}`()` — GETs only, downloads
54
+ * excluded. The offline guard runs before it and cannot be overridden. */
55
+ shouldWatch?: ServerWakeFilter;
56
+ }
57
+ /** What a request is, for {@link ServerWakeWatcher.track} and a {@link ServerWakeFilter}. */
58
+ interface ServerWakeRequest {
59
+ method?: string;
60
+ url?: string;
61
+ /** axios's `responseType`. `"blob"`, `"arraybuffer"` and `"stream"` mark a download,
62
+ * which the default filter does not count. */
63
+ responseType?: string;
64
+ }
65
+ interface ServerWakeWatcher {
66
+ /**
67
+ * Count a request that is going out, and arm the escalation if it is the first.
68
+ * Returns whether it was counted — the caller MUST call {@link end} exactly once
69
+ * for every `true`, or the counter never returns to zero and the notice sticks.
70
+ * (The axios adapter stashes it on the request config for that reason.)
71
+ * `request` reaches the filter as its third argument; default `{ method, url }`.
72
+ */
73
+ start(method?: string, url?: string, request?: ServerWakeRequest): boolean;
74
+ /** A counted request settled — either way: an error still proves the server
75
+ * answered or gave up. When the last one does, the notice goes away. Never takes
76
+ * the counter below zero. */
77
+ end(): void;
78
+ /** `start` + `end` around a promise (or a function returning one), for a request
79
+ * that does not go through an adapter. Resolves and rejects as the work does. */
80
+ track<T>(work: PromiseLike<T> | (() => PromiseLike<T>), request?: ServerWakeRequest): Promise<T>;
81
+ /** Back to a pristine watchdog — for tests, which share an instance across cases. */
82
+ reset(): void;
83
+ /** `useSyncExternalStore`'s pair. Listeners hear stage changes only. */
84
+ subscribe(listener: () => void): () => void;
85
+ getStage(): ServerWakeStage;
86
+ /** Counted requests currently outstanding. */
87
+ getInFlight(): number;
88
+ }
89
+ /**
90
+ * The usual filter: every GET, plus any other request whose URL matches `writes` —
91
+ * keksdose's `/\/auth\/(login|register|demo-session)\b/` — but never a download
92
+ * (`responseType` `"blob"`, `"arraybuffer"` or `"stream"`). Keep upload routes out of
93
+ * the pattern (see the module comment).
94
+ */
95
+ declare function watchReadsAnd(writes?: RegExp): ServerWakeFilter;
96
+ declare function createServerWake({ slowMs, wakingMs, shouldWatch, }?: ServerWakeOptions): ServerWakeWatcher;
97
+ /**
98
+ * The shared instance: what `<ServerWakeNotice />`, `useServerWakeStage()`,
99
+ * {@link attachServerWake} and {@link wrapFetch} use when no watcher is passed. GETs
100
+ * only, default timings — an app that also watches its login POST creates its own
101
+ * with {@link createServerWake} and passes it to both ends.
102
+ */
103
+ declare const serverWake: ServerWakeWatcher;
104
+ /** The config fields the watchdog reads — what axios's request config has. The whole
105
+ * config reaches the filter, so an app's filter can read the rest of it. */
106
+ interface ServerWakeAxiosConfig {
107
+ method?: string;
108
+ url?: string;
109
+ responseType?: string;
110
+ }
111
+ /** The slice of an interceptor manager the adapter uses. A method (not a function
112
+ * property) so axios's wider `use(onFulfilled?, onRejected?, options?)` fits it. */
113
+ interface InterceptorManagerLike<V> {
114
+ use(onFulfilled: (value: V) => V | Promise<V>, onRejected: (error: unknown) => unknown): number;
115
+ eject(id: number): void;
116
+ }
117
+ /** An axios instance, structurally — so the kit types the adapter without importing
118
+ * axios. `axios.create()`'s result fits it as is. */
119
+ interface AxiosLikeInstance<C extends ServerWakeAxiosConfig, R extends {
120
+ config: C;
121
+ }> {
122
+ interceptors: {
123
+ request: InterceptorManagerLike<C>;
124
+ response: InterceptorManagerLike<R>;
125
+ };
126
+ }
127
+ /**
128
+ * Drive a watcher from an axios instance's interceptors: `attachServerWake(api)`.
129
+ * Returns a function that ejects both interceptors again.
130
+ *
131
+ * Attach it BEFORE the app's own response interceptors: axios runs those in the order
132
+ * they were added, so the wake one ends the wait before a 401 handler awaits a token
133
+ * refresh — otherwise the notice would keep counting a request that has already been
134
+ * answered. (Request-side order does not matter.)
135
+ */
136
+ declare function attachServerWake<C extends ServerWakeAxiosConfig, R extends {
137
+ config: C;
138
+ }>(instance: AxiosLikeInstance<C, R>, watcher?: ServerWakeWatcher): () => void;
139
+ /**
140
+ * A `fetch` that drives a watcher: `const apiFetch = wrapFetch(fetch)`. The wait ends
141
+ * when the response's HEADERS arrive (or the call fails) — the server is awake by then,
142
+ * however long the body takes.
143
+ */
144
+ declare function wrapFetch(fetchImpl?: typeof fetch, watcher?: ServerWakeWatcher): typeof fetch;
145
+
146
+ export { type AxiosLikeInstance, type ServerWakeAxiosConfig, type ServerWakeFilter, type ServerWakeOptions, type ServerWakeRequest, type ServerWakeStage, type ServerWakeWatcher, attachServerWake, createServerWake, serverWake, watchReadsAnd, wrapFetch };