@eifi1/ui-kit 0.52.0 → 0.54.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.
- package/README.md +52 -59
- package/dist/account/delete-account-setting.d.ts +3 -2
- package/dist/account/delete-account-setting.js.map +1 -1
- package/dist/auth/auth-errors.d.ts +36 -0
- package/dist/auth/auth-errors.js +20 -0
- package/dist/auth/auth-errors.js.map +1 -1
- package/dist/billing/cancel-subscription.d.ts +5 -14
- package/dist/billing/cancel-subscription.js +1 -3
- package/dist/billing/cancel-subscription.js.map +1 -1
- package/dist/billing/plan-card.d.ts +5 -13
- package/dist/billing/plan-card.js +3 -11
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/billing/plan-switch.d.ts +5 -14
- package/dist/billing/plan-switch.js +1 -3
- package/dist/billing/plan-switch.js.map +1 -1
- package/dist/chart.d.ts +3 -14
- package/dist/chart.js +64 -11
- package/dist/chart.js.map +1 -1
- package/dist/components/amount-input.d.ts +2 -1
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/calendar-heatmap.js +4 -2
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/currency-option-list.d.ts +2 -1
- package/dist/components/currency-option-list.js +10 -3
- package/dist/components/currency-option-list.js.map +1 -1
- package/dist/components/currency-select.d.ts +3 -1
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +22 -1
- package/dist/components/danger-confirm.js +50 -28
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-filter-popover.d.ts +2 -1
- package/dist/components/data-table-filter-popover.js +3 -2
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +33 -3
- package/dist/components/data-table-filters.js +6 -4
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/date-field.d.ts +1 -1
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.d.ts +1 -1
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.d.ts +4 -17
- package/dist/components/date-range-picker.js +2 -2
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +3 -1
- package/dist/components/dialog-frame.js +6 -2
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/mini-calendar.js +3 -2
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +27 -0
- package/dist/components/modal.js +5 -3
- package/dist/components/modal.js.map +1 -1
- package/dist/components/nav-pills.d.ts +1 -1
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/series-chart-axes.d.ts +1 -1
- package/dist/components/series-chart-axes.js.map +1 -1
- package/dist/components/series-chart.d.ts +64 -19
- package/dist/components/series-chart.js +2 -1
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/stat-tile.d.ts +21 -2
- package/dist/components/stat-tile.js +4 -2
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/treemap-labels.d.ts +6 -1
- package/dist/components/treemap-labels.js +2 -2
- package/dist/components/treemap-labels.js.map +1 -1
- package/dist/components/treemap.d.ts +29 -4
- package/dist/components/treemap.js +22 -9
- package/dist/components/treemap.js.map +1 -1
- package/dist/currencies.d.ts +16 -2
- package/dist/currencies.js +2 -1
- package/dist/currencies.js.map +1 -1
- package/dist/data-table.d.ts +1 -6
- package/dist/data-table.js +1 -9
- package/dist/data-table.js.map +1 -1
- package/dist/demo/demo-countdown.d.ts +0 -5
- package/dist/demo/demo-countdown.js +0 -4
- package/dist/demo/demo-countdown.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-context-labels.js +2 -1
- package/dist/feedback/feedback-context-labels.js.map +1 -1
- package/dist/feedback/feedback-context.d.ts +19 -3
- package/dist/feedback/feedback-context.js +12 -1
- package/dist/feedback/feedback-context.js.map +1 -1
- package/dist/feedback/feedback-diagnostics.d.ts +17 -0
- package/dist/feedback/feedback-diagnostics.js +27 -0
- package/dist/feedback/feedback-diagnostics.js.map +1 -0
- package/dist/feedback/feedback-dialog.d.ts +10 -0
- package/dist/feedback/feedback-dialog.js +6 -4
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-mutation.d.ts +11 -23
- package/dist/feedback/feedback-mutation.js +1 -9
- package/dist/feedback/feedback-mutation.js.map +1 -1
- package/dist/feedback/feedback-public-list.d.ts +1 -6
- package/dist/feedback/feedback-public-list.js +1 -2
- package/dist/feedback/feedback-public-list.js.map +1 -1
- package/dist/feedback/feedback-record.d.ts +5 -0
- package/dist/feedback/feedback-record.js.map +1 -1
- package/dist/feedback/feedback-row-detail-labels.js +1 -8
- package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +6 -50
- package/dist/feedback/feedback-row-detail.js +3 -8
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +3 -19
- package/dist/feedback/feedback-status-undo.js.map +1 -1
- package/dist/feedback/feedback-submit-dialog.d.ts +5 -1
- package/dist/feedback/feedback-submit-dialog.js +15 -3
- package/dist/feedback/feedback-submit-dialog.js.map +1 -1
- package/dist/feedback/feedback-submit.d.ts +30 -2
- package/dist/feedback/feedback-submit.js +5 -2
- package/dist/feedback/feedback-submit.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +0 -9
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/feedback.d.ts +6 -40
- package/dist/feedback.js +88 -46
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-hotkey.js +11 -1
- package/dist/hooks/use-hotkey.js.map +1 -1
- package/dist/i18n/defaults.js +0 -4
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +5 -20
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +5 -14
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +5 -31
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +5 -20
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +5 -20
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +5 -20
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +5 -21
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +5 -20
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +9 -4
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +0 -1
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +18 -67
- package/dist/index.js +28 -103
- package/dist/index.js.map +1 -1
- package/dist/lib/dates.d.ts +7 -1
- package/dist/lib/dates.js +8 -1
- package/dist/lib/dates.js.map +1 -1
- package/dist/lib/format.d.ts +37 -2
- package/dist/lib/format.js +25 -1
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/intl-cache.d.ts +4 -2
- package/dist/lib/intl-cache.js.map +1 -1
- package/dist/lib/translation-review.d.ts +26 -0
- package/dist/lib/translation-review.js +14 -0
- package/dist/lib/translation-review.js.map +1 -1
- package/dist/rhf/fields.d.ts +2 -2
- package/dist/rhf/fields.js.map +1 -1
- package/dist/search/search-index.d.ts +0 -6
- package/dist/search/search-index.js +0 -4
- package/dist/search/search-index.js.map +1 -1
- package/dist/search.d.ts +1 -8
- package/dist/search.js +2 -7
- package/dist/search.js.map +1 -1
- package/dist/shell/account-menu-language.d.ts +7 -21
- package/dist/shell/account-menu-language.js +1 -3
- package/dist/shell/account-menu-language.js.map +1 -1
- package/dist/shell/account-menu-text-size.d.ts +9 -23
- package/dist/shell/account-menu-text-size.js +2 -3
- package/dist/shell/account-menu-text-size.js.map +1 -1
- package/dist/shell/app-shell.d.ts +0 -10
- package/dist/shell/app-shell.js +1 -7
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/company-switcher.d.ts +1 -1
- package/dist/shell/company-switcher.js.map +1 -1
- package/dist/shell/dev-menu.d.ts +1 -1
- package/dist/shell/dev-menu.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.d.ts +1 -1
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell.d.ts +7 -29
- package/dist/shell.js +12 -42
- package/dist/shell.js.map +1 -1
- package/dist/sync.d.ts +2 -6
- package/dist/sync.js +2 -15
- package/dist/sync.js.map +1 -1
- package/dist/theme/chart-palette.d.ts +0 -5
- package/dist/theme/chart-palette.js +0 -2
- package/dist/theme/chart-palette.js.map +1 -1
- package/package.json +1 -1
- package/src/account/delete-account-setting.tsx +3 -2
- package/src/auth/auth-errors.ts +69 -0
- package/src/billing/cancel-subscription.tsx +5 -16
- package/src/billing/plan-card.tsx +7 -24
- package/src/billing/plan-switch.tsx +5 -16
- package/src/chart.ts +62 -13
- package/src/components/amount-input.tsx +2 -1
- package/src/components/calendar-heatmap.tsx +8 -2
- package/src/components/currency-option-list.tsx +12 -4
- package/src/components/currency-select.tsx +3 -1
- package/src/components/danger-confirm.tsx +80 -27
- package/src/components/data-table-filter-popover.tsx +19 -11
- package/src/components/data-table-filters.ts +36 -8
- package/src/components/date-field.tsx +1 -1
- package/src/components/date-picker.tsx +0 -1
- package/src/components/date-range-picker.tsx +6 -22
- package/src/components/dialog-frame.tsx +12 -4
- package/src/components/mini-calendar.tsx +5 -2
- package/src/components/modal.tsx +36 -7
- package/src/components/nav-pills.tsx +1 -1
- package/src/components/number-input.tsx +4 -3
- package/src/components/series-chart-axes.ts +6 -1
- package/src/components/series-chart.tsx +78 -21
- package/src/components/stat-tile.tsx +32 -3
- package/src/components/treemap-labels.ts +11 -3
- package/src/components/treemap.tsx +65 -12
- package/src/currencies.ts +23 -3
- package/src/data-table.ts +1 -11
- package/src/demo/demo-countdown.ts +3 -8
- package/src/feedback/feedback-context-labels.ts +1 -0
- package/src/feedback/feedback-context.tsx +34 -2
- package/src/feedback/feedback-diagnostics.tsx +41 -0
- package/src/feedback/feedback-dialog.tsx +16 -4
- package/src/feedback/feedback-mutation.ts +12 -33
- package/src/feedback/feedback-public-list.tsx +1 -7
- package/src/feedback/feedback-record.ts +5 -0
- package/src/feedback/feedback-row-detail-labels.ts +1 -8
- package/src/feedback/feedback-row-detail.tsx +21 -58
- package/src/feedback/feedback-status-undo.ts +4 -18
- package/src/feedback/feedback-submit-dialog.tsx +15 -2
- package/src/feedback/feedback-submit.tsx +36 -3
- package/src/feedback/feedback-thread.tsx +0 -10
- package/src/feedback.ts +89 -48
- package/src/hooks/use-hotkey.ts +24 -1
- package/src/i18n/defaults.ts +0 -4
- package/src/i18n/german.ts +4 -19
- package/src/i18n/kit-labels.tsx +5 -14
- package/src/i18n/locales/es.ts +4 -19
- package/src/i18n/locales/fr.ts +4 -19
- package/src/i18n/locales/hu.ts +4 -19
- package/src/i18n/locales/it.ts +4 -20
- package/src/i18n/locales/zh.ts +4 -19
- package/src/i18n/review.ts +9 -4
- package/src/i18n/shared-labels.ts +0 -1
- package/src/index.ts +44 -114
- package/src/lib/dates.ts +24 -2
- package/src/lib/format.ts +66 -3
- package/src/lib/intl-cache.ts +4 -2
- package/src/lib/translation-review.ts +52 -0
- package/src/rhf/fields.tsx +2 -2
- package/src/search/search-index.ts +0 -15
- package/src/search.ts +8 -8
- package/src/shell/account-menu-language.tsx +7 -27
- package/src/shell/account-menu-text-size.tsx +13 -32
- package/src/shell/app-shell.tsx +0 -13
- package/src/shell/company-switcher.tsx +1 -1
- package/src/shell/dev-menu.tsx +1 -1
- package/src/shell/top-bar-parts-labels.ts +1 -1
- package/src/shell.ts +17 -44
- package/src/sync.ts +2 -15
- package/src/theme/chart-palette.ts +0 -2
- package/tokens.css +4 -4
- package/dist/billing/dialog-close.d.ts +0 -29
- package/dist/billing/dialog-close.js +0 -8
- package/dist/billing/dialog-close.js.map +0 -1
- package/dist/components/field-sync-labels.d.ts +0 -3
- package/dist/components/field-sync-labels.js +0 -12
- package/dist/components/field-sync-labels.js.map +0 -1
- package/dist/components/field-sync.d.ts +0 -186
- package/dist/components/field-sync.js +0 -269
- package/dist/components/field-sync.js.map +0 -1
- package/dist/components/section-nav.d.ts +0 -44
- package/dist/components/section-nav.js +0 -33
- package/dist/components/section-nav.js.map +0 -1
- package/dist/components/sign-chip-labels.d.ts +0 -3
- package/dist/components/sign-chip-labels.js +0 -11
- package/dist/components/sign-chip-labels.js.map +0 -1
- package/dist/components/sign-chip.d.ts +0 -77
- package/dist/components/sign-chip.js +0 -47
- package/dist/components/sign-chip.js.map +0 -1
- package/dist/lib/isin.d.ts +0 -38
- package/dist/lib/isin.js +0 -36
- package/dist/lib/isin.js.map +0 -1
- package/dist/shell/role-switcher.d.ts +0 -28
- package/dist/shell/role-switcher.js +0 -31
- package/dist/shell/role-switcher.js.map +0 -1
- package/src/billing/dialog-close.ts +0 -34
- package/src/components/field-sync-labels.ts +0 -14
- package/src/components/field-sync.tsx +0 -504
- package/src/components/section-nav.tsx +0 -72
- package/src/components/sign-chip-labels.ts +0 -13
- package/src/components/sign-chip.tsx +0 -133
- package/src/lib/isin.ts +0 -72
- package/src/shell/role-switcher.tsx +0 -53
|
@@ -129,13 +129,26 @@ export interface TreemapProps extends Omit<ComponentPropsWithoutRef<"div">, "chi
|
|
|
129
129
|
* A tile's and a group's accessible name (`labels.tile`, `labels.group`, 0.52) says the
|
|
130
130
|
* value too, asked of the formatter as the tooltip asks it, share and all, and its text
|
|
131
131
|
* is used; markup is said as the bare number. The name says the share itself, so a
|
|
132
|
-
* formatter that writes it into its text has it said twice
|
|
132
|
+
* formatter that writes it into its text has it said twice — say so with
|
|
133
|
+
* {@link TreemapProps.shareInValue}.
|
|
133
134
|
*
|
|
134
135
|
* Default: the bare number through `Intl.NumberFormat` in the provider's locale (0.52,
|
|
135
136
|
* DOC-10: before, the runtime's). A host with a currency passes this — the kit knows
|
|
136
137
|
* none.
|
|
137
138
|
*/
|
|
138
139
|
valueFormatter?: (value: number, share: number | undefined) => ReactNode;
|
|
140
|
+
/**
|
|
141
|
+
* The `valueFormatter`'s text already says the share ("CHF 412.30 · 12%"), so a tile's
|
|
142
|
+
* and a group's accessible name leave their own " (12%)" out (0.53, keksdose after 0.52):
|
|
143
|
+
* the map hands `labels.tile` and `labels.group` the share as `""`, and every catalogue's
|
|
144
|
+
* template drops it there, in its own typography. Before, the only way out was to replace
|
|
145
|
+
* both templates, and with them the kit's seven catalogues. The tooltip is the same either
|
|
146
|
+
* way: it prints what the formatter prints. A formatter whose value is markup is said as
|
|
147
|
+
* the bare number, so its share is then not said at all.
|
|
148
|
+
*
|
|
149
|
+
* Default `false`: the name says the share.
|
|
150
|
+
*/
|
|
151
|
+
shareInValue?: boolean;
|
|
139
152
|
/**
|
|
140
153
|
* What the map SHOWS, in a sentence — "Rent is three fifths of the month." — the
|
|
141
154
|
* figure's accessible description (0.52, DSP-5). Not drawn; a caption the eye should
|
|
@@ -185,8 +198,18 @@ export interface TreemapProps extends Omit<ComponentPropsWithoutRef<"div">, "chi
|
|
|
185
198
|
* Every tile is tagged as a whole too, clickable or not (0.52): its accessible name
|
|
186
199
|
* (`labels.tile`) says the name and the value, in an attribute a host's redaction
|
|
187
200
|
* reaches only through the tag.
|
|
201
|
+
*
|
|
202
|
+
* - `true`: every node — the tiles, and a group's frame (its `<g>` and its band's name)
|
|
203
|
+
* and tooltip bubble too.
|
|
204
|
+
* - `"tiles"` (0.53, keksdose after 0.52): the tiles as `true` tags them, and no group —
|
|
205
|
+
* for groups that are public names over private ones, as chains (Migros, Coop) over
|
|
206
|
+
* the shops a user paid. Its frame's band, its frame and its bubble's name are
|
|
207
|
+
* untagged, and so is the frame's accessible name (`labels.group`), which says the
|
|
208
|
+
* group's TOTAL: use it only where the groups' names and totals are not the private
|
|
209
|
+
* part. The bubble's value cell is tagged whatever this says, as in every chart.
|
|
210
|
+
* A tile's name still says its group's (`labels.inGroup`), under the tile's tag.
|
|
188
211
|
*/
|
|
189
|
-
redactNames?: boolean;
|
|
212
|
+
redactNames?: boolean | "tiles";
|
|
190
213
|
/**
|
|
191
214
|
* A second, smaller line under a tile's label — one extra figure per node that the
|
|
192
215
|
* name cannot carry (a year-on-year `+12%`, `new`). Return `undefined` for a node
|
|
@@ -436,7 +459,9 @@ export interface TreemapCellProps {
|
|
|
436
459
|
/** Filled in by recharts for a GROUP: the nodes laid out inside it. A cell with
|
|
437
460
|
* children draws a frame, not a tile (0.52). */
|
|
438
461
|
children?: readonly object[] | null;
|
|
439
|
-
redactNames
|
|
462
|
+
/** See {@link TreemapProps.redactNames}: `"tiles"` tags a cell with no `children`, and
|
|
463
|
+
* leaves a group's frame and band untagged (0.53). */
|
|
464
|
+
redactNames?: boolean | "tiles";
|
|
440
465
|
nodeNote?: (id: string) => string | undefined;
|
|
441
466
|
/** The page's reading direction, which `Treemap` reads off its own root. In `rtl`
|
|
442
467
|
* the label stands in the tile's top RIGHT corner and is shaped right-to-left, so a
|
|
@@ -578,6 +603,10 @@ function Tile({
|
|
|
578
603
|
// is a button.
|
|
579
604
|
const roving = path != null && keys ? keys : undefined;
|
|
580
605
|
const focusable = roving !== undefined || activate !== undefined;
|
|
606
|
+
// `"tiles"` (0.53, keksdose after 0.52) leaves a group's frame and band untagged: its
|
|
607
|
+
// groups are public chain names, and its blur on `[data-private]` blurred them with the
|
|
608
|
+
// payees. A truthy test on the union would have tagged them still.
|
|
609
|
+
const redact = redactNames === true || (redactNames === "tiles" && !group);
|
|
581
610
|
const onKeyDown = focusable
|
|
582
611
|
? (e: KeyboardEvent<SVGGElement>) => {
|
|
583
612
|
if (roving?.move(path!, e)) {
|
|
@@ -606,7 +635,7 @@ function Tile({
|
|
|
606
635
|
// In a map, -1 as drawn: the map moves its one stop on the elements in place.
|
|
607
636
|
tabIndex={roving ? -1 : activate ? 0 : undefined}
|
|
608
637
|
aria-label={focusable ? (roving?.name(path!) ?? name) : undefined}
|
|
609
|
-
data-private={focusable &&
|
|
638
|
+
data-private={focusable && redact ? "" : undefined}
|
|
610
639
|
data-group={group ? "" : undefined}
|
|
611
640
|
cursor={activate ? "pointer" : undefined}
|
|
612
641
|
// CSS beats a presentation attribute, so this recolours the rect's own stroke
|
|
@@ -646,7 +675,7 @@ function Tile({
|
|
|
646
675
|
<text
|
|
647
676
|
// A plain attribute, so the host's `[data-private]` rule reaches an SVG
|
|
648
677
|
// <text> exactly as it reaches a table cell.
|
|
649
|
-
data-private={
|
|
678
|
+
data-private={redact ? "" : undefined}
|
|
650
679
|
x={textX}
|
|
651
680
|
// A band's name centred in its 20px; a tile's where its floor leaves room below.
|
|
652
681
|
y={py + (group ? 14 : 15) * scale}
|
|
@@ -697,10 +726,20 @@ const TREEMAP_CONFIG: ChartConfig = {};
|
|
|
697
726
|
* recharts CLONES the `content` element to inject `active`/`payload`, so this has to BE
|
|
698
727
|
* that element — a `<div>` wrapped around `<ChartTooltipContent />` in the JSX would
|
|
699
728
|
* take those props itself and hand the tooltip nothing.
|
|
729
|
+
*
|
|
730
|
+
* `groups: false` is `redactNames="tiles"` (0.53): a group's bubble is left untagged, as its
|
|
731
|
+
* frame is. The payload says which node is hovered — recharts hands the node itself, with
|
|
732
|
+
* its laid-out `children` for a group and `null` for a tile, as the cell is told — so
|
|
733
|
+
* anything else, or no payload, is tagged.
|
|
700
734
|
*/
|
|
701
|
-
function RedactedTooltipContent(
|
|
735
|
+
function RedactedTooltipContent({
|
|
736
|
+
groups = true,
|
|
737
|
+
...props
|
|
738
|
+
}: ComponentProps<typeof ChartTooltipContent> & { groups?: boolean }) {
|
|
739
|
+
const children = props.payload?.[0]?.payload?.children;
|
|
740
|
+
const group = Array.isArray(children) && children.length > 0;
|
|
702
741
|
return (
|
|
703
|
-
<div data-private>
|
|
742
|
+
<div data-private={groups || !group ? true : undefined}>
|
|
704
743
|
<ChartTooltipContent {...props} />
|
|
705
744
|
</div>
|
|
706
745
|
);
|
|
@@ -895,6 +934,7 @@ export function Treemap({
|
|
|
895
934
|
onNodeClick,
|
|
896
935
|
onGroupClick,
|
|
897
936
|
redactNames = false,
|
|
937
|
+
shareInValue = false,
|
|
898
938
|
nodeNote,
|
|
899
939
|
maxTiles,
|
|
900
940
|
colors,
|
|
@@ -1066,10 +1106,13 @@ export function Treemap({
|
|
|
1066
1106
|
};
|
|
1067
1107
|
// A group's name by its path; a node's groups by theirs, its own path's prefixes.
|
|
1068
1108
|
const groupName = new Map(order.filter((n) => n.children).map((n) => [n.path, n.name]));
|
|
1109
|
+
// `""` when the formatter's text says the share already, which every template drops
|
|
1110
|
+
// (0.53, `shareInValue`).
|
|
1111
|
+
const shareOf = (v: number) => (shareInValue ? "" : shareText(total > 0 ? v / total : 0));
|
|
1069
1112
|
return new Map(
|
|
1070
1113
|
order.map((n) => {
|
|
1071
1114
|
const say = n.children ? labels.group : labels.tile;
|
|
1072
|
-
const own = say(n.name, valueText(n.size),
|
|
1115
|
+
const own = say(n.name, valueText(n.size), shareOf(n.size));
|
|
1073
1116
|
// The innermost group first: "Rent: …, in Housing, in Fixed costs".
|
|
1074
1117
|
const named = n.groups
|
|
1075
1118
|
.map((g, i) => groupName.get(pathOf(n.groups.slice(0, i), g)) ?? g)
|
|
@@ -1077,7 +1120,7 @@ export function Treemap({
|
|
|
1077
1120
|
return [n.path, named] as const;
|
|
1078
1121
|
}),
|
|
1079
1122
|
);
|
|
1080
|
-
}, [order, labels, format, numberFormat, shareText, total]);
|
|
1123
|
+
}, [order, labels, format, numberFormat, shareText, shareInValue, total]);
|
|
1081
1124
|
const keys = useMemo<TileKeys>(
|
|
1082
1125
|
() => ({ name: (path) => names.get(path), onFocus, register, move }),
|
|
1083
1126
|
[names, onFocus, register, move],
|
|
@@ -1086,7 +1129,6 @@ export function Treemap({
|
|
|
1086
1129
|
() => ({ tokens: tokenHex, edgeFor, edges: edgesInForce, keys, layout, hover: anchor.set }),
|
|
1087
1130
|
[tokenHex, edgeFor, edgesInForce, keys, layout, anchor],
|
|
1088
1131
|
);
|
|
1089
|
-
const Content = redactNames ? RedactedTooltipContent : ChartTooltipContent;
|
|
1090
1132
|
const cell = useMemo(
|
|
1091
1133
|
() => (
|
|
1092
1134
|
<TreemapCell
|
|
@@ -1100,8 +1142,19 @@ export function Treemap({
|
|
|
1100
1142
|
[onNodeClick, onGroupClick, redactNames, nodeNote, dir],
|
|
1101
1143
|
);
|
|
1102
1144
|
const tooltip = useMemo(
|
|
1103
|
-
() =>
|
|
1104
|
-
|
|
1145
|
+
() => (
|
|
1146
|
+
<TreemapTooltip
|
|
1147
|
+
content={
|
|
1148
|
+
redactNames ? (
|
|
1149
|
+
<RedactedTooltipContent hideLabel valueFormatter={format} groups={redactNames === true} />
|
|
1150
|
+
) : (
|
|
1151
|
+
<ChartTooltipContent hideLabel valueFormatter={format} />
|
|
1152
|
+
)
|
|
1153
|
+
}
|
|
1154
|
+
anchor={anchor}
|
|
1155
|
+
/>
|
|
1156
|
+
),
|
|
1157
|
+
[redactNames, format, anchor],
|
|
1105
1158
|
);
|
|
1106
1159
|
|
|
1107
1160
|
if (nodes.length === 0) return null;
|
package/src/currencies.ts
CHANGED
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
// CurrencySelect, AmountInput's currency chip and NumberInput's code detection read this
|
|
13
13
|
// same array (components/currency-option-list re-exports it, and currency-select as it
|
|
14
14
|
// always has), so the list the pickers show and the flags an app ships cannot drift.
|
|
15
|
+
//
|
|
16
|
+
// A currency that was REPLACED stays in the array, marked `retired`: the pickers stop
|
|
17
|
+
// offering it, and whatever reads a value an app already holds — the trigger's flag and
|
|
18
|
+
// code, `getCurrency`, the flag subset, NumberInput's code detection — keeps knowing it
|
|
19
|
+
// (keksdose audit follow-up, 2026-11-01: the lev, after Bulgaria's euro).
|
|
15
20
|
|
|
16
21
|
export interface CurrencyOption {
|
|
17
22
|
code: string;
|
|
@@ -19,6 +24,19 @@ export interface CurrencyOption {
|
|
|
19
24
|
symbol: string;
|
|
20
25
|
/** ISO 3166-1 alpha-2 country code (lowercase) used by flag-icons. */
|
|
21
26
|
country: string;
|
|
27
|
+
/**
|
|
28
|
+
* The day ("YYYY-MM-DD") the currency was replaced as its country's money — set only on
|
|
29
|
+
* one that was. The pickers leave it out of their default list. A field that already
|
|
30
|
+
* holds it still shows it (the trigger's flag and code, and its row in the list,
|
|
31
|
+
* checked), and an explicit `options` list that names it still offers it: that list is
|
|
32
|
+
* the caller's.
|
|
33
|
+
*
|
|
34
|
+
* Marked rather than deleted, because an app may hold amounts in it from before. Gone
|
|
35
|
+
* from the array, `getCurrency` would answer `undefined` for a stored value, the field
|
|
36
|
+
* would show its placeholder over a real amount, and an app's flag subset
|
|
37
|
+
* (`CURRENCY_COUNTRIES`) would ship that value's flag as an empty box.
|
|
38
|
+
*/
|
|
39
|
+
retired?: string;
|
|
22
40
|
}
|
|
23
41
|
|
|
24
42
|
export const CURRENCIES: CurrencyOption[] = [
|
|
@@ -37,7 +55,8 @@ export const CURRENCIES: CurrencyOption[] = [
|
|
|
37
55
|
{ code: "CZK", name: "Czech Koruna", symbol: "Kč", country: "cz" },
|
|
38
56
|
{ code: "HUF", name: "Hungarian Forint", symbol: "Ft", country: "hu" },
|
|
39
57
|
{ code: "RON", name: "Romanian Leu", symbol: "lei", country: "ro" },
|
|
40
|
-
|
|
58
|
+
// Bulgaria adopted the euro on 2026-01-01 (keksdose audit follow-up, 2026-11-01).
|
|
59
|
+
{ code: "BGN", name: "Bulgarian Lev", symbol: "лв", country: "bg", retired: "2026-01-01" },
|
|
41
60
|
{ code: "TRY", name: "Turkish Lira", symbol: "₺", country: "tr" },
|
|
42
61
|
{ code: "RUB", name: "Russian Ruble", symbol: "₽", country: "ru" },
|
|
43
62
|
{ code: "CNY", name: "Chinese Yuan", symbol: "¥", country: "cn" },
|
|
@@ -56,8 +75,9 @@ export const CURRENCIES: CurrencyOption[] = [
|
|
|
56
75
|
* code flag-icons names it by (`fi-ch`, `flags/4x3/ch.svg`). `CHF` → `"ch"`, `EUR` →
|
|
57
76
|
* `"eu"` (flag-icons has the EU's flag under that code).
|
|
58
77
|
*
|
|
59
|
-
* Derived from {@link CURRENCIES}, so a currency the pickers gain is a flag here too
|
|
60
|
-
*
|
|
78
|
+
* Derived from {@link CURRENCIES}, so a currency the pickers gain is a flag here too — and
|
|
79
|
+
* a `retired` one keeps its flag, for the values an app still holds in it. An app that
|
|
80
|
+
* ships only the flags it renders builds its subset from these values:
|
|
61
81
|
*
|
|
62
82
|
* ```ts
|
|
63
83
|
* // vite.config.ts — plain Node, no DOM
|
package/src/data-table.ts
CHANGED
|
@@ -27,22 +27,12 @@ export type {
|
|
|
27
27
|
DataTableRowAction,
|
|
28
28
|
DataTableDesktopSwipeActions,
|
|
29
29
|
} from "./components/data-table";
|
|
30
|
-
// 0.46.1 (keksdose N3): a swipe plan as `RowActions`' actions — the desktop column's own
|
|
31
|
-
// derivation, for an app's hand-built actions column.
|
|
32
|
-
export {
|
|
33
|
-
/** @deprecated 0.52 — `DataTable`'s `desktopSwipeActions`, which builds its column with it (API-4); removed in 0.53 */
|
|
34
|
-
swipePlanRowActions,
|
|
35
|
-
} from "./components/data-table-row-actions";
|
|
36
30
|
// 0.48 (keksdose dev #605): a `groupBy` level (also `mobileGroupBy`'s) and the `loadMore` footer.
|
|
37
31
|
export type { DataTableGroupBy, DataTableLoadMore } from "./components/data-table-groups";
|
|
38
32
|
// 0.48.2 (keksdose dev #632): `virtualize`'s options.
|
|
39
33
|
export type { DataTableVirtualize } from "./components/data-table-virtual";
|
|
40
34
|
export * from "./components/data-table-cells";
|
|
41
|
-
export {
|
|
42
|
-
useTableUrlState,
|
|
43
|
-
/** @deprecated 0.52 — `useTableUrlState`, which reads the URL with it (API-4); removed in 0.53 */
|
|
44
|
-
readTableUrlState,
|
|
45
|
-
} from "./components/use-table-state";
|
|
35
|
+
export { useTableUrlState } from "./components/use-table-state";
|
|
46
36
|
export type {
|
|
47
37
|
TableUrlState,
|
|
48
38
|
TableUrlSync,
|
|
@@ -50,14 +50,9 @@ function minutesAt(expiry: number, now: number): number | null {
|
|
|
50
50
|
return Math.max(0, Math.ceil((expiry - now) / MINUTE));
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*/
|
|
57
|
-
export function demoCountdown(expiresAt: DemoExpiry | null | undefined, now: number): DemoCountdown {
|
|
58
|
-
return countdownOf(minutesAt(expiryMs(expiresAt), now));
|
|
59
|
-
}
|
|
60
|
-
|
|
53
|
+
/** The countdown for `minutesLeft` — see {@link DemoCountdown} for the rounding and the
|
|
54
|
+
* thresholds. (Public as `demoCountdown(expiresAt, now)` until 0.53, deprecated in 0.52,
|
|
55
|
+
* API-4: `useDemoCountdown` with its `now` option is the same countdown.) */
|
|
61
56
|
function countdownOf(minutesLeft: number | null): DemoCountdown {
|
|
62
57
|
if (minutesLeft === null) {
|
|
63
58
|
return { minutesLeft, hours: 0, minutes: 0, lastHour: false, warning: false, ended: false };
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { useId } from "react";
|
|
1
2
|
import { Checkbox } from "../components/checkbox";
|
|
2
3
|
import { DEFAULT_COMMON_LABELS, useKitLabels, type LabelOverride } from "../i18n/kit-labels";
|
|
3
4
|
import { cn } from "../lib/cn";
|
|
4
5
|
import type { FeedbackContext, FeedbackEnvironment } from "./feedback-record";
|
|
5
6
|
import { DEFAULT_FEEDBACK_CONTEXT_LABELS } from "./feedback-context-labels";
|
|
7
|
+
import { diagnosticsEntries, FeedbackDiagnosticsList } from "./feedback-diagnostics";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* The reporter context of a feedback report: the box the submit dialog shows, and the
|
|
@@ -70,6 +72,10 @@ export interface FeedbackContextInput {
|
|
|
70
72
|
viewport?: string;
|
|
71
73
|
/** Default `navigator.userAgent`. */
|
|
72
74
|
ua?: string;
|
|
75
|
+
/** What the app attached about the thing reported — `useFeedbackSubmit().open(category,
|
|
76
|
+
* { diagnostics })` (keksdose live #481). Sent whatever `attachUrl` says, as the box
|
|
77
|
+
* shows it; left out of the object when it has no key to show. */
|
|
78
|
+
diagnostics?: Readonly<Record<string, unknown>> | null;
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
/** The pathname of a full or path-only URL; `""` for something that is neither. */
|
|
@@ -85,12 +91,13 @@ function pathOf(url: string, base: string | undefined): string {
|
|
|
85
91
|
/**
|
|
86
92
|
* The `context` object of `POST /feedback` (§3.2), keys in keksdose's order: `url`, `route`,
|
|
87
93
|
* `origin`, `environment`, `user_id`, `user_email`, `user_display_name`, `viewport`, `ua`,
|
|
88
|
-
* `version`.
|
|
94
|
+
* `version`, then the app's `diagnostics`.
|
|
89
95
|
*
|
|
90
96
|
* With `attachUrl: false`, `url` and `route` are `""` — present and empty, as keksdose
|
|
91
97
|
* sends them, so a reader can tell "withheld" from "an old row without the key".
|
|
92
98
|
* `environment` and `version` are omitted when the app passes none (kastlan had no
|
|
93
|
-
* version), rather than sent as `undefined
|
|
99
|
+
* version), rather than sent as `undefined`; `diagnostics` when the box would show no
|
|
100
|
+
* line of it (none, `{}`, not a record).
|
|
94
101
|
*
|
|
95
102
|
* Reads `window` / `navigator` only for the defaults it was not given, so it is a pure
|
|
96
103
|
* function in a test that passes everything.
|
|
@@ -111,6 +118,8 @@ export function feedbackContext(input: FeedbackContextInput): FeedbackContext {
|
|
|
111
118
|
context.viewport = input.viewport ?? (win ? `${win.innerWidth}x${win.innerHeight}` : "");
|
|
112
119
|
context.ua = input.ua ?? (typeof navigator === "undefined" ? "" : navigator.userAgent);
|
|
113
120
|
if (input.version !== undefined) context.version = input.version;
|
|
121
|
+
// The box's own test: what it draws no line of is not sent either.
|
|
122
|
+
if (input.diagnostics && diagnosticsEntries(input.diagnostics).length) context.diagnostics = input.diagnostics;
|
|
114
123
|
return context;
|
|
115
124
|
}
|
|
116
125
|
|
|
@@ -127,6 +136,9 @@ export interface FeedbackContextLabels {
|
|
|
127
136
|
user: string;
|
|
128
137
|
/** The checkbox. The URL it is about is the line beneath it. */
|
|
129
138
|
attachUrl: string;
|
|
139
|
+
/** The heading over what the app attached — `open(category, { diagnostics })`, shown
|
|
140
|
+
* read-only under the URL (keksdose live #481). de-CH: "Mitgesendete Diagnose" */
|
|
141
|
+
diagnostics: string;
|
|
130
142
|
}
|
|
131
143
|
|
|
132
144
|
// Defined in ./feedback-context-labels, which holds no component code (0.34, KS U8).
|
|
@@ -140,6 +152,10 @@ export interface FeedbackContextBoxProps {
|
|
|
140
152
|
/** The checkbox, on by default in the dialog (§4.2) — the same `attachUrl`. */
|
|
141
153
|
attachUrl: boolean;
|
|
142
154
|
onAttachUrlChange: (next: boolean) => void;
|
|
155
|
+
/** What the app attached — the same `diagnostics` handed to {@link feedbackContext}.
|
|
156
|
+
* Shown read-only, each key and its value as the row detail draws them; no block when
|
|
157
|
+
* there is no line to show. */
|
|
158
|
+
diagnostics?: Readonly<Record<string, unknown>> | null;
|
|
143
159
|
/** Prop > `<UiKitProvider labels={{ feedbackContext }}>` > English. */
|
|
144
160
|
labels?: LabelOverride<FeedbackContextLabels>;
|
|
145
161
|
className?: string;
|
|
@@ -157,6 +173,10 @@ export interface FeedbackContextBoxProps {
|
|
|
157
173
|
* against whatever screen happened to be open, the queue reads as if it were about that
|
|
158
174
|
* screen.
|
|
159
175
|
*
|
|
176
|
+
* Below the URL, what the app attached (`diagnostics`, keksdose live #481: a stuck receipt
|
|
177
|
+
* scan's bookkeeping), read-only and in the row detail's own lines: the reporter sees what
|
|
178
|
+
* goes with the report, and that the URL checkbox does not withhold it.
|
|
179
|
+
*
|
|
160
180
|
* Nothing in here is the app's: hand it the values handed to {@link feedbackContext}, and
|
|
161
181
|
* what the box shows is what is sent.
|
|
162
182
|
*/
|
|
@@ -165,11 +185,14 @@ export function FeedbackContextBox({
|
|
|
165
185
|
url,
|
|
166
186
|
attachUrl,
|
|
167
187
|
onAttachUrlChange,
|
|
188
|
+
diagnostics,
|
|
168
189
|
labels: labelsProp,
|
|
169
190
|
className,
|
|
170
191
|
}: FeedbackContextBoxProps) {
|
|
171
192
|
const labels = useKitLabels("feedbackContext", DEFAULT_FEEDBACK_CONTEXT_LABELS, labelsProp);
|
|
172
193
|
const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
|
|
194
|
+
const diagnosticsId = useId();
|
|
195
|
+
const attached = diagnosticsEntries(diagnostics);
|
|
173
196
|
const submitter = submitterLine(user);
|
|
174
197
|
// "User: Ada …" through `common.fieldValue`, so French gets its space before the colon
|
|
175
198
|
// and Chinese its full-width one; the label and its punctuation are drawn bold, as
|
|
@@ -208,6 +231,15 @@ export function FeedbackContextBox({
|
|
|
208
231
|
) : undefined
|
|
209
232
|
}
|
|
210
233
|
/>
|
|
234
|
+
{attached.length > 0 && (
|
|
235
|
+
// A named group, so a screen reader hears what the pairs are before it reads them.
|
|
236
|
+
<div role="group" aria-labelledby={diagnosticsId} className="space-y-0.5 pt-1">
|
|
237
|
+
<div id={diagnosticsId} className="font-medium text-secondary">
|
|
238
|
+
{labels.diagnostics}
|
|
239
|
+
</div>
|
|
240
|
+
<FeedbackDiagnosticsList entries={attached} />
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
211
243
|
</div>
|
|
212
244
|
);
|
|
213
245
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `context.diagnostics` (§3.2), drawn one way wherever it shows: in the submit dialog's
|
|
3
|
+
* context box, to the reporter before it goes (§4.2), and in the row detail, to the admin
|
|
4
|
+
* and the author afterwards (§4.4.2). One module for both, so the reporter is shown the
|
|
5
|
+
* very lines the admin will read (keksdose live #481).
|
|
6
|
+
*
|
|
7
|
+
* Internal: neither `@eifi1/ui-kit/feedback` nor the barrel names it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** `diagnostics` as [key, text] pairs, in the app's order: a string, number or boolean as
|
|
11
|
+
* written, an object or a list as JSON, nothing as "—". Empty for no diagnostics, and for
|
|
12
|
+
* a value that is not a plain object — the server stores whatever the client sent. */
|
|
13
|
+
export function diagnosticsEntries(diagnostics: unknown): Array<[string, string]> {
|
|
14
|
+
if (!diagnostics || typeof diagnostics !== "object" || Array.isArray(diagnostics)) return [];
|
|
15
|
+
return Object.entries(diagnostics).map(([key, v]) => [
|
|
16
|
+
key,
|
|
17
|
+
v === null || v === undefined || v === ""
|
|
18
|
+
? "—"
|
|
19
|
+
: typeof v === "object"
|
|
20
|
+
? JSON.stringify(v)
|
|
21
|
+
: String(v as string | number | boolean | bigint),
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The pairs as a definition list: each key beside its value, in monospace. Read-only in
|
|
26
|
+
* both places; the heading over it is the caller's. */
|
|
27
|
+
export function FeedbackDiagnosticsList({ entries }: { entries: ReadonlyArray<readonly [string, string]> }) {
|
|
28
|
+
return (
|
|
29
|
+
<dl
|
|
30
|
+
className="grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-0.5 font-mono text-xs"
|
|
31
|
+
data-feedback-diagnostics=""
|
|
32
|
+
>
|
|
33
|
+
{entries.map(([key, text]) => (
|
|
34
|
+
<div key={key} className="contents">
|
|
35
|
+
<dt className="text-muted">{key}</dt>
|
|
36
|
+
<dd className="min-w-0 break-words text-secondary">{text}</dd>
|
|
37
|
+
</div>
|
|
38
|
+
))}
|
|
39
|
+
</dl>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -146,6 +146,16 @@ interface FeedbackDialogBaseProps<C extends string = string> {
|
|
|
146
146
|
* English. Optional since 0.12.0; a whole `FeedbackDialogLabels` still fits. */
|
|
147
147
|
labels?: LabelOverride<FeedbackDialogLabels>;
|
|
148
148
|
submitting?: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* The subject and the body each opening starts with, in place of empty fields — the
|
|
151
|
+
* reporter edits them like anything typed. `useFeedbackSubmit().open(category, { title,
|
|
152
|
+
* body })` passes them (keksdose live #481: "Report this problem" opens on words the
|
|
153
|
+
* screen already knows). Read when the dialog opens; a change while it is open leaves
|
|
154
|
+
* the draft alone.
|
|
155
|
+
*/
|
|
156
|
+
defaultTitle?: string;
|
|
157
|
+
/** The body's, as `defaultTitle` is the subject's. */
|
|
158
|
+
defaultBody?: string;
|
|
149
159
|
contextSlot?: ReactNode;
|
|
150
160
|
attachmentAccept?: string[];
|
|
151
161
|
maxAttachmentBytes?: number;
|
|
@@ -245,6 +255,8 @@ export function FeedbackDialog<C extends string = string>(props: FeedbackDialogP
|
|
|
245
255
|
onCategoryChange,
|
|
246
256
|
labels: labelsProp,
|
|
247
257
|
submitting = false,
|
|
258
|
+
defaultTitle = "",
|
|
259
|
+
defaultBody = "",
|
|
248
260
|
contextSlot,
|
|
249
261
|
attachmentAccept = DEFAULT_ATTACHMENT_ACCEPT,
|
|
250
262
|
maxAttachmentBytes = DEFAULT_MAX_ATTACHMENT_BYTES,
|
|
@@ -254,8 +266,8 @@ export function FeedbackDialog<C extends string = string>(props: FeedbackDialogP
|
|
|
254
266
|
attachmentButtonSize,
|
|
255
267
|
publicNotice,
|
|
256
268
|
} = props;
|
|
257
|
-
const [title, setTitle] = useState(
|
|
258
|
-
const [body, setBody] = useState(
|
|
269
|
+
const [title, setTitle] = useState(defaultTitle);
|
|
270
|
+
const [body, setBody] = useState(defaultBody);
|
|
259
271
|
const [attachment, setAttachment] = useState<File | null>(null);
|
|
260
272
|
// `attachments="multiple"` only: the capture, apart from the picked/pasted files.
|
|
261
273
|
const [screenshot, setScreenshot] = useState<File | null>(null);
|
|
@@ -283,8 +295,8 @@ export function FeedbackDialog<C extends string = string>(props: FeedbackDialogP
|
|
|
283
295
|
if (open !== wasOpen) {
|
|
284
296
|
setWasOpen(open);
|
|
285
297
|
if (open) {
|
|
286
|
-
setTitle(
|
|
287
|
-
setBody(
|
|
298
|
+
setTitle(defaultTitle);
|
|
299
|
+
setBody(defaultBody);
|
|
288
300
|
setAttachment(null);
|
|
289
301
|
setScreenshot(null);
|
|
290
302
|
setFiles([]);
|
|
@@ -1,41 +1,20 @@
|
|
|
1
|
-
import { isThenable } from "../lib/thenable";
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* A page's mutation
|
|
5
|
-
*
|
|
2
|
+
* A page's mutation, run once (refactor audit 2026-10-30, FLW-3): TanStack Query's
|
|
3
|
+
* `mutateAsync`, or any `(patch) => Promise` that resolves once the PATCH has landed.
|
|
6
4
|
*
|
|
7
|
-
* The parts
|
|
8
|
-
* v5 calls per-call callbacks for the LATEST `mutate()` on
|
|
9
|
-
* overwrites the options and detaches from the earlier mutation —
|
|
10
|
-
* before the first PATCH answered (another swipe, another pill,
|
|
11
|
-
* first change's Undo toast, and the detail's editor stayed
|
|
12
|
-
* `mutateAsync` returns settles for every call, whatever the observer
|
|
5
|
+
* The parts took TanStack Query's `mutate` with a per-call `onSuccess` until 0.53
|
|
6
|
+
* (deprecated in 0.52). TanStack v5 calls per-call callbacks for the LATEST `mutate()` on
|
|
7
|
+
* an observer only — it overwrites the options and detaches from the earlier mutation —
|
|
8
|
+
* so a second change sent before the first PATCH answered (another swipe, another pill,
|
|
9
|
+
* the Undo itself) lost the first change's Undo toast, and the detail's editor stayed
|
|
10
|
+
* open. The promise `mutateAsync` returns settles for every call, whatever the observer
|
|
11
|
+
* does next.
|
|
13
12
|
*
|
|
14
|
-
*
|
|
15
|
-
* taken from whichever comes first: the callback, or the promise the call returned. A
|
|
16
|
-
* rejected promise is the mutation's own to report (its `onError`), so it is caught here
|
|
13
|
+
* A rejected promise is the mutation's own to report (its `onError`), so it is caught here
|
|
17
14
|
* and nothing more happens.
|
|
18
15
|
*
|
|
19
16
|
* @internal Not part of `/feedback` or the barrel.
|
|
20
17
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* A mutation in either form: the promise form (0.52) — TanStack's `mutateAsync`, any
|
|
24
|
-
* `(patch) => Promise` — or the callback form before it — TanStack's `mutate`, a function
|
|
25
|
-
* that calls `onSuccess` once the PATCH has landed, and only then. One signature for
|
|
26
|
-
* both, so an inline function handed to a prop of this type gets its parameter types (a
|
|
27
|
-
* union of the two leaves `async (patch) => …` an implicit `any`).
|
|
28
|
-
*/
|
|
29
|
-
export type FeedbackMutationCall<P> = (patch: P, callbacks: { onSuccess: () => void }) => void | Promise<unknown>;
|
|
30
|
-
|
|
31
|
-
/** Send `patch` through `call`, and run `landed` once when it has landed. */
|
|
32
|
-
export function runMutation<P>(call: FeedbackMutationCall<P>, patch: P, landed: () => void): void {
|
|
33
|
-
let done = false;
|
|
34
|
-
const once = () => {
|
|
35
|
-
if (done) return;
|
|
36
|
-
done = true;
|
|
37
|
-
landed();
|
|
38
|
-
};
|
|
39
|
-
const answer: unknown = call(patch, { onSuccess: once });
|
|
40
|
-
if (isThenable(answer)) answer.then(once, () => undefined);
|
|
18
|
+
export function runMutation<P>(call: (patch: P) => Promise<unknown>, patch: P, landed: () => void): void {
|
|
19
|
+
call(patch).then(landed, () => undefined);
|
|
41
20
|
}
|
|
@@ -174,11 +174,6 @@ export interface FeedbackPublicListProps<T extends FeedbackPublicListItem = Feed
|
|
|
174
174
|
* what it carries: every other `onOpenChange` in the kit is a boolean (refactor audit
|
|
175
175
|
* 2026-10-30, API-5). */
|
|
176
176
|
onOpenIdChange?: (id: number | null) => void;
|
|
177
|
-
/**
|
|
178
|
-
* @deprecated 0.52 — `onOpenIdChange`, the same call: an `onOpenChange` is a boolean
|
|
179
|
-
* everywhere else in the kit; removed in 0.53
|
|
180
|
-
*/
|
|
181
|
-
onOpenChange?: (id: number | null) => void;
|
|
182
177
|
/**
|
|
183
178
|
* 0.38.1: the opened report when it is NOT on this page — a `?row=` link to an older
|
|
184
179
|
* one (Kurvenschmiede). The app fetches it (`GET /feedback/public/{id}`, a list item's
|
|
@@ -249,7 +244,6 @@ export function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPu
|
|
|
249
244
|
loading = false,
|
|
250
245
|
openId: openIdProp,
|
|
251
246
|
onOpenIdChange,
|
|
252
|
-
onOpenChange,
|
|
253
247
|
openRow,
|
|
254
248
|
page: pageIndex = 0,
|
|
255
249
|
hasMore = false,
|
|
@@ -269,7 +263,7 @@ export function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPu
|
|
|
269
263
|
const openId = openIdProp !== undefined ? openIdProp : ownOpenId;
|
|
270
264
|
const setOpen = (id: number | null) => {
|
|
271
265
|
if (openIdProp === undefined) setOwnOpenId(id);
|
|
272
|
-
|
|
266
|
+
onOpenIdChange?.(id);
|
|
273
267
|
};
|
|
274
268
|
// A `?row=` report from another page: first, while it is the open one, and only when
|
|
275
269
|
// the page does not have it itself.
|
|
@@ -59,6 +59,11 @@ export interface FeedbackContext {
|
|
|
59
59
|
ua?: string;
|
|
60
60
|
/** The app build (`__APP_VERSION__`). */
|
|
61
61
|
version?: string;
|
|
62
|
+
/** What the app attached about the thing reported (keksdose's stuck receipt scan, live
|
|
63
|
+
* #481): a flat record of short values, in the order the app wrote them. The row
|
|
64
|
+
* detail shows it to the admin and the author, never to anyone else; erasure drops it
|
|
65
|
+
* (it is not on server-kit's `FEEDBACK_CONTEXT_KEEP`). */
|
|
66
|
+
diagnostics?: Readonly<Record<string, unknown>>;
|
|
62
67
|
// Crash rows (server-written, keksdose `feedback_service.py:346`).
|
|
63
68
|
fingerprint?: string;
|
|
64
69
|
/** Which error boundary caught it. */
|
|
@@ -14,20 +14,13 @@ export const DEFAULT_FEEDBACK_DETAIL_LABELS: FeedbackDetailLabels = {
|
|
|
14
14
|
cancel: "Cancel",
|
|
15
15
|
url: "URL",
|
|
16
16
|
copyUrl: "Copy URL",
|
|
17
|
+
diagnostics: "Diagnostics",
|
|
17
18
|
attachment: "Attachment",
|
|
18
19
|
download: (name) => `Download ${name}`,
|
|
19
20
|
downloadFailed: "The attachment could not be downloaded.",
|
|
20
21
|
outcome: "Outcome",
|
|
21
22
|
replies: "Replies",
|
|
22
23
|
resolvedAt: (date) => `Resolved: ${date}`,
|
|
23
|
-
// The outcome editor's three words, read by nothing since 0.38 and "kept for one
|
|
24
|
-
// release" for 13 minors: no tag, so the deprecations test never listed them (refactor
|
|
25
|
-
// audit 2026-10-30, DOC-9). Deprecated on `FeedbackDetailLabels`, the type an app reads
|
|
26
|
-
// them through — a tag on these keys reached no reader (0.52 code review). The keys
|
|
27
|
-
// leave the type and all seven catalogues in 0.53.
|
|
28
|
-
outcomeAdd: "Add outcome",
|
|
29
|
-
outcomeUpdate: "Update",
|
|
30
|
-
outcomePlaceholder: "What was done, decided, or why this won't be addressed.",
|
|
31
24
|
rework: "Rework",
|
|
32
25
|
reworkTitle: "Send for rework",
|
|
33
26
|
reworkSend: "Send rework",
|