@eifi1/ui-kit 0.15.3 → 0.15.5

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 (122) hide show
  1. package/README.md +11 -10
  2. package/dist/chart.d.ts +50 -1
  3. package/dist/components/alert-banner.js +1 -1
  4. package/dist/components/alert-banner.js.map +1 -1
  5. package/dist/components/amount-input.d.ts +5 -3
  6. package/dist/components/amount-input.js +6 -11
  7. package/dist/components/amount-input.js.map +1 -1
  8. package/dist/components/button-group.d.ts +2 -3
  9. package/dist/components/calculator.d.ts +2 -3
  10. package/dist/components/chart.d.ts +16 -1
  11. package/dist/components/chart.js +65 -37
  12. package/dist/components/chart.js.map +1 -1
  13. package/dist/components/checkbox.js +1 -1
  14. package/dist/components/checkbox.js.map +1 -1
  15. package/dist/components/chip.d.ts +8 -2
  16. package/dist/components/chip.js +12 -3
  17. package/dist/components/chip.js.map +1 -1
  18. package/dist/components/choice-card.js +1 -1
  19. package/dist/components/choice-card.js.map +1 -1
  20. package/dist/components/copy-button.d.ts +2 -3
  21. package/dist/components/error-boundary.js +1 -1
  22. package/dist/components/error-boundary.js.map +1 -1
  23. package/dist/components/facing-pair.d.ts +50 -0
  24. package/dist/components/file-button.d.ts +2 -3
  25. package/dist/components/file-dropzone.d.ts +2 -3
  26. package/dist/components/file-dropzone.js +1 -1
  27. package/dist/components/file-dropzone.js.map +1 -1
  28. package/dist/components/form-actions.d.ts +2 -3
  29. package/dist/components/form-actions.js +9 -1
  30. package/dist/components/form-actions.js.map +1 -1
  31. package/dist/components/money-field.d.ts +2 -3
  32. package/dist/components/number-field.d.ts +2 -3
  33. package/dist/components/number-input.d.ts +2 -3
  34. package/dist/components/number-input.js +17 -3
  35. package/dist/components/number-input.js.map +1 -1
  36. package/dist/components/numpad-sheet.d.ts +2 -3
  37. package/dist/components/series-chart.d.ts +78 -1
  38. package/dist/components/series-chart.js +74 -13
  39. package/dist/components/series-chart.js.map +1 -1
  40. package/dist/components/settings-fields.d.ts +2 -3
  41. package/dist/components/text-link.d.ts +2 -3
  42. package/dist/components/time-input.d.ts +2 -3
  43. package/dist/components/ui.d.ts +2 -3
  44. package/dist/components/ui.js +7 -7
  45. package/dist/components/ui.js.map +1 -1
  46. package/dist/feedback/feedback-attachment.d.ts +2 -82
  47. package/dist/feedback/feedback-attachment.js +271 -54
  48. package/dist/feedback/feedback-attachment.js.map +1 -1
  49. package/dist/feedback/feedback-dialog.d.ts +2 -105
  50. package/dist/feedback/feedback-dialog.js +56 -25
  51. package/dist/feedback/feedback-dialog.js.map +1 -1
  52. package/dist/feedback/feedback-inbox.d.ts +1 -1
  53. package/dist/feedback/feedback-thread.d.ts +2 -2
  54. package/dist/feedback-attachment-BYAHJMOr.d.ts +289 -0
  55. package/dist/feedback.d.ts +1 -2
  56. package/dist/hooks/use-file-drop.d.ts +2 -3
  57. package/dist/i18n/defaults.d.ts +2 -3
  58. package/dist/i18n/kit-labels.d.ts +2 -3
  59. package/dist/i18n/kit-labels.js +8 -2
  60. package/dist/i18n/kit-labels.js.map +1 -1
  61. package/dist/i18n/locales/de-CH-informal.d.ts +2 -3
  62. package/dist/i18n/locales/de-CH.d.ts +2 -3
  63. package/dist/i18n/locales/de-informal.d.ts +2 -3
  64. package/dist/i18n/locales/de.d.ts +2 -3
  65. package/dist/i18n/locales/de.js +5 -1
  66. package/dist/i18n/locales/de.js.map +1 -1
  67. package/dist/i18n/locales/es.d.ts +2 -3
  68. package/dist/i18n/locales/es.js +5 -1
  69. package/dist/i18n/locales/es.js.map +1 -1
  70. package/dist/i18n/locales/fr.d.ts +2 -3
  71. package/dist/i18n/locales/fr.js +5 -1
  72. package/dist/i18n/locales/fr.js.map +1 -1
  73. package/dist/i18n/locales/hu.d.ts +2 -3
  74. package/dist/i18n/locales/hu.js +5 -1
  75. package/dist/i18n/locales/hu.js.map +1 -1
  76. package/dist/i18n/locales/it.d.ts +2 -3
  77. package/dist/i18n/locales/it.js +5 -1
  78. package/dist/i18n/locales/it.js.map +1 -1
  79. package/dist/i18n/locales/zh.d.ts +2 -3
  80. package/dist/i18n/locales/zh.js +5 -1
  81. package/dist/i18n/locales/zh.js.map +1 -1
  82. package/dist/index.d.ts +4 -5
  83. package/dist/{kit-labels-BHfRBbHs.d.ts → kit-labels-BD2F7sOz.d.ts} +15 -5
  84. package/dist/lib/format.d.ts +15 -1
  85. package/dist/lib/format.js +16 -0
  86. package/dist/lib/format.js.map +1 -1
  87. package/dist/rhf/fields.d.ts +2 -3
  88. package/dist/rhf/form.d.ts +2 -3
  89. package/dist/rhf.d.ts +2 -3
  90. package/dist/shell/app-shell.d.ts +2 -3
  91. package/dist/shell/top-bar-brand.d.ts +2 -3
  92. package/dist/shell.d.ts +2 -3
  93. package/dist/theme/palette-derive.d.ts +3 -0
  94. package/dist/theme/palette-derive.js +7 -0
  95. package/dist/theme/palette-derive.js.map +1 -1
  96. package/dist/wizard/stepper-nav.d.ts +2 -3
  97. package/dist/wizard.d.ts +2 -3
  98. package/package.json +4 -2
  99. package/src/components/alert-banner.tsx +1 -1
  100. package/src/components/amount-input.tsx +16 -14
  101. package/src/components/chart.tsx +102 -41
  102. package/src/components/checkbox.tsx +1 -1
  103. package/src/components/chip.tsx +29 -4
  104. package/src/components/choice-card.tsx +1 -1
  105. package/src/components/error-boundary.tsx +1 -1
  106. package/src/components/file-dropzone.tsx +1 -1
  107. package/src/components/form-actions.tsx +9 -1
  108. package/src/components/number-input.tsx +22 -2
  109. package/src/components/series-chart.tsx +122 -20
  110. package/src/components/ui.tsx +7 -7
  111. package/src/feedback/feedback-attachment.tsx +441 -96
  112. package/src/feedback/feedback-dialog.tsx +147 -50
  113. package/src/i18n/kit-labels.tsx +19 -1
  114. package/src/i18n/locales/de.ts +4 -0
  115. package/src/i18n/locales/es.ts +4 -0
  116. package/src/i18n/locales/fr.ts +4 -0
  117. package/src/i18n/locales/hu.ts +4 -0
  118. package/src/i18n/locales/it.ts +4 -0
  119. package/src/i18n/locales/zh.ts +4 -0
  120. package/src/lib/format.ts +30 -0
  121. package/src/theme/palette-derive.ts +10 -0
  122. package/tokens.css +9 -1
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { commitExpression, evaluateExpression, formatResult, isBareAmount, looksLikeExpression, sanitizeLive, splitLeadingSign } from './lib/calc.js';
2
2
  export { cn } from './lib/cn.js';
3
3
  export { logger, setStoreLog } from './lib/logger.js';
4
- export { DateInput, EMPTY_FORMATTED_VALUE, FormatDateOptions, FormatDateStyle, FormatDigits, FormatMoneyOptions, FormatNumberBaseOptions, FormatNumberOptions, FormatPercentOptions, FormatRelativeTimeOptions, KitFormat, formatDate, formatMoney, formatNumber, formatPercent, formatRelativeTime, toDate, useKitFormat } from './lib/format.js';
4
+ export { DateInput, EMPTY_FORMATTED_VALUE, FormatDateOptions, FormatDateStyle, FormatDigits, FormatMoneyOptions, FormatNumberBaseOptions, FormatNumberOptions, FormatPercentOptions, FormatRelativeTimeOptions, KitFormat, currencyMinorDigits, formatDate, formatMoney, formatNumber, formatPercent, formatRelativeTime, roundToCurrency, toDate, useKitFormat } from './lib/format.js';
5
5
  export { readStored, writeStored } from './lib/safe-storage.js';
6
6
  export { useMediaQuery } from './hooks/use-media-query.js';
7
7
  export { useBodyScrollLock } from './hooks/use-body-scroll-lock.js';
@@ -25,7 +25,7 @@ export { AnchorRole, ChartRampReport, ContrastCheck, ContrastReport, DeriveOptio
25
25
  export { ALTERNATIVE_PRESETS, DEFAULT_PRESET, DERIVED_PRESETS, IMPRINT_PRESET, PALETTES, PalettePreset, TokenSet, applyTokenSet, presetById } from './theme/palette-presets.js';
26
26
  export { ThemeMode, ThemePreference, ThemeState, ThemeStore, applyPersistedTheme, createThemeStore } from './theme/theme-store.js';
27
27
  export { PaletteStore, applyPersistedPalette, createPaletteStore } from './theme/palette-store.js';
28
- export { A as AppShellLabels, B as Button, a as ButtonClassesOptions, b as ButtonLinkProps, c as ButtonProps, d as ButtonSize, e as ButtonTone, f as ButtonVariant, C as CalculatorLabels, g as Card, h as CardAction, i as CardActionProps, j as CardContent, k as CardContentProps, l as CardDescription, m as CardDescriptionProps, n as CardFooter, o as CardFooterProps, p as CardHeader, q as CardHeaderProps, r as CardProps, s as CardTitle, t as CardTitleProps, u as CardTone, v as ComboboxLabels, w as CommonLabels, x as CopyButton, y as CopyButtonLabels, z as CopyButtonProps, D as CurrencyLabels, E as DEFAULT_APP_SHELL_LABELS, F as DEFAULT_CALCULATOR_LABELS, G as DEFAULT_COMBOBOX_LABELS, H as DEFAULT_COMMON_LABELS, I as DEFAULT_COPY_BUTTON_LABELS, J as DEFAULT_CURRENCY_LABELS, K as DEFAULT_DATE_PICKER_LABELS, L as DEFAULT_FILE_LABELS, M as DEFAULT_FILE_PICKER_LABELS, N as DEFAULT_FORM_ACTIONS_LABELS, O as DEFAULT_MULTI_SELECT_LABELS, P as DEFAULT_PASSWORD_REVEAL_LABELS, Q as DEFAULT_PICKER_SHEET_LABELS, R as DEFAULT_SWIPEABLE_ROW_LABELS, S as DEFAULT_TABS_LABELS, T as DEFAULT_TOP_BAR_LABELS, U as DatePickerLabels, V as EmptyState, W as EmptyStateProps, X as FIELD_BASE, Y as FIELD_DISPLAY, Z as FIELD_FLOATING_PAD, _ as FIELD_INVALID, $ as FIELD_TRIGGER, a0 as FIELD_WRITABLE_LOOK, a1 as FLOATING_INPUT_CLASS, a2 as FLOATING_LABEL_CLASS, a3 as FLOATING_LABEL_STATIC, a4 as FieldChevron, a5 as FieldChevronProps, a6 as FieldHint, a7 as FieldHintProps, a8 as FieldLabel, a9 as FieldLabelProps, aa as FileButton, ab as FileButtonProps, ac as FileLabels, ad as FilePickerLabels, ae as FileRejection, af as FileRejectionReason, ag as FileScreenOptions, ah as FloatingField, ai as FloatingFieldProps, aj as FormActions, ak as FormActionsAlign, al as FormActionsDestructive, am as FormActionsLabels, an as FormActionsPlacement, ao as FormActionsProps, ap as IconButton, aq as IconButtonGlyphSize, ar as IconButtonLinkProps, as as IconButtonProps, at as IconButtonSize, au as IconButtonTone, av as IconButtonToneColor, aw as IconButtonVariant, ax as Input, ay as InputProps, az as KitLinkComponent, aA as KitLinkProps, aB as Label, aC as LabelOverride, aD as LabelProps, aE as MultiSelectLabels, aF as PHONE_QUERY, aG as PasswordRevealLabels, aH as PickerSheetLabels, aI as Select, aJ as SelectProps, aK as Spinner, aL as SpinnerProps, aM as SwipeableRowLabels, aN as TabItem, aO as Tabs, aP as TabsLabels, aQ as TabsProps, aR as Textarea, aS as TextareaProps, aT as TopBarLabels, aU as UiKitLabelOverrides, aV as UiKitLabels, aW as UiKitProvider, aX as UiKitProviderProps, aY as UseFilePickerOptions, aZ as UseFilePickerReturn, a_ as buttonClasses, a$ as formatFileSize, b0 as matchesAccept, b1 as missingKitLabels, b2 as resolvePasswordRevealLabels, b3 as useFilePicker, b4 as useKitFileLabels, b5 as useKitLabelOverrides, b6 as useKitLabels, b7 as useKitLink, b8 as useKitLocale, b9 as useKitWeekStart } from './kit-labels-BHfRBbHs.js';
28
+ export { A as AppShellLabels, B as Button, a as ButtonClassesOptions, b as ButtonLinkProps, c as ButtonProps, d as ButtonSize, e as ButtonTone, f as ButtonVariant, C as CalculatorLabels, g as Card, h as CardAction, i as CardActionProps, j as CardContent, k as CardContentProps, l as CardDescription, m as CardDescriptionProps, n as CardFooter, o as CardFooterProps, p as CardHeader, q as CardHeaderProps, r as CardProps, s as CardTitle, t as CardTitleProps, u as CardTone, v as ChartTooltipPlacement, w as ComboboxLabels, x as CommonLabels, y as CopyButton, z as CopyButtonLabels, D as CopyButtonProps, E as CurrencyLabels, F as DEFAULT_APP_SHELL_LABELS, G as DEFAULT_CALCULATOR_LABELS, H as DEFAULT_COMBOBOX_LABELS, I as DEFAULT_COMMON_LABELS, J as DEFAULT_COPY_BUTTON_LABELS, K as DEFAULT_CURRENCY_LABELS, L as DEFAULT_DATE_PICKER_LABELS, M as DEFAULT_FILE_LABELS, N as DEFAULT_FILE_PICKER_LABELS, O as DEFAULT_FORM_ACTIONS_LABELS, P as DEFAULT_MULTI_SELECT_LABELS, Q as DEFAULT_PASSWORD_REVEAL_LABELS, R as DEFAULT_PICKER_SHEET_LABELS, S as DEFAULT_SWIPEABLE_ROW_LABELS, T as DEFAULT_TABS_LABELS, U as DEFAULT_TOP_BAR_LABELS, V as DatePickerLabels, W as EmptyState, X as EmptyStateProps, Y as FIELD_BASE, Z as FIELD_DISPLAY, _ as FIELD_FLOATING_PAD, $ as FIELD_INVALID, a0 as FIELD_TRIGGER, a1 as FIELD_WRITABLE_LOOK, a2 as FLOATING_INPUT_CLASS, a3 as FLOATING_LABEL_CLASS, a4 as FLOATING_LABEL_STATIC, a5 as FieldChevron, a6 as FieldChevronProps, a7 as FieldHint, a8 as FieldHintProps, a9 as FieldLabel, aa as FieldLabelProps, ab as FileButton, ac as FileButtonProps, ad as FileLabels, ae as FilePickerLabels, af as FileRejection, ag as FileRejectionReason, ah as FileScreenOptions, ai as FloatingField, aj as FloatingFieldProps, ak as FormActions, al as FormActionsAlign, am as FormActionsDestructive, an as FormActionsLabels, ao as FormActionsPlacement, ap as FormActionsProps, aq as IconButton, ar as IconButtonGlyphSize, as as IconButtonLinkProps, at as IconButtonProps, au as IconButtonSize, av as IconButtonTone, aw as IconButtonToneColor, ax as IconButtonVariant, ay as Input, az as InputProps, aA as KitLinkComponent, aB as KitLinkProps, aC as Label, aD as LabelOverride, aE as LabelProps, aF as MultiSelectLabels, aG as PHONE_QUERY, aH as PasswordRevealLabels, aI as PickerSheetLabels, aJ as Select, aK as SelectProps, aL as Spinner, aM as SpinnerProps, aN as SwipeableRowLabels, aO as TabItem, aP as Tabs, aQ as TabsLabels, aR as TabsProps, aS as Textarea, aT as TextareaProps, aU as TopBarLabels, aV as UiKitLabelOverrides, aW as UiKitLabels, aX as UiKitProvider, aY as UiKitProviderProps, aZ as UseFilePickerOptions, a_ as UseFilePickerReturn, a$ as buttonClasses, b0 as formatFileSize, b1 as matchesAccept, b2 as missingKitLabels, b3 as resolvePasswordRevealLabels, b4 as useFilePicker, b5 as useKitChartTooltipPlacement, b6 as useKitFileLabels, b7 as useKitLabelOverrides, b8 as useKitLabels, b9 as useKitLink, ba as useKitLocale, bb as useKitWeekStart } from './kit-labels-BD2F7sOz.js';
29
29
  export { Field, FieldControlProps, FieldLabelVisibility, FieldProps, FieldRenderMeta } from './components/field.js';
30
30
  export { SearchField, SearchFieldProps } from './components/search-field.js';
31
31
  export { DropdownPanel, DropdownPanelProps, DropdownSearchHeader, DropdownSearchHeaderProps, useDropdown, useDropdownSearch } from './components/dropdown.js';
@@ -70,7 +70,7 @@ export { Autocomplete, AutocompleteProps } from './components/autocomplete.js';
70
70
  export { DEFAULT_MEASURED_GRID_LABELS, MeasuredGrid, MeasuredGridColumn, MeasuredGridLabels, MeasuredGridProps, MeasuredGridView, MeasuredRows, UseMeasuredRowsOptions, useMeasuredRows } from './components/measured-grid.js';
71
71
  export { WindowedRows, useWindowedRows } from './hooks/use-windowed-rows.js';
72
72
  export { Treemap, TreemapCell, TreemapCellProps, TreemapNode, TreemapProps, fitLabel } from './components/treemap.js';
73
- export { AXIS_TICK_WIDTH, AXIS_TITLE_STRIP, SeriesChart, SeriesChartAxis, SeriesChartHit, SeriesChartMarker, SeriesChartPoint, SeriesChartProps, SeriesChartReference, SeriesChartRow, SeriesChartSeries, SeriesChartSpan, SeriesChartTickValues, SeriesChartTone, SeriesChartTooltip, SeriesChartType, SeriesChartX, SeriesChartXTick, SeriesChartXValue, SeriesLegendAxes, SeriesSource, StaticSeriesChart, anchoredBand, axisBandWidth, mergeSeries, oneAxis, padBand, paddedDomain, seriesKey, seriesLegendEntries, soleSeriesColor, visibleSeries } from './components/series-chart.js';
73
+ export { AXIS_TICK_WIDTH, AXIS_TITLE_STRIP, SeriesChart, SeriesChartAxis, SeriesChartHit, SeriesChartMarker, SeriesChartPoint, SeriesChartProps, SeriesChartReference, SeriesChartRow, SeriesChartSeries, SeriesChartSpan, SeriesChartTickValues, SeriesChartTone, SeriesChartTooltip, SeriesChartTooltipPlacement, SeriesChartType, SeriesChartX, SeriesChartXTick, SeriesChartXValue, SeriesLegendAxes, SeriesSource, StaticSeriesChart, anchoredBand, axisBandWidth, mergeSeries, oneAxis, padBand, paddedDomain, resolveTooltipPlacement, seriesKey, seriesLegendEntries, soleSeriesColor, visibleSeries } from './components/series-chart.js';
74
74
  export { DEFAULT_Y_AXIS, NO_ZOOM, SharedXZoom, ZOOM_MIN_DRAG, ZOOM_SQUARE_ENOUGH, ZoomAxes, ZoomAxesSetting, ZoomBinding, ZoomDrag, ZoomFitSeries, ZoomFitSource, ZoomRow, ZoomSelection, ZoomState, ZoomTarget, axisExtent, defaultZoomAxes, fitXToY, fitYToX, selectionFromDrag, withChartZoom, zoomAfter, zoomAxesFor, zoomDomains } from './components/chart-zoom.js';
75
75
  export { LegendColumn, LegendEntry, LegendGroup, STEP_DASH, STROKE_PATTERNS, StaticLegend, StaticLegendProps, ToggleLegend, ToggleLegendProps, strokeDash, toggleHidden } from './components/toggle-legend.js';
76
76
  export { FACING_SIDES, FacingSide, facingAxes, facingBand, facingHeadingPad } from './components/facing-pair.js';
@@ -132,8 +132,7 @@ export { RoleSwitcher, RoleSwitcherProps } from './shell/role-switcher.js';
132
132
  export { TopBarActionMenu, TopBarMenuEntry, TopBarMenuHeader } from './shell/topbar-action-menu.js';
133
133
  export { TopBarBrand, TopBarBrandProps } from './shell/top-bar-brand.js';
134
134
  export { AuthLayout, AuthLayoutProps } from './shell/auth-layout.js';
135
- export { DEFAULT_ATTACHMENT_ACCEPT, DEFAULT_FEEDBACK_ATTACHMENT_LABELS, DEFAULT_MAX_ATTACHMENT_BYTES, FeedbackAttachmentField, FeedbackAttachmentFieldLabels, pastedName } from './feedback/feedback-attachment.js';
136
- export { DEFAULT_FEEDBACK_DIALOG_LABELS, FeedbackAttachmentLabels, FeedbackCategoryOption, FeedbackDialog, FeedbackDialogLabels, FeedbackDialogTextLabels, FeedbackSubmission } from './feedback/feedback-dialog.js';
135
+ export { D as DEFAULT_ATTACHMENT_ACCEPT, a as DEFAULT_FEEDBACK_ATTACHMENT_LABELS, b as DEFAULT_FEEDBACK_DIALOG_LABELS, c as DEFAULT_MAX_ATTACHMENTS, d as DEFAULT_MAX_ATTACHMENT_BYTES, F as FeedbackAttachmentError, e as FeedbackAttachmentField, f as FeedbackAttachmentFieldLabels, g as FeedbackAttachmentFieldMultipleProps, h as FeedbackAttachmentFieldProps, i as FeedbackAttachmentFieldSingleProps, j as FeedbackAttachmentLabels, k as FeedbackCategoryOption, l as FeedbackDialog, m as FeedbackDialogLabels, n as FeedbackDialogMultipleProps, o as FeedbackDialogProps, p as FeedbackDialogSingleProps, q as FeedbackDialogTextLabels, r as FeedbackMultipleSubmission, s as FeedbackSubmission, t as pastedName } from './feedback-attachment-BYAHJMOr.js';
137
136
  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';
138
137
  export { DEFAULT_FEEDBACK_COMPOSER_LABELS, DEFAULT_FEEDBACK_THREAD_LABELS, FeedbackComposer, FeedbackComposerAttachment, FeedbackComposerHandle, FeedbackComposerLabels, FeedbackComposerProps, FeedbackThread, FeedbackThreadAttachment, FeedbackThreadEvent, FeedbackThreadItem, FeedbackThreadLabels, FeedbackThreadMessage, FeedbackThreadProps } from './feedback/feedback-thread.js';
139
138
  export { DEFAULT_WIZARD_LABELS, FieldErrors, StepStatus, SummaryItem, SummarySection, UseWizardOptions, UseWizardReturn, ValidateResult, WizardLabels, WizardStepConfig, WizardUrlSync, resolveWizardLabels } from './wizard/types.js';
@@ -24,8 +24,7 @@ import { SwatchPickerLabels } from './components/swatch-picker.js';
24
24
  import { IconPickerLabels } from './components/icon-picker.js';
25
25
  import { DialogFrameLabels } from './components/dialog-frame.js';
26
26
  import { MeasuredGridLabels } from './components/measured-grid.js';
27
- import { FeedbackAttachmentFieldLabels } from './feedback/feedback-attachment.js';
28
- import { FeedbackDialogTextLabels } from './feedback/feedback-dialog.js';
27
+ import { f as FeedbackAttachmentFieldLabels, q as FeedbackDialogTextLabels } from './feedback-attachment-BYAHJMOr.js';
29
28
  import { FeedbackThreadLabels, FeedbackComposerLabels } from './feedback/feedback-thread.js';
30
29
  import { AccountSettingsLabels } from './components/account-settings-labels.js';
31
30
  import { ConfirmDialogLabels } from './components/confirm-dialog.js';
@@ -468,7 +467,7 @@ declare const FIELD_BASE: string;
468
467
  * cannot make the later one win. */
469
468
  declare const FIELD_WRITABLE_LOOK = "[&[readonly]]:bg-[var(--bg-surface)] [&[readonly]]:text-[var(--text-primary)]";
470
469
  declare const FIELD_FLOATING_PAD = "pt-4 pb-1";
471
- declare const FIELD_INVALID = "border-[var(--danger-border)] ring-1 ring-[var(--danger-border)] focus:border-[var(--danger)] focus:ring-[var(--danger)]";
470
+ declare const FIELD_INVALID = "border-[var(--danger-border-strong)] ring-1 ring-[var(--danger-border-strong)] focus:border-[var(--danger)] focus:ring-[var(--danger)]";
472
471
  declare const FLOATING_INPUT_CLASS: string;
473
472
  /** The phone breakpoint the display treatment below keys off — the same one the
474
473
  * numpad sheet uses, kept in one place so the two can't drift apart. */
@@ -1647,6 +1646,8 @@ declare const DEFAULT_FILE_LABELS: FileLabels;
1647
1646
  /** "12 kB", "3.4 MB" — `Intl`'s unit formatting, so the digits, the decimal mark and
1648
1647
  * the unit's spelling all follow `locale`. */
1649
1648
  declare function formatFileSize(bytes: number, locale?: string): string;
1649
+ /** Where a chart shows the values under the pointer — see `SeriesChartTooltip.placement`. */
1650
+ type ChartTooltipPlacement = "cursor" | "above" | "below" | "auto";
1650
1651
  /**
1651
1652
  * What every kit link receives: the anchor attributes the kit decided (href, class,
1652
1653
  * aria-current, handlers, children, ref). An app's router link maps them —
@@ -1700,6 +1701,13 @@ interface UiKitProviderProps {
1700
1701
  * hash-router `#/path` is handed to it like `/path` (see `pickLinkRenderer`).
1701
1702
  */
1702
1703
  linkComponent?: KitLinkComponent;
1704
+ /**
1705
+ * Where every `SeriesChart` below shows its tooltip when it does not say so itself
1706
+ * (`tooltip.placement`) — set once so an app's phone charts stop putting the box under
1707
+ * the reader's finger (keksdose #359). Default: `"cursor"`, the box that follows the
1708
+ * pointer, as before.
1709
+ */
1710
+ chartTooltipPlacement?: ChartTooltipPlacement;
1703
1711
  children: ReactNode;
1704
1712
  }
1705
1713
  /**
@@ -1710,7 +1718,7 @@ interface UiKitProviderProps {
1710
1718
  * it names, so a page can re-label one table's `dataTable.table` without restating
1711
1719
  * the language.
1712
1720
  */
1713
- declare function UiKitProvider({ labels, locale, weekStartsOn, linkComponent, children, }: UiKitProviderProps): react.JSX.Element;
1721
+ declare function UiKitProvider({ labels, locale, weekStartsOn, linkComponent, chartTooltipPlacement, children, }: UiKitProviderProps): react.JSX.Element;
1714
1722
  /** What the nearest provider says about one namespace — `undefined` outside one.
1715
1723
  * For a component whose own resolver does more than a merge (the data table
1716
1724
  * derives `columnsCount` from `columns`): feed `{ ...overrides, ...props }` to it. */
@@ -1730,6 +1738,8 @@ declare function useKitLocale(prop?: string): string | undefined;
1730
1738
  * win over it: `const Link = renderLink ?? useKitLink()`. */
1731
1739
  declare function useKitLink(): KitLinkComponent | undefined;
1732
1740
  declare function useKitWeekStart(): WeekDay | undefined;
1741
+ /** The provider's `chartTooltipPlacement`, or `undefined` — a chart's own prop wins. */
1742
+ declare function useKitChartTooltipPlacement(): ChartTooltipPlacement | undefined;
1733
1743
  /** {@link DEFAULT_FILE_LABELS}, but formatting in the provider's locale. */
1734
1744
  declare function useKitFileLabels(prop?: Partial<FileLabels>): FileLabels;
1735
1745
  /**
@@ -1745,4 +1755,4 @@ declare function useKitFileLabels(prop?: Partial<FileLabels>): FileLabels;
1745
1755
  */
1746
1756
  declare function missingKitLabels(labels: UiKitLabelOverrides | undefined, reference: UiKitLabels): string[];
1747
1757
 
1748
- export { FIELD_TRIGGER as $, type AppShellLabels as A, Button as B, type CalculatorLabels as C, type CurrencyLabels as D, DEFAULT_APP_SHELL_LABELS as E, DEFAULT_CALCULATOR_LABELS as F, DEFAULT_COMBOBOX_LABELS as G, DEFAULT_COMMON_LABELS as H, DEFAULT_COPY_BUTTON_LABELS as I, DEFAULT_CURRENCY_LABELS as J, DEFAULT_DATE_PICKER_LABELS as K, DEFAULT_FILE_LABELS as L, DEFAULT_FILE_PICKER_LABELS as M, DEFAULT_FORM_ACTIONS_LABELS as N, DEFAULT_MULTI_SELECT_LABELS as O, DEFAULT_PASSWORD_REVEAL_LABELS as P, DEFAULT_PICKER_SHEET_LABELS as Q, DEFAULT_SWIPEABLE_ROW_LABELS as R, DEFAULT_TABS_LABELS as S, DEFAULT_TOP_BAR_LABELS as T, type DatePickerLabels as U, EmptyState as V, type EmptyStateProps as W, FIELD_BASE as X, FIELD_DISPLAY as Y, FIELD_FLOATING_PAD as Z, FIELD_INVALID as _, type ButtonClassesOptions as a, formatFileSize as a$, FIELD_WRITABLE_LOOK as a0, FLOATING_INPUT_CLASS as a1, FLOATING_LABEL_CLASS as a2, FLOATING_LABEL_STATIC as a3, FieldChevron as a4, type FieldChevronProps as a5, FieldHint as a6, type FieldHintProps as a7, FieldLabel as a8, type FieldLabelProps as a9, type KitLinkProps as aA, Label as aB, type LabelOverride as aC, type LabelProps as aD, type MultiSelectLabels as aE, PHONE_QUERY as aF, type PasswordRevealLabels as aG, type PickerSheetLabels as aH, Select as aI, type SelectProps as aJ, Spinner as aK, type SpinnerProps as aL, type SwipeableRowLabels as aM, type TabItem as aN, Tabs as aO, type TabsLabels as aP, type TabsProps as aQ, Textarea as aR, type TextareaProps as aS, type TopBarLabels as aT, type UiKitLabelOverrides as aU, type UiKitLabels as aV, UiKitProvider as aW, type UiKitProviderProps as aX, type UseFilePickerOptions as aY, type UseFilePickerReturn as aZ, buttonClasses as a_, FileButton as aa, type FileButtonProps as ab, type FileLabels as ac, type FilePickerLabels as ad, type FileRejection as ae, type FileRejectionReason as af, type FileScreenOptions as ag, FloatingField as ah, type FloatingFieldProps as ai, FormActions as aj, type FormActionsAlign as ak, type FormActionsDestructive as al, type FormActionsLabels as am, type FormActionsPlacement as an, type FormActionsProps as ao, IconButton as ap, type IconButtonGlyphSize as aq, type IconButtonLinkProps as ar, type IconButtonProps as as, type IconButtonSize as at, type IconButtonTone as au, type IconButtonToneColor as av, type IconButtonVariant as aw, Input as ax, type InputProps as ay, type KitLinkComponent as az, type ButtonLinkProps as b, matchesAccept as b0, missingKitLabels as b1, resolvePasswordRevealLabels as b2, useFilePicker as b3, useKitFileLabels as b4, useKitLabelOverrides as b5, useKitLabels as b6, useKitLink as b7, useKitLocale as b8, useKitWeekStart as b9, type FilePickHandler as ba, formatAccept as bb, judgePick as bc, screenFiles as bd, summariseRejections as be, 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, CardDescription as l, type CardDescriptionProps as m, CardFooter as n, type CardFooterProps as o, CardHeader as p, type CardHeaderProps as q, type CardProps as r, CardTitle as s, type CardTitleProps as t, type CardTone as u, type ComboboxLabels as v, type CommonLabels as w, CopyButton as x, type CopyButtonLabels as y, type CopyButtonProps as z };
1758
+ export { FIELD_INVALID as $, type AppShellLabels as A, Button as B, type CalculatorLabels as C, type CopyButtonProps as D, type CurrencyLabels as E, DEFAULT_APP_SHELL_LABELS as F, DEFAULT_CALCULATOR_LABELS as G, DEFAULT_COMBOBOX_LABELS as H, DEFAULT_COMMON_LABELS as I, DEFAULT_COPY_BUTTON_LABELS as J, DEFAULT_CURRENCY_LABELS as K, DEFAULT_DATE_PICKER_LABELS as L, DEFAULT_FILE_LABELS as M, DEFAULT_FILE_PICKER_LABELS as N, DEFAULT_FORM_ACTIONS_LABELS as O, DEFAULT_MULTI_SELECT_LABELS as P, DEFAULT_PASSWORD_REVEAL_LABELS as Q, DEFAULT_PICKER_SHEET_LABELS as R, DEFAULT_SWIPEABLE_ROW_LABELS as S, DEFAULT_TABS_LABELS as T, DEFAULT_TOP_BAR_LABELS as U, type DatePickerLabels as V, EmptyState as W, type EmptyStateProps as X, FIELD_BASE as Y, FIELD_DISPLAY as Z, FIELD_FLOATING_PAD as _, type ButtonClassesOptions as a, buttonClasses as a$, FIELD_TRIGGER as a0, FIELD_WRITABLE_LOOK as a1, FLOATING_INPUT_CLASS as a2, FLOATING_LABEL_CLASS as a3, FLOATING_LABEL_STATIC as a4, FieldChevron as a5, type FieldChevronProps as a6, FieldHint as a7, type FieldHintProps as a8, FieldLabel as a9, type KitLinkComponent as aA, type KitLinkProps as aB, Label as aC, type LabelOverride as aD, type LabelProps as aE, type MultiSelectLabels as aF, PHONE_QUERY as aG, type PasswordRevealLabels as aH, type PickerSheetLabels as aI, Select as aJ, type SelectProps as aK, Spinner as aL, type SpinnerProps as aM, type SwipeableRowLabels as aN, type TabItem as aO, Tabs as aP, type TabsLabels as aQ, type TabsProps as aR, Textarea as aS, type TextareaProps as aT, type TopBarLabels as aU, type UiKitLabelOverrides as aV, type UiKitLabels as aW, UiKitProvider as aX, type UiKitProviderProps as aY, type UseFilePickerOptions as aZ, type UseFilePickerReturn as a_, type FieldLabelProps as aa, FileButton as ab, type FileButtonProps as ac, type FileLabels as ad, type FilePickerLabels as ae, type FileRejection as af, type FileRejectionReason as ag, type FileScreenOptions as ah, FloatingField as ai, type FloatingFieldProps as aj, FormActions as ak, type FormActionsAlign as al, type FormActionsDestructive as am, type FormActionsLabels as an, type FormActionsPlacement as ao, type FormActionsProps as ap, IconButton as aq, type IconButtonGlyphSize as ar, type IconButtonLinkProps as as, type IconButtonProps as at, type IconButtonSize as au, type IconButtonTone as av, type IconButtonToneColor as aw, type IconButtonVariant as ax, Input as ay, type InputProps as az, type ButtonLinkProps as b, formatFileSize as b0, matchesAccept as b1, missingKitLabels as b2, resolvePasswordRevealLabels as b3, useFilePicker as b4, useKitChartTooltipPlacement as b5, useKitFileLabels as b6, useKitLabelOverrides as b7, useKitLabels as b8, useKitLink as b9, useKitLocale as ba, useKitWeekStart as bb, type FilePickHandler as bc, formatAccept as bd, judgePick as be, screenFiles as bf, summariseRejections as bg, 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, CardDescription as l, type CardDescriptionProps as m, CardFooter as n, type CardFooterProps as o, CardHeader as p, type CardHeaderProps as q, type CardProps as r, CardTitle as s, type CardTitleProps as t, type CardTone as u, type ChartTooltipPlacement as v, type ComboboxLabels as w, type CommonLabels as x, CopyButton as y, type CopyButtonLabels as z };
@@ -158,5 +158,19 @@ interface KitFormat {
158
158
  * formatMoney(row.balance, "CHF");
159
159
  */
160
160
  declare function useKitFormat(locale?: string): KitFormat;
161
+ /**
162
+ * The decimals a currency is written with — its minor unit: CHF and EUR 2, JPY 0,
163
+ * KWD 3 — from `Intl`, or `undefined` for a code `Intl` does not accept. The rule
164
+ * AmountInput settles a typed figure by, for a host that needs it outside the field.
165
+ */
166
+ declare function currencyMinorDigits(currency: string | null | undefined): number | undefined;
167
+ /**
168
+ * `value` rounded to the currency's minor unit (or to `digits`), half away from zero,
169
+ * in decimal arithmetic: 93.4213 CHF → 93.42, 1.005 EUR → 1.01 (`Math.round(1.005 *
170
+ * 100)` is 100, since 1.005 is 1.00499… in binary). A non-finite value, or a currency
171
+ * `Intl` does not know without `digits`, comes back unchanged (keksdose live #356: an
172
+ * FX estimate set into an AmountInput showed four decimals).
173
+ */
174
+ declare function roundToCurrency(value: number, currency: string | null | undefined, digits?: number): number;
161
175
 
162
- export { type DateInput, EMPTY_FORMATTED_VALUE, type FormatDateOptions, type FormatDateStyle, type FormatDigits, type FormatMoneyOptions, type FormatNumberBaseOptions, type FormatNumberOptions, type FormatPercentOptions, type FormatRelativeTimeOptions, type KitFormat, formatDate, formatMoney, formatNumber, formatPercent, formatRelativeTime, toDate, useKitFormat };
176
+ export { type DateInput, EMPTY_FORMATTED_VALUE, type FormatDateOptions, type FormatDateStyle, type FormatDigits, type FormatMoneyOptions, type FormatNumberBaseOptions, type FormatNumberOptions, type FormatPercentOptions, type FormatRelativeTimeOptions, type KitFormat, currencyMinorDigits, formatDate, formatMoney, formatNumber, formatPercent, formatRelativeTime, roundToCurrency, toDate, useKitFormat };
@@ -138,13 +138,29 @@ function useKitFormat(locale) {
138
138
  [kitLocale]
139
139
  );
140
140
  }
141
+ function currencyMinorDigits(currency) {
142
+ if (!currency) return void 0;
143
+ try {
144
+ return new Intl.NumberFormat("en", { style: "currency", currency }).resolvedOptions().maximumFractionDigits;
145
+ } catch {
146
+ return void 0;
147
+ }
148
+ }
149
+ function roundToCurrency(value, currency, digits) {
150
+ const places = digits ?? currencyMinorDigits(currency);
151
+ if (places === void 0 || !Number.isFinite(value)) return value;
152
+ const magnitude = Number(`${Math.round(Number(`${Math.abs(value)}e${places}`))}e-${places}`);
153
+ return value < 0 ? -magnitude : magnitude;
154
+ }
141
155
  export {
142
156
  EMPTY_FORMATTED_VALUE,
157
+ currencyMinorDigits,
143
158
  formatDate,
144
159
  formatMoney,
145
160
  formatNumber,
146
161
  formatPercent,
147
162
  formatRelativeTime,
163
+ roundToCurrency,
148
164
  toDate,
149
165
  useKitFormat
150
166
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/format.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useKitLocale } from \"../i18n/kit-labels\";\nimport { parseIsoDate } from \"./dates\";\n\n/**\n * Number, money, percentage, date and relative-time formatting — `Intl`, in one place,\n * for the figures an app prints OUTSIDE a kit component.\n *\n * Each app grew its own: kastlan's `utils/formatters.ts` (a Swiss-pinned\n * `formatNumber`/`formatCurrency`, a date-fns `formatDate`, and a `formatRelativeTime`\n * whose \"5m ago\" / \"3h ago\" / \"2d ago\" stayed English in every language), its tenancy\n * `format.ts` (`formatChf`, a `formatPct` gluing a \"+\" onto `toFixed(2)`), keksdose's\n * own set. Every one of them is `Intl` with a locale bolted on, so the only thing\n * worth sharing is the locale — and the kit already has one, the provider's.\n *\n * The plain functions take a `locale` (none: the runtime default, as every `Intl` API\n * reads `undefined`). Inside a component, {@link useKitFormat} hands back the same\n * functions bound to the `<UiKitProvider locale>`, so a page formats exactly like the\n * kit components on it.\n *\n * A missing value — `null`, `undefined`, `NaN`, an unparseable date — formats as\n * `empty` (default \"—\", the dash `StatTile` shows for no value), never as \"NaN\" or\n * \"Invalid Date\".\n */\n\n/** What every formatter here prints for a missing value, unless told otherwise. */\nexport const EMPTY_FORMATTED_VALUE = \"—\";\n\n/** Fraction digits: a number pins both the minimum and the maximum (`2` → \"3.10\");\n * an object sets them apart (a meter reading, 1–3). */\nexport type FormatDigits = number | { min?: number; max?: number };\n\ninterface BaseOptions {\n /** BCP 47 tag. Left out: the runtime default (the hook: the provider's). */\n locale?: string;\n /** Printed for a missing value. Default {@link EMPTY_FORMATTED_VALUE}. */\n empty?: string;\n}\n\n/** The options every number formatter shares (money and percent carry their own unit). */\nexport interface FormatNumberBaseOptions extends BaseOptions {\n digits?: FormatDigits;\n /** Compact notation: 12 400 → \"12K\". */\n compact?: boolean;\n /** `\"exceptZero\"` for a change (\"+3\", \"−3\", \"0\"). Default: the minus only. */\n signDisplay?: Intl.NumberFormatOptions[\"signDisplay\"];\n /** Any further `Intl.NumberFormat` options, merged over the ones above. */\n options?: Intl.NumberFormatOptions;\n}\n\n/** The options {@link formatNumber} takes: the shared ones plus a free-text `unit`. */\nexport interface FormatNumberOptions extends FormatNumberBaseOptions {\n /**\n * A unit printed after the figure, joined by a narrow no-break space (U+202F):\n * \"3.400 N\", \"12 kg\", \"4,5 m²\". lenkbank (P8) prints forces, masses and lengths in\n * every table and summary, and each call site glued `${n} N` together with a plain\n * space — which wraps the \"N\" onto the next line in a narrow column and reads loose\n * beside the digit grouping. The narrow no-break space is the SI brochure's and the\n * Swiss typesetting rule's separator between value and unit, and it never breaks.\n *\n * Free text, not `Intl`'s `style: \"unit\"`: that one only knows the ECMA-402\n * sanctioned units (no \"N\", no \"kN\", no \"m³/h\"), so an app would still be gluing.\n * A missing value prints `empty` ALONE — \"— N\" says there is a newton-shaped\n * nothing, which is noise in a column that already heads its unit.\n */\n unit?: string;\n}\n\n/** Narrow no-break space (U+202F): between a figure and its {@link FormatNumberOptions.unit}. */\nconst UNIT_SEPARATOR = \"\\u202F\";\n\nexport interface FormatMoneyOptions extends FormatNumberBaseOptions {\n /** `\"symbol\"` (default), `\"narrowSymbol\"`, `\"code\"` (\"CHF 12.00\") or `\"name\"`. */\n currencyDisplay?: Intl.NumberFormatOptions[\"currencyDisplay\"];\n}\n\nexport interface FormatPercentOptions extends FormatNumberBaseOptions {\n /**\n * `true` (default): `value` is a RATIO, as `Intl` and `StatTile`'s percent delta read\n * it — `0.12` is 12 %. `false`: it is already a percentage — `12` is 12 %\n * (kastlan's rent `change_pct`).\n */\n ratio?: boolean;\n}\n\n// One formatter per option set: a table formats the same way thousands of times, and\n// constructing an Intl formatter is the expensive part.\nconst numberFormatters = new Map<string, Intl.NumberFormat>();\nfunction numberFormatter(locale: string | undefined, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n let f = numberFormatters.get(key);\n if (!f) {\n f = new Intl.NumberFormat(locale, options);\n numberFormatters.set(key, f);\n }\n return f;\n}\n\nfunction digitOptions(digits: FormatDigits | undefined): Intl.NumberFormatOptions {\n if (digits === undefined) return {};\n if (typeof digits === \"number\") return { minimumFractionDigits: digits, maximumFractionDigits: digits };\n return {\n ...(digits.min !== undefined ? { minimumFractionDigits: digits.min } : null),\n ...(digits.max !== undefined ? { maximumFractionDigits: digits.max } : null),\n };\n}\n\nfunction numberOptions(o: FormatNumberBaseOptions): Intl.NumberFormatOptions {\n return {\n ...digitOptions(o.digits),\n ...(o.compact ? { notation: \"compact\" } : null),\n ...(o.signDisplay ? { signDisplay: o.signDisplay } : null),\n ...o.options,\n };\n}\n\nfunction isMissing(value: number | null | undefined): value is null | undefined {\n // ±Infinity too (lenkbank, adopting 0.13): a division by zero upstream is a missing\n // figure, and \"∞\" printed in a money or measurement column reads like data.\n return value === null || value === undefined || !Number.isFinite(value);\n}\n\n/** A number in `locale`: \"1,234.5\", \"1.234,5\", \"1’234.5\" — with `unit`, \"3.400 N\". */\nexport function formatNumber(value: number | null | undefined, options: FormatNumberOptions = {}): string {\n if (isMissing(value)) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const figure = numberFormatter(options.locale, numberOptions(options)).format(value);\n return options.unit ? `${figure}${UNIT_SEPARATOR}${options.unit}` : figure;\n}\n\n/** An amount of `currency` (ISO 4217) in `locale`: \"CHF 1’234.50\", \"1.234,50 €\". */\nexport function formatMoney(\n value: number | null | undefined,\n currency: string,\n options: FormatMoneyOptions = {},\n): string {\n if (isMissing(value)) return options.empty ?? EMPTY_FORMATTED_VALUE;\n return numberFormatter(options.locale, {\n style: \"currency\",\n currency,\n ...(options.currencyDisplay ? { currencyDisplay: options.currencyDisplay } : null),\n ...numberOptions(options),\n }).format(value);\n}\n\n/** A percentage in `locale` (\"12%\", \"12 %\", \"+1.25%\"). See {@link FormatPercentOptions.ratio}\n * for what `value` means. Default digits: at most one. */\nexport function formatPercent(value: number | null | undefined, options: FormatPercentOptions = {}): string {\n if (isMissing(value)) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const ratio = options.ratio === false ? value / 100 : value;\n return numberFormatter(options.locale, {\n style: \"percent\",\n ...(options.digits === undefined ? { maximumFractionDigits: 1 } : null),\n ...numberOptions(options),\n }).format(ratio);\n}\n\n/* ── Dates ─────────────────────────────────────────────────────────────────── */\n\n/**\n * The shapes a date is printed in, each a set of `Intl.DateTimeFormat` options:\n * - `short` / `medium` / `long` / `full` — the date alone, at that `dateStyle`\n * (\"08/07/2026\", \"8 Jul 2026\", \"8 July 2026\", \"Wednesday, 8 July 2026\" in en-GB).\n * - `dateTime` — medium date and short time: a timestamp (\"8 Jul 2026, 14:05\").\n * - `time` — the short time alone (\"14:05\").\n * - `monthYear` — \"July 2026\".\n *\n * Or pass `Intl.DateTimeFormatOptions` for anything else. A date-fns PATTERN\n * (\"dd.MM.yyyy\") is deliberately not accepted: a pattern is one locale's order baked\n * into a string, which is how an English page ends up with German dates.\n */\nexport type FormatDateStyle = \"short\" | \"medium\" | \"long\" | \"full\" | \"dateTime\" | \"time\" | \"monthYear\";\n\nconst DATE_STYLES: Record<FormatDateStyle, Intl.DateTimeFormatOptions> = {\n short: { dateStyle: \"short\" },\n medium: { dateStyle: \"medium\" },\n long: { dateStyle: \"long\" },\n full: { dateStyle: \"full\" },\n dateTime: { dateStyle: \"medium\", timeStyle: \"short\" },\n time: { timeStyle: \"short\" },\n monthYear: { month: \"long\", year: \"numeric\" },\n};\n\nexport interface FormatDateOptions extends BaseOptions {\n /** An IANA zone (\"Europe/Zurich\") for a timestamp. Left out: the device's. */\n timeZone?: string;\n}\n\n/** What {@link formatDate} and {@link formatRelativeTime} accept. */\nexport type DateInput = string | number | Date | null | undefined;\n\n/**\n * `value` as a Date, or null. A bare \"YYYY-MM-DD\" is a CALENDAR day and is read on the\n * local calendar (`parseIsoDate`) — `new Date(\"2026-07-08\")` is UTC midnight, which is\n * the 7th anywhere west of Greenwich. Anything else (a full ISO timestamp, epoch\n * milliseconds, a Date) is an instant and goes through `Date` as is.\n */\nexport function toDate(value: DateInput): Date | null {\n if (value === null || value === undefined || value === \"\") return null;\n if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return parseIsoDate(value);\n const d = value instanceof Date ? value : new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n}\n\nconst dateFormatters = new Map<string, Intl.DateTimeFormat>();\nfunction dateFormatter(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n let f = dateFormatters.get(key);\n if (!f) {\n f = new Intl.DateTimeFormat(locale, options);\n dateFormatters.set(key, f);\n }\n return f;\n}\n\n/**\n * A date or timestamp in `locale`. Takes an ISO date (\"2026-07-08\", read on the local\n * calendar), an ISO timestamp (\"2026-07-08T14:05:00Z\"), epoch milliseconds or a Date.\n * `style` defaults to `medium`; see {@link FormatDateStyle}.\n *\n * `formatIsoDate` (`@eifi1/ui-kit/dates`) remains for a plain \"YYYY-MM-DD\" with an\n * explicit locale; this is its superset for timestamps and the named styles.\n */\nexport function formatDate(\n value: DateInput,\n style: FormatDateStyle | Intl.DateTimeFormatOptions = \"medium\",\n options: FormatDateOptions = {},\n): string {\n const d = toDate(value);\n if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const base = typeof style === \"string\" ? DATE_STYLES[style] : style;\n return dateFormatter(options.locale, options.timeZone ? { ...base, timeZone: options.timeZone } : base).format(d);\n}\n\nexport interface FormatRelativeTimeOptions extends BaseOptions {\n /** The moment it is measured from. Default: now. */\n now?: DateInput;\n /** `\"auto\"` (default): \"yesterday\", \"now\", \"next week\". `\"always\"`: \"1 day ago\". */\n numeric?: Intl.RelativeTimeFormatNumeric;\n /** `\"long\"` (default) \"3 hours ago\", `\"short\"` \"3 hr. ago\", `\"narrow\"` \"3h ago\". */\n style?: Intl.RelativeTimeFormatStyle;\n /**\n * Past this many days either way, print the date instead ({@link formatDate} in\n * `absoluteStyle`) — kastlan's feed switches to the date after a week, because \"5\n * weeks ago\" makes a reader do sums. Default: never.\n */\n absoluteAfterDays?: number;\n /** The style of that date. Default `medium`. */\n absoluteStyle?: FormatDateStyle | Intl.DateTimeFormatOptions;\n}\n\nconst SECOND = 1000;\nconst MINUTE = 60 * SECOND;\nconst HOUR = 60 * MINUTE;\nconst DAY = 24 * HOUR;\n\n/** [the largest |difference| a unit is used for, the unit, its length]. */\nconst RELATIVE_STEPS: Array<[number, Intl.RelativeTimeFormatUnit, number]> = [\n [45 * SECOND, \"second\", SECOND],\n [45 * MINUTE, \"minute\", MINUTE],\n [22 * HOUR, \"hour\", HOUR],\n [6.5 * DAY, \"day\", DAY],\n [26 * DAY, \"week\", 7 * DAY],\n [320 * DAY, \"month\", 30.44 * DAY],\n [Infinity, \"year\", 365.25 * DAY],\n];\n\nconst relativeFormatters = new Map<string, Intl.RelativeTimeFormat>();\nfunction relativeFormatter(\n locale: string | undefined,\n numeric: Intl.RelativeTimeFormatNumeric,\n style: Intl.RelativeTimeFormatStyle,\n): Intl.RelativeTimeFormat {\n const key = `${locale ?? \"\"}|${numeric}|${style}`;\n let f = relativeFormatters.get(key);\n if (!f) {\n f = new Intl.RelativeTimeFormat(locale, { numeric, style });\n relativeFormatters.set(key, f);\n }\n return f;\n}\n\n/**\n * How long ago (or until) `value` is, in `locale`'s words, through\n * `Intl.RelativeTimeFormat`: \"3 hours ago\", \"vor 3 Stunden\", \"il y a 3 heures\",\n * \"in 2 days\", and under 45 seconds \"now\". The unit is picked by size — seconds,\n * minutes, hours, days, weeks, months, years — and the count is rounded.\n */\nexport function formatRelativeTime(value: DateInput, options: FormatRelativeTimeOptions = {}): string {\n const d = toDate(value);\n if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const now = toDate(options.now ?? new Date()) ?? new Date();\n const diff = d.getTime() - now.getTime();\n const abs = Math.abs(diff);\n if (options.absoluteAfterDays !== undefined && abs > options.absoluteAfterDays * DAY) {\n return formatDate(d, options.absoluteStyle ?? \"medium\", options);\n }\n const rtf = relativeFormatter(options.locale, options.numeric ?? \"auto\", options.style ?? \"long\");\n const [, unit, size] = RELATIVE_STEPS.find(([limit]) => abs < limit)!;\n // Under 45 seconds is \"now\" rather than \"12 seconds ago\": a feed that ticks through\n // the seconds is noise, and nothing reads a timestamp to the second.\n const count = unit === \"second\" ? 0 : Math.round(diff / size);\n // `+ 0` folds -0 into 0: `format(-0, …)` says \"0 seconds ago\" instead of \"now\".\n return rtf.format(count + 0, unit);\n}\n\n/* ── Bound to the provider ─────────────────────────────────────────────────── */\n\ntype WithoutLocale<T> = Omit<T, \"locale\"> & { locale?: string };\n\n/** What {@link useKitFormat} returns: the formatters above with the provider's locale\n * as their default (an explicit `locale` in the options still wins). */\nexport interface KitFormat {\n /** The locale they format in — the provider's, or `undefined` (runtime default). */\n locale: string | undefined;\n formatNumber: (value: number | null | undefined, options?: WithoutLocale<FormatNumberOptions>) => string;\n formatMoney: (\n value: number | null | undefined,\n currency: string,\n options?: WithoutLocale<FormatMoneyOptions>,\n ) => string;\n formatPercent: (value: number | null | undefined, options?: WithoutLocale<FormatPercentOptions>) => string;\n formatDate: (\n value: DateInput,\n style?: FormatDateStyle | Intl.DateTimeFormatOptions,\n options?: WithoutLocale<FormatDateOptions>,\n ) => string;\n formatRelativeTime: (value: DateInput, options?: WithoutLocale<FormatRelativeTimeOptions>) => string;\n}\n\n/**\n * The formatters, bound to the `<UiKitProvider locale>` (or to `locale`, which wins\n * over it — the same precedence as a component's `locale` prop):\n *\n * const { formatMoney, formatRelativeTime } = useKitFormat();\n * formatMoney(row.balance, \"CHF\");\n */\nexport function useKitFormat(locale?: string): KitFormat {\n const kitLocale = useKitLocale(locale);\n return useMemo<KitFormat>(\n () => ({\n locale: kitLocale,\n formatNumber: (value, options) => formatNumber(value, { locale: kitLocale, ...options }),\n formatMoney: (value, currency, options) => formatMoney(value, currency, { locale: kitLocale, ...options }),\n formatPercent: (value, options) => formatPercent(value, { locale: kitLocale, ...options }),\n formatDate: (value, style, options) => formatDate(value, style, { locale: kitLocale, ...options }),\n formatRelativeTime: (value, options) => formatRelativeTime(value, { locale: kitLocale, ...options }),\n }),\n [kitLocale],\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/lib/format.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useKitLocale } from \"../i18n/kit-labels\";\nimport { parseIsoDate } from \"./dates\";\n\n/**\n * Number, money, percentage, date and relative-time formatting — `Intl`, in one place,\n * for the figures an app prints OUTSIDE a kit component.\n *\n * Each app grew its own: kastlan's `utils/formatters.ts` (a Swiss-pinned\n * `formatNumber`/`formatCurrency`, a date-fns `formatDate`, and a `formatRelativeTime`\n * whose \"5m ago\" / \"3h ago\" / \"2d ago\" stayed English in every language), its tenancy\n * `format.ts` (`formatChf`, a `formatPct` gluing a \"+\" onto `toFixed(2)`), keksdose's\n * own set. Every one of them is `Intl` with a locale bolted on, so the only thing\n * worth sharing is the locale — and the kit already has one, the provider's.\n *\n * The plain functions take a `locale` (none: the runtime default, as every `Intl` API\n * reads `undefined`). Inside a component, {@link useKitFormat} hands back the same\n * functions bound to the `<UiKitProvider locale>`, so a page formats exactly like the\n * kit components on it.\n *\n * A missing value — `null`, `undefined`, `NaN`, an unparseable date — formats as\n * `empty` (default \"—\", the dash `StatTile` shows for no value), never as \"NaN\" or\n * \"Invalid Date\".\n */\n\n/** What every formatter here prints for a missing value, unless told otherwise. */\nexport const EMPTY_FORMATTED_VALUE = \"—\";\n\n/** Fraction digits: a number pins both the minimum and the maximum (`2` → \"3.10\");\n * an object sets them apart (a meter reading, 1–3). */\nexport type FormatDigits = number | { min?: number; max?: number };\n\ninterface BaseOptions {\n /** BCP 47 tag. Left out: the runtime default (the hook: the provider's). */\n locale?: string;\n /** Printed for a missing value. Default {@link EMPTY_FORMATTED_VALUE}. */\n empty?: string;\n}\n\n/** The options every number formatter shares (money and percent carry their own unit). */\nexport interface FormatNumberBaseOptions extends BaseOptions {\n digits?: FormatDigits;\n /** Compact notation: 12 400 → \"12K\". */\n compact?: boolean;\n /** `\"exceptZero\"` for a change (\"+3\", \"−3\", \"0\"). Default: the minus only. */\n signDisplay?: Intl.NumberFormatOptions[\"signDisplay\"];\n /** Any further `Intl.NumberFormat` options, merged over the ones above. */\n options?: Intl.NumberFormatOptions;\n}\n\n/** The options {@link formatNumber} takes: the shared ones plus a free-text `unit`. */\nexport interface FormatNumberOptions extends FormatNumberBaseOptions {\n /**\n * A unit printed after the figure, joined by a narrow no-break space (U+202F):\n * \"3.400 N\", \"12 kg\", \"4,5 m²\". lenkbank (P8) prints forces, masses and lengths in\n * every table and summary, and each call site glued `${n} N` together with a plain\n * space — which wraps the \"N\" onto the next line in a narrow column and reads loose\n * beside the digit grouping. The narrow no-break space is the SI brochure's and the\n * Swiss typesetting rule's separator between value and unit, and it never breaks.\n *\n * Free text, not `Intl`'s `style: \"unit\"`: that one only knows the ECMA-402\n * sanctioned units (no \"N\", no \"kN\", no \"m³/h\"), so an app would still be gluing.\n * A missing value prints `empty` ALONE — \"— N\" says there is a newton-shaped\n * nothing, which is noise in a column that already heads its unit.\n */\n unit?: string;\n}\n\n/** Narrow no-break space (U+202F): between a figure and its {@link FormatNumberOptions.unit}. */\nconst UNIT_SEPARATOR = \"\\u202F\";\n\nexport interface FormatMoneyOptions extends FormatNumberBaseOptions {\n /** `\"symbol\"` (default), `\"narrowSymbol\"`, `\"code\"` (\"CHF 12.00\") or `\"name\"`. */\n currencyDisplay?: Intl.NumberFormatOptions[\"currencyDisplay\"];\n}\n\nexport interface FormatPercentOptions extends FormatNumberBaseOptions {\n /**\n * `true` (default): `value` is a RATIO, as `Intl` and `StatTile`'s percent delta read\n * it — `0.12` is 12 %. `false`: it is already a percentage — `12` is 12 %\n * (kastlan's rent `change_pct`).\n */\n ratio?: boolean;\n}\n\n// One formatter per option set: a table formats the same way thousands of times, and\n// constructing an Intl formatter is the expensive part.\nconst numberFormatters = new Map<string, Intl.NumberFormat>();\nfunction numberFormatter(locale: string | undefined, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n let f = numberFormatters.get(key);\n if (!f) {\n f = new Intl.NumberFormat(locale, options);\n numberFormatters.set(key, f);\n }\n return f;\n}\n\nfunction digitOptions(digits: FormatDigits | undefined): Intl.NumberFormatOptions {\n if (digits === undefined) return {};\n if (typeof digits === \"number\") return { minimumFractionDigits: digits, maximumFractionDigits: digits };\n return {\n ...(digits.min !== undefined ? { minimumFractionDigits: digits.min } : null),\n ...(digits.max !== undefined ? { maximumFractionDigits: digits.max } : null),\n };\n}\n\nfunction numberOptions(o: FormatNumberBaseOptions): Intl.NumberFormatOptions {\n return {\n ...digitOptions(o.digits),\n ...(o.compact ? { notation: \"compact\" } : null),\n ...(o.signDisplay ? { signDisplay: o.signDisplay } : null),\n ...o.options,\n };\n}\n\nfunction isMissing(value: number | null | undefined): value is null | undefined {\n // ±Infinity too (lenkbank, adopting 0.13): a division by zero upstream is a missing\n // figure, and \"∞\" printed in a money or measurement column reads like data.\n return value === null || value === undefined || !Number.isFinite(value);\n}\n\n/** A number in `locale`: \"1,234.5\", \"1.234,5\", \"1’234.5\" — with `unit`, \"3.400 N\". */\nexport function formatNumber(value: number | null | undefined, options: FormatNumberOptions = {}): string {\n if (isMissing(value)) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const figure = numberFormatter(options.locale, numberOptions(options)).format(value);\n return options.unit ? `${figure}${UNIT_SEPARATOR}${options.unit}` : figure;\n}\n\n/** An amount of `currency` (ISO 4217) in `locale`: \"CHF 1’234.50\", \"1.234,50 €\". */\nexport function formatMoney(\n value: number | null | undefined,\n currency: string,\n options: FormatMoneyOptions = {},\n): string {\n if (isMissing(value)) return options.empty ?? EMPTY_FORMATTED_VALUE;\n return numberFormatter(options.locale, {\n style: \"currency\",\n currency,\n ...(options.currencyDisplay ? { currencyDisplay: options.currencyDisplay } : null),\n ...numberOptions(options),\n }).format(value);\n}\n\n/** A percentage in `locale` (\"12%\", \"12 %\", \"+1.25%\"). See {@link FormatPercentOptions.ratio}\n * for what `value` means. Default digits: at most one. */\nexport function formatPercent(value: number | null | undefined, options: FormatPercentOptions = {}): string {\n if (isMissing(value)) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const ratio = options.ratio === false ? value / 100 : value;\n return numberFormatter(options.locale, {\n style: \"percent\",\n ...(options.digits === undefined ? { maximumFractionDigits: 1 } : null),\n ...numberOptions(options),\n }).format(ratio);\n}\n\n/* ── Dates ─────────────────────────────────────────────────────────────────── */\n\n/**\n * The shapes a date is printed in, each a set of `Intl.DateTimeFormat` options:\n * - `short` / `medium` / `long` / `full` — the date alone, at that `dateStyle`\n * (\"08/07/2026\", \"8 Jul 2026\", \"8 July 2026\", \"Wednesday, 8 July 2026\" in en-GB).\n * - `dateTime` — medium date and short time: a timestamp (\"8 Jul 2026, 14:05\").\n * - `time` — the short time alone (\"14:05\").\n * - `monthYear` — \"July 2026\".\n *\n * Or pass `Intl.DateTimeFormatOptions` for anything else. A date-fns PATTERN\n * (\"dd.MM.yyyy\") is deliberately not accepted: a pattern is one locale's order baked\n * into a string, which is how an English page ends up with German dates.\n */\nexport type FormatDateStyle = \"short\" | \"medium\" | \"long\" | \"full\" | \"dateTime\" | \"time\" | \"monthYear\";\n\nconst DATE_STYLES: Record<FormatDateStyle, Intl.DateTimeFormatOptions> = {\n short: { dateStyle: \"short\" },\n medium: { dateStyle: \"medium\" },\n long: { dateStyle: \"long\" },\n full: { dateStyle: \"full\" },\n dateTime: { dateStyle: \"medium\", timeStyle: \"short\" },\n time: { timeStyle: \"short\" },\n monthYear: { month: \"long\", year: \"numeric\" },\n};\n\nexport interface FormatDateOptions extends BaseOptions {\n /** An IANA zone (\"Europe/Zurich\") for a timestamp. Left out: the device's. */\n timeZone?: string;\n}\n\n/** What {@link formatDate} and {@link formatRelativeTime} accept. */\nexport type DateInput = string | number | Date | null | undefined;\n\n/**\n * `value` as a Date, or null. A bare \"YYYY-MM-DD\" is a CALENDAR day and is read on the\n * local calendar (`parseIsoDate`) — `new Date(\"2026-07-08\")` is UTC midnight, which is\n * the 7th anywhere west of Greenwich. Anything else (a full ISO timestamp, epoch\n * milliseconds, a Date) is an instant and goes through `Date` as is.\n */\nexport function toDate(value: DateInput): Date | null {\n if (value === null || value === undefined || value === \"\") return null;\n if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return parseIsoDate(value);\n const d = value instanceof Date ? value : new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n}\n\nconst dateFormatters = new Map<string, Intl.DateTimeFormat>();\nfunction dateFormatter(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n let f = dateFormatters.get(key);\n if (!f) {\n f = new Intl.DateTimeFormat(locale, options);\n dateFormatters.set(key, f);\n }\n return f;\n}\n\n/**\n * A date or timestamp in `locale`. Takes an ISO date (\"2026-07-08\", read on the local\n * calendar), an ISO timestamp (\"2026-07-08T14:05:00Z\"), epoch milliseconds or a Date.\n * `style` defaults to `medium`; see {@link FormatDateStyle}.\n *\n * `formatIsoDate` (`@eifi1/ui-kit/dates`) remains for a plain \"YYYY-MM-DD\" with an\n * explicit locale; this is its superset for timestamps and the named styles.\n */\nexport function formatDate(\n value: DateInput,\n style: FormatDateStyle | Intl.DateTimeFormatOptions = \"medium\",\n options: FormatDateOptions = {},\n): string {\n const d = toDate(value);\n if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const base = typeof style === \"string\" ? DATE_STYLES[style] : style;\n return dateFormatter(options.locale, options.timeZone ? { ...base, timeZone: options.timeZone } : base).format(d);\n}\n\nexport interface FormatRelativeTimeOptions extends BaseOptions {\n /** The moment it is measured from. Default: now. */\n now?: DateInput;\n /** `\"auto\"` (default): \"yesterday\", \"now\", \"next week\". `\"always\"`: \"1 day ago\". */\n numeric?: Intl.RelativeTimeFormatNumeric;\n /** `\"long\"` (default) \"3 hours ago\", `\"short\"` \"3 hr. ago\", `\"narrow\"` \"3h ago\". */\n style?: Intl.RelativeTimeFormatStyle;\n /**\n * Past this many days either way, print the date instead ({@link formatDate} in\n * `absoluteStyle`) — kastlan's feed switches to the date after a week, because \"5\n * weeks ago\" makes a reader do sums. Default: never.\n */\n absoluteAfterDays?: number;\n /** The style of that date. Default `medium`. */\n absoluteStyle?: FormatDateStyle | Intl.DateTimeFormatOptions;\n}\n\nconst SECOND = 1000;\nconst MINUTE = 60 * SECOND;\nconst HOUR = 60 * MINUTE;\nconst DAY = 24 * HOUR;\n\n/** [the largest |difference| a unit is used for, the unit, its length]. */\nconst RELATIVE_STEPS: Array<[number, Intl.RelativeTimeFormatUnit, number]> = [\n [45 * SECOND, \"second\", SECOND],\n [45 * MINUTE, \"minute\", MINUTE],\n [22 * HOUR, \"hour\", HOUR],\n [6.5 * DAY, \"day\", DAY],\n [26 * DAY, \"week\", 7 * DAY],\n [320 * DAY, \"month\", 30.44 * DAY],\n [Infinity, \"year\", 365.25 * DAY],\n];\n\nconst relativeFormatters = new Map<string, Intl.RelativeTimeFormat>();\nfunction relativeFormatter(\n locale: string | undefined,\n numeric: Intl.RelativeTimeFormatNumeric,\n style: Intl.RelativeTimeFormatStyle,\n): Intl.RelativeTimeFormat {\n const key = `${locale ?? \"\"}|${numeric}|${style}`;\n let f = relativeFormatters.get(key);\n if (!f) {\n f = new Intl.RelativeTimeFormat(locale, { numeric, style });\n relativeFormatters.set(key, f);\n }\n return f;\n}\n\n/**\n * How long ago (or until) `value` is, in `locale`'s words, through\n * `Intl.RelativeTimeFormat`: \"3 hours ago\", \"vor 3 Stunden\", \"il y a 3 heures\",\n * \"in 2 days\", and under 45 seconds \"now\". The unit is picked by size — seconds,\n * minutes, hours, days, weeks, months, years — and the count is rounded.\n */\nexport function formatRelativeTime(value: DateInput, options: FormatRelativeTimeOptions = {}): string {\n const d = toDate(value);\n if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const now = toDate(options.now ?? new Date()) ?? new Date();\n const diff = d.getTime() - now.getTime();\n const abs = Math.abs(diff);\n if (options.absoluteAfterDays !== undefined && abs > options.absoluteAfterDays * DAY) {\n return formatDate(d, options.absoluteStyle ?? \"medium\", options);\n }\n const rtf = relativeFormatter(options.locale, options.numeric ?? \"auto\", options.style ?? \"long\");\n const [, unit, size] = RELATIVE_STEPS.find(([limit]) => abs < limit)!;\n // Under 45 seconds is \"now\" rather than \"12 seconds ago\": a feed that ticks through\n // the seconds is noise, and nothing reads a timestamp to the second.\n const count = unit === \"second\" ? 0 : Math.round(diff / size);\n // `+ 0` folds -0 into 0: `format(-0, …)` says \"0 seconds ago\" instead of \"now\".\n return rtf.format(count + 0, unit);\n}\n\n/* ── Bound to the provider ─────────────────────────────────────────────────── */\n\ntype WithoutLocale<T> = Omit<T, \"locale\"> & { locale?: string };\n\n/** What {@link useKitFormat} returns: the formatters above with the provider's locale\n * as their default (an explicit `locale` in the options still wins). */\nexport interface KitFormat {\n /** The locale they format in — the provider's, or `undefined` (runtime default). */\n locale: string | undefined;\n formatNumber: (value: number | null | undefined, options?: WithoutLocale<FormatNumberOptions>) => string;\n formatMoney: (\n value: number | null | undefined,\n currency: string,\n options?: WithoutLocale<FormatMoneyOptions>,\n ) => string;\n formatPercent: (value: number | null | undefined, options?: WithoutLocale<FormatPercentOptions>) => string;\n formatDate: (\n value: DateInput,\n style?: FormatDateStyle | Intl.DateTimeFormatOptions,\n options?: WithoutLocale<FormatDateOptions>,\n ) => string;\n formatRelativeTime: (value: DateInput, options?: WithoutLocale<FormatRelativeTimeOptions>) => string;\n}\n\n/**\n * The formatters, bound to the `<UiKitProvider locale>` (or to `locale`, which wins\n * over it — the same precedence as a component's `locale` prop):\n *\n * const { formatMoney, formatRelativeTime } = useKitFormat();\n * formatMoney(row.balance, \"CHF\");\n */\nexport function useKitFormat(locale?: string): KitFormat {\n const kitLocale = useKitLocale(locale);\n return useMemo<KitFormat>(\n () => ({\n locale: kitLocale,\n formatNumber: (value, options) => formatNumber(value, { locale: kitLocale, ...options }),\n formatMoney: (value, currency, options) => formatMoney(value, currency, { locale: kitLocale, ...options }),\n formatPercent: (value, options) => formatPercent(value, { locale: kitLocale, ...options }),\n formatDate: (value, style, options) => formatDate(value, style, { locale: kitLocale, ...options }),\n formatRelativeTime: (value, options) => formatRelativeTime(value, { locale: kitLocale, ...options }),\n }),\n [kitLocale],\n );\n}\n\n/* ── Currency minor units ──────────────────────────────────────────────────── */\n\n/**\n * The decimals a currency is written with — its minor unit: CHF and EUR 2, JPY 0,\n * KWD 3 — from `Intl`, or `undefined` for a code `Intl` does not accept. The rule\n * AmountInput settles a typed figure by, for a host that needs it outside the field.\n */\nexport function currencyMinorDigits(currency: string | null | undefined): number | undefined {\n if (!currency) return undefined;\n try {\n return new Intl.NumberFormat(\"en\", { style: \"currency\", currency }).resolvedOptions().maximumFractionDigits;\n } catch {\n return undefined;\n }\n}\n\n/**\n * `value` rounded to the currency's minor unit (or to `digits`), half away from zero,\n * in decimal arithmetic: 93.4213 CHF → 93.42, 1.005 EUR → 1.01 (`Math.round(1.005 *\n * 100)` is 100, since 1.005 is 1.00499… in binary). A non-finite value, or a currency\n * `Intl` does not know without `digits`, comes back unchanged (keksdose live #356: an\n * FX estimate set into an AmountInput showed four decimals).\n */\nexport function roundToCurrency(value: number, currency: string | null | undefined, digits?: number): number {\n const places = digits ?? currencyMinorDigits(currency);\n if (places === undefined || !Number.isFinite(value)) return value;\n const magnitude = Number(`${Math.round(Number(`${Math.abs(value)}e${places}`))}e-${places}`);\n return value < 0 ? -magnitude : magnitude;\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ReactElement } from 'react';
3
3
  import { FieldValues, FieldPath, ControllerProps, ControllerRenderProps, ControllerFieldState } from 'react-hook-form';
4
- import { aJ as SelectProps, ay as InputProps, aS as TextareaProps } from '../kit-labels-BHfRBbHs.js';
4
+ import { aK as SelectProps, az as InputProps, aT as TextareaProps } from '../kit-labels-BD2F7sOz.js';
5
5
  import { NumberFieldProps } from '../components/number-field.js';
6
6
  import { DatePickerProps } from '../components/date-picker.js';
7
7
  import { CheckboxProps } from '../components/checkbox.js';
@@ -39,8 +39,7 @@ import '../components/dialog-frame.js';
39
39
  import '../components/modal.js';
40
40
  import '../hooks/use-search-param-state.js';
41
41
  import '../components/measured-grid.js';
42
- import '../feedback/feedback-attachment.js';
43
- import '../feedback/feedback-dialog.js';
42
+ import '../feedback-attachment-BYAHJMOr.js';
44
43
  import '../feedback/feedback-thread.js';
45
44
  import '../feedback/feedback-inbox.js';
46
45
  import '../components/account-settings-labels.js';
@@ -2,7 +2,7 @@ import * as react from 'react';
2
2
  import { ReactElement, ComponentProps } from 'react';
3
3
  import * as react_hook_form from 'react-hook-form';
4
4
  import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
5
- import { aD as LabelProps } from '../kit-labels-BHfRBbHs.js';
5
+ import { aE as LabelProps } from '../kit-labels-BD2F7sOz.js';
6
6
  import '../components/data-table-labels.js';
7
7
  import '../components/mini-calendar.js';
8
8
  import '../components/calendar-heatmap.js';
@@ -34,8 +34,7 @@ import '../components/dialog-frame.js';
34
34
  import '../components/modal.js';
35
35
  import '../hooks/use-search-param-state.js';
36
36
  import '../components/measured-grid.js';
37
- import '../feedback/feedback-attachment.js';
38
- import '../feedback/feedback-dialog.js';
37
+ import '../feedback-attachment-BYAHJMOr.js';
39
38
  import '../feedback/feedback-thread.js';
40
39
  import '../feedback/feedback-inbox.js';
41
40
  import '../components/account-settings-labels.js';
package/dist/rhf.d.ts CHANGED
@@ -4,7 +4,7 @@ export { RhfCheckbox, RhfCheckboxProps, RhfCombobox, RhfComboboxProps, RhfDateFi
4
4
  export { RhfLineItemCellContext, RhfLineItems, RhfLineItemsColumn, RhfLineItemsProps } from './rhf/line-items.js';
5
5
  import 'react';
6
6
  import 'react-hook-form';
7
- import './kit-labels-BHfRBbHs.js';
7
+ import './kit-labels-BD2F7sOz.js';
8
8
  import './components/data-table-labels.js';
9
9
  import './components/mini-calendar.js';
10
10
  import './components/calendar-heatmap.js';
@@ -36,8 +36,7 @@ import './components/dialog-frame.js';
36
36
  import './components/modal.js';
37
37
  import './hooks/use-search-param-state.js';
38
38
  import './components/measured-grid.js';
39
- import './feedback/feedback-attachment.js';
40
- import './feedback/feedback-dialog.js';
39
+ import './feedback-attachment-BYAHJMOr.js';
41
40
  import './feedback/feedback-thread.js';
42
41
  import './feedback/feedback-inbox.js';
43
42
  import './components/account-settings-labels.js';
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ComponentPropsWithoutRef, ReactNode } from 'react';
3
3
  import { LucideIcon } from 'lucide-react';
4
- import { az as KitLinkComponent } from '../kit-labels-BHfRBbHs.js';
4
+ import { aA as KitLinkComponent } from '../kit-labels-BD2F7sOz.js';
5
5
  import '../components/data-table-labels.js';
6
6
  import '../components/mini-calendar.js';
7
7
  import '../components/calendar-heatmap.js';
@@ -32,8 +32,7 @@ import '../components/dialog-frame.js';
32
32
  import '../components/modal.js';
33
33
  import '../hooks/use-search-param-state.js';
34
34
  import '../components/measured-grid.js';
35
- import '../feedback/feedback-attachment.js';
36
- import '../feedback/feedback-dialog.js';
35
+ import '../feedback-attachment-BYAHJMOr.js';
37
36
  import '../feedback/feedback-thread.js';
38
37
  import '../feedback/feedback-inbox.js';
39
38
  import '../components/account-settings-labels.js';
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { aA as KitLinkProps, az as KitLinkComponent } from '../kit-labels-BHfRBbHs.js';
3
+ import { aB as KitLinkProps, aA as KitLinkComponent } from '../kit-labels-BD2F7sOz.js';
4
4
  import '../components/data-table-labels.js';
5
5
  import '../components/mini-calendar.js';
6
6
  import '../components/calendar-heatmap.js';
@@ -32,8 +32,7 @@ import '../components/dialog-frame.js';
32
32
  import '../components/modal.js';
33
33
  import '../hooks/use-search-param-state.js';
34
34
  import '../components/measured-grid.js';
35
- import '../feedback/feedback-attachment.js';
36
- import '../feedback/feedback-dialog.js';
35
+ import '../feedback-attachment-BYAHJMOr.js';
37
36
  import '../feedback/feedback-thread.js';
38
37
  import '../feedback/feedback-inbox.js';
39
38
  import '../components/account-settings-labels.js';
package/dist/shell.d.ts CHANGED
@@ -13,7 +13,7 @@ import './theme/theme-store.js';
13
13
  import 'zustand/middleware';
14
14
  import 'zustand';
15
15
  import 'lucide-react';
16
- import './kit-labels-BHfRBbHs.js';
16
+ import './kit-labels-BD2F7sOz.js';
17
17
  import './components/data-table-labels.js';
18
18
  import './components/mini-calendar.js';
19
19
  import './components/calendar-heatmap.js';
@@ -44,8 +44,7 @@ import './components/dialog-frame.js';
44
44
  import './components/modal.js';
45
45
  import './hooks/use-search-param-state.js';
46
46
  import './components/measured-grid.js';
47
- import './feedback/feedback-attachment.js';
48
- import './feedback/feedback-dialog.js';
47
+ import './feedback-attachment-BYAHJMOr.js';
49
48
  import './feedback/feedback-thread.js';
50
49
  import './feedback/feedback-inbox.js';
51
50
  import './components/account-settings-labels.js';
@@ -53,6 +53,9 @@ interface SemanticTokens {
53
53
  dangerHover: string;
54
54
  dangerContrast: string;
55
55
  dangerBorder: string;
56
+ /** The error line (invalid field, danger banner, destructive focus ring): as loud as
57
+ * `dangerBorder` was before 0.15.5, so it keeps 3:1 against the surface. */
58
+ dangerBorderStrong: string;
56
59
  dangerBg: string;
57
60
  warning: string;
58
61
  warningBorder: string;
@@ -180,7 +180,14 @@ function derivePalette({
180
180
  l: mode === "light" ? dangerLch.l - 0.06 : dangerLch.l + 0.06
181
181
  }),
182
182
  dangerContrast: contrastOn(danger),
183
+ // The tone border follows its siblings' lightness in dark mode (warning 0.44, info
184
+ // and success 0.45) — a soft frame; the strong line keeps the old, louder recipe.
183
185
  dangerBorder: oklchToHex({
186
+ l: mode === "light" ? dangerLch.l + 0.22 : 0.45,
187
+ c: semanticChroma.danger,
188
+ h: semanticHue.danger
189
+ }),
190
+ dangerBorderStrong: oklchToHex({
184
191
  l: mode === "light" ? dangerLch.l + 0.22 : dangerLch.l - 0.22,
185
192
  c: semanticChroma.danger,
186
193
  h: semanticHue.danger