@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
|
@@ -8,7 +8,15 @@ export type ColumnFilter<T> =
|
|
|
8
8
|
| { type: "text"; getValue: (row: T) => string }
|
|
9
9
|
| { type: "select"; getValue: (row: T) => string; options?: { value: string; label?: string }[] }
|
|
10
10
|
| { type: "date"; getValue: (row: T) => string | null | undefined }
|
|
11
|
-
| {
|
|
11
|
+
| {
|
|
12
|
+
type: "number";
|
|
13
|
+
getValue: (row: T) => number | null | undefined;
|
|
14
|
+
/** `false`: no "Absolute value" box, for a figure that is never negative (a year, a
|
|
15
|
+
* count, a size), where the box only offers a choice that changes nothing. A state
|
|
16
|
+
* or a link that carries `abs` anyway is read without it. Default `true`. See
|
|
17
|
+
* {@link numberFilter}. */
|
|
18
|
+
abs?: boolean;
|
|
19
|
+
};
|
|
12
20
|
|
|
13
21
|
/** The current state of a column's filter (what the user has entered). */
|
|
14
22
|
export type FilterValue =
|
|
@@ -46,8 +54,15 @@ export function isFilterActive(v: FilterValue | undefined): boolean {
|
|
|
46
54
|
* match every row while the column showed its filter as active; `Number(" ")` is 0, and
|
|
47
55
|
* a blank bound kept every negative row out. The popover hands in the dot-decimal text
|
|
48
56
|
* its locale's marks read (`readTyped`); anything else is no bound.
|
|
57
|
+
*
|
|
58
|
+
* Exported for a server-side table (`serverPagination`), which sends the bounds to its
|
|
59
|
+
* server instead of comparing here: send what this reads, and leave out a `null`. The box
|
|
60
|
+
* holds a half-typed "-" or "5e" while the user types, and kastlan's server tables sent
|
|
61
|
+
* that text on to `col >= :bound` until they wrote this rule again for themselves (kastlan
|
|
62
|
+
* reuse audit 2026-11-02, proposal 7). And send `abs` only beside a bound: on its own it
|
|
63
|
+
* narrows nothing.
|
|
49
64
|
*/
|
|
50
|
-
function numberBound(raw: string): number | null {
|
|
65
|
+
export function numberBound(raw: string): number | null {
|
|
51
66
|
if (raw.trim() === "") return null;
|
|
52
67
|
const n = Number(raw);
|
|
53
68
|
return Number.isFinite(n) ? n : null;
|
|
@@ -108,7 +123,9 @@ export function rowMatches<T>(col: DataTableColumn<T>, row: T, state: FilterValu
|
|
|
108
123
|
const min = numberBound(state.min);
|
|
109
124
|
const max = numberBound(state.max);
|
|
110
125
|
if (raw == null || Number.isNaN(raw)) return min === null && max === null;
|
|
111
|
-
|
|
126
|
+
// A column without the box (`abs: false`) compares the figure as it is, whatever
|
|
127
|
+
// the state says: there is nothing on screen to untick.
|
|
128
|
+
const x = state.abs && filter.abs !== false ? Math.abs(raw) : raw;
|
|
112
129
|
if (min !== null && x < min) return false;
|
|
113
130
|
if (max !== null && x > max) return false;
|
|
114
131
|
return true;
|
|
@@ -204,9 +221,12 @@ export function decodeFilterValueOfType(type: FilterValue["type"], raw: string):
|
|
|
204
221
|
}
|
|
205
222
|
}
|
|
206
223
|
|
|
207
|
-
/** Inverse of {@link encodeFilterValue}, typed by the column's filter config.
|
|
224
|
+
/** Inverse of {@link encodeFilterValue}, typed by the column's filter config. A number
|
|
225
|
+
* filter without the "Absolute value" box (`abs: false`) drops a link's `!`: kept, a
|
|
226
|
+
* server-side table would send `abs` that nobody can see or untick. */
|
|
208
227
|
export function decodeFilterValue<T>(filter: ColumnFilter<T>, raw: string): FilterValue {
|
|
209
|
-
|
|
228
|
+
const value = decodeFilterValueOfType(filter.type, raw);
|
|
229
|
+
return value.type === "number" && filter.type === "number" && filter.abs === false ? { ...value, abs: false } : value;
|
|
210
230
|
}
|
|
211
231
|
|
|
212
232
|
// ---------- Filter builders ----------
|
|
@@ -244,9 +264,17 @@ export function dateFilter<T>(getValue: (row: T) => string | null | undefined):
|
|
|
244
264
|
return { type: "date", getValue };
|
|
245
265
|
}
|
|
246
266
|
|
|
247
|
-
/**
|
|
248
|
-
|
|
249
|
-
|
|
267
|
+
/**
|
|
268
|
+
* A min/max (and absolute-value) filter over a number. `{ abs: false }` leaves out the
|
|
269
|
+
* "Absolute value" box, for a figure that is never negative — a year, a count, a size —
|
|
270
|
+
* where it only offers a choice that changes nothing (kastlan reuse audit 2026-11-02,
|
|
271
|
+
* proposal 7: its server tables drew it under construction years and file sizes).
|
|
272
|
+
*/
|
|
273
|
+
export function numberFilter<T>(
|
|
274
|
+
getValue: (row: T) => number | null | undefined,
|
|
275
|
+
options?: { abs?: boolean },
|
|
276
|
+
): ColumnFilter<T> {
|
|
277
|
+
return options?.abs === false ? { type: "number", getValue, abs: false } : { type: "number", getValue };
|
|
250
278
|
}
|
|
251
279
|
|
|
252
280
|
/**
|
|
@@ -137,7 +137,7 @@ export interface DatePickerBaseProps extends Omit<ComponentPropsWithoutRef<"div"
|
|
|
137
137
|
*
|
|
138
138
|
* Off by default: the caption becomes one more stop in the panel's tab order, which a
|
|
139
139
|
* 0.x minor does not spring on a form. Works in the popover and in
|
|
140
|
-
* `DateRangePicker`'s phone sheet alike, beside presets and
|
|
140
|
+
* `DateRangePicker`'s phone sheet alike, beside presets and with `apply`
|
|
141
141
|
* (a preset picked while the months are open goes back to the days, on its start).
|
|
142
142
|
*
|
|
143
143
|
* Why not a two-month panel (`months={2}`) instead, or beside it: for a twelve-month
|
|
@@ -16,7 +16,6 @@ import { PanelCalendar } from "./jump-calendar";
|
|
|
16
16
|
export type { DateTriggerAttributes } from "./date-field";
|
|
17
17
|
export { DateRangePicker } from "./date-range-picker";
|
|
18
18
|
export type {
|
|
19
|
-
DateRangeCommit,
|
|
20
19
|
DateRangeDraftSummary,
|
|
21
20
|
DateRangePickerPreset,
|
|
22
21
|
DateRangePickerProps,
|
|
@@ -40,17 +40,6 @@ export interface DateRangePickerPreset {
|
|
|
40
40
|
to: string;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/**
|
|
44
|
-
* The 0.x values of DateRangePicker's `commit`, when it was the panel's mode:
|
|
45
|
-
* `"immediate"` is `apply` left out, `"apply"` is `apply`. See
|
|
46
|
-
* {@link DateRangePickerProps.apply}.
|
|
47
|
-
*
|
|
48
|
-
* @deprecated 0.52 — DateRangePicker's `apply` (`commit="apply"` → `apply`, `"immediate"`
|
|
49
|
-
* → nothing); removed in 0.53. `commit` is the write lock's opt-in on every other kit
|
|
50
|
-
* control, and since 0.52 on this one too (refactor audit 2026-10-30, API-3).
|
|
51
|
-
*/
|
|
52
|
-
export type DateRangeCommit = "immediate" | "apply";
|
|
53
|
-
|
|
54
43
|
export interface DateRangePickerProps extends DatePickerBaseProps {
|
|
55
44
|
from: string;
|
|
56
45
|
to: string;
|
|
@@ -89,8 +78,7 @@ export interface DateRangePickerProps extends DatePickerBaseProps {
|
|
|
89
78
|
* must never escape the panel.
|
|
90
79
|
*
|
|
91
80
|
* `commit="apply"` until 0.52 (refactor audit 2026-10-30, API-3): `commit` is the write
|
|
92
|
-
* lock's prop on every other kit control. The old spelling
|
|
93
|
-
* goes in 0.53 ({@link DateRangeCommit}); `apply` wins where both are given.
|
|
81
|
+
* lock's prop on every other kit control. The old spelling went in 0.53.
|
|
94
82
|
*/
|
|
95
83
|
apply?: boolean;
|
|
96
84
|
/**
|
|
@@ -100,12 +88,10 @@ export interface DateRangePickerProps extends DatePickerBaseProps {
|
|
|
100
88
|
* kit control since 0.33, and on this one since 0.52 (API-3). A range inside a form with
|
|
101
89
|
* its own Save stays editable under the lock — leave this off there.
|
|
102
90
|
*
|
|
103
|
-
* The strings `"immediate"` and `"apply"
|
|
104
|
-
*
|
|
105
|
-
* {@link DateRangeCommit}. They cannot be taken for a scope, which is a boolean or an
|
|
106
|
-
* object.
|
|
91
|
+
* The strings `"immediate"` and `"apply"`, the panel's mode in 0.x, went in 0.53
|
|
92
|
+
* (deprecated in 0.52): `apply` is the mode.
|
|
107
93
|
*/
|
|
108
|
-
commit?: CommitScope
|
|
94
|
+
commit?: CommitScope;
|
|
109
95
|
/**
|
|
110
96
|
* Draw the trigger yourself. keksdose's report range field
|
|
111
97
|
* (reports/report-range-field.tsx, "Why this is app-level") is why: its trigger
|
|
@@ -428,10 +414,8 @@ export function DateRangePicker(props: DateRangePickerProps) {
|
|
|
428
414
|
const locale = useKitLocale(localeProp);
|
|
429
415
|
const text = useDatePickerLabels(clearLabel, undefined, undefined);
|
|
430
416
|
const render = useTriggerFormat("dateRangePicker", locale, formatOptions, formatValue);
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const drafting = apply ?? commit === "apply";
|
|
434
|
-
const lock = useLockReason(typeof commit === "string" ? undefined : commit, disabledReason);
|
|
417
|
+
const drafting = apply ?? false;
|
|
418
|
+
const lock = useLockReason(commit, disabledReason);
|
|
435
419
|
const locked = lock.locked;
|
|
436
420
|
const messages = useFieldMessages({
|
|
437
421
|
hint,
|
|
@@ -26,7 +26,9 @@ export { DEFAULT_DIALOG_FRAME_LABELS };
|
|
|
26
26
|
* `onKeyDown`, a `data-tour` anchor, still reaches the `Modal` — and since 0.35 `ref`
|
|
27
27
|
* (round 0.35 R4): the panel, the `role="dialog"` element the frame's header, body and
|
|
28
28
|
* actions sit in. `busy` too (DUP-3): it holds the frame's X and the `close` that
|
|
29
|
-
* `actions` and `headerActions` receive, since both are the `Modal`'s own close.
|
|
29
|
+
* `actions` and `headerActions` receive, since both are the `Modal`'s own close. And
|
|
30
|
+
* `held` (keksdose audit follow-up, 2026-11-01), which holds the same and shows the X
|
|
31
|
+
* disabled — see {@link ModalProps.held}.
|
|
30
32
|
*/
|
|
31
33
|
export interface DialogFrameProps extends Omit<ModalProps, "labelledBy" | "children" | "title"> {
|
|
32
34
|
/** The heading, and therefore the dialog's accessible name (`aria-labelledby`). */
|
|
@@ -187,7 +189,7 @@ export function DialogFrame({
|
|
|
187
189
|
<FrameHeaderActions actions={headerActions} onClose={modal.onClose} />
|
|
188
190
|
)}
|
|
189
191
|
</div>
|
|
190
|
-
{closeButton && <FrameClose label={closeLabel} onClose={modal.onClose} />}
|
|
192
|
+
{closeButton && <FrameClose label={closeLabel} onClose={modal.onClose} held={modal.held} />}
|
|
191
193
|
</div>
|
|
192
194
|
{hasBody && (
|
|
193
195
|
<div
|
|
@@ -210,8 +212,10 @@ export function DialogFrame({
|
|
|
210
212
|
}
|
|
211
213
|
|
|
212
214
|
/** The X. A component of its own so it can read the panel's animated close, which
|
|
213
|
-
* only exists INSIDE the `Modal` (the frame's own body runs outside it).
|
|
214
|
-
|
|
215
|
+
* only exists INSIDE the `Modal` (the frame's own body runs outside it). Disabled
|
|
216
|
+
* while `held`; under `busy` it stays enabled and the `Modal` drops its press (see
|
|
217
|
+
* {@link ModalProps.held} for why the two differ). */
|
|
218
|
+
function FrameClose({ label, onClose, held = false }: { label?: string; onClose: () => void; held?: boolean }) {
|
|
215
219
|
const close = useContext(ModalCloseContext) ?? onClose;
|
|
216
220
|
const labels = useKitLabels(
|
|
217
221
|
"dialogFrame",
|
|
@@ -222,9 +226,13 @@ function FrameClose({ label, onClose }: { label?: string; onClose: () => void })
|
|
|
222
226
|
<button
|
|
223
227
|
type="button"
|
|
224
228
|
onClick={close}
|
|
229
|
+
disabled={held}
|
|
225
230
|
aria-label={labels.close}
|
|
226
231
|
className={cn(
|
|
227
232
|
"-me-1.5 -mt-1 shrink-0 rounded p-1.5 text-muted outline-none hover:bg-hover hover:text-secondary",
|
|
233
|
+
// The kit's disabled look (Button, IconButton), and the resting look pinned under
|
|
234
|
+
// `disabled:hover:` so a refused X does not light up under the pointer.
|
|
235
|
+
"disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-transparent disabled:hover:text-muted",
|
|
228
236
|
FOCUS_RING,
|
|
229
237
|
)}
|
|
230
238
|
>
|
|
@@ -8,6 +8,7 @@ import { FOCUS_RING, FOCUS_RING_WIDTH } from "./focus-ring";
|
|
|
8
8
|
import { horizontalStep } from "../lib/direction";
|
|
9
9
|
import { useAnnounce } from "../hooks/use-announce";
|
|
10
10
|
import { addMonthsClamped, localeWeekStart, monthKey, parseIsoDate, sameYmd, toLocalIso } from "../lib/dates";
|
|
11
|
+
import { dateFormatter } from "../lib/intl-cache";
|
|
11
12
|
import { useKitLabels, useKitLocale, useKitWeekStart, type LabelOverride } from "../i18n/kit-labels";
|
|
12
13
|
import { DEFAULT_MINI_CALENDAR_LABELS } from "./mini-calendar-labels";
|
|
13
14
|
|
|
@@ -355,8 +356,10 @@ export function MiniCalendar({
|
|
|
355
356
|
// The first letter only, not CSS `capitalize`, which upper-cased every word: Spanish
|
|
356
357
|
// read "Septiembre De 2026" and Hungarian "2026. Szeptember" (refactor audit
|
|
357
358
|
// 2026-10-30, DUP-11) — while the same panel with `monthJump` read them right.
|
|
358
|
-
|
|
359
|
-
|
|
359
|
+
// Both through the kit's formatter cache: every day cell names its date, which was a
|
|
360
|
+
// formatter per cell and render (keksdose audit follow-up, 2026-11-01).
|
|
361
|
+
const monthLabel = upperFirst(dateFormatter(locale, { month: "long", year: "numeric" }).format(firstOfMonth), locale);
|
|
362
|
+
const formatDay = (d: Date) => dateFormatter(locale, DAY_NAME_FORMAT).format(d);
|
|
360
363
|
|
|
361
364
|
// Weekday labels via locale, from the week's first day: the narrow one for the
|
|
362
365
|
// column head, the long one as its accessible name — "M" is the same letter for
|
package/src/components/modal.tsx
CHANGED
|
@@ -257,8 +257,35 @@ export interface ModalProps extends Omit<ComponentPropsWithoutRef<"div">, "role"
|
|
|
257
257
|
* the dialog asks for itself through `ModalCloseContext` while busy is refused too; one
|
|
258
258
|
* made from an effect that depends on that close (ReauthDialog's on a resolved submit)
|
|
259
259
|
* runs again once `busy` turns false, because the close changes with it.
|
|
260
|
+
*
|
|
261
|
+
* A dialog that must not be dismissed while NOTHING runs — a code shown once — wants
|
|
262
|
+
* {@link ModalProps.held} instead: the same hold without the request.
|
|
260
263
|
*/
|
|
261
264
|
busy?: boolean;
|
|
265
|
+
/**
|
|
266
|
+
* Hold the dialog open: every dismissal `busy` refuses is refused — the backdrop,
|
|
267
|
+
* Escape, Back, the frame's X and an `actions(close)` button — but no request is
|
|
268
|
+
* claimed: the panel is not `aria-busy`. For a dialog the user must not lose by
|
|
269
|
+
* accident while nothing is in flight (keksdose audit follow-up, 2026-11-01): keksdose's
|
|
270
|
+
* one-time recovery code (auth/recovery-code-pane.tsx) refused in its own `onClose`,
|
|
271
|
+
* which comes after the exit has started and cannot hold Back — the first Back spent
|
|
272
|
+
* the dialog's history entry, and the second left the page, and the code with it.
|
|
273
|
+
* Held, a refused Back puts the entry back, so every Back is held, not only the first.
|
|
274
|
+
* The refusal is silent, as under `busy` — `onClose` is not called — so what the
|
|
275
|
+
* dialog is waiting for is said up front, not after a refused press.
|
|
276
|
+
*
|
|
277
|
+
* The way out is a close the CALLER decides on, which is never held: `open={false}`
|
|
278
|
+
* (the exit still plays) or unmounting, from the dialog's own "I have saved it" button.
|
|
279
|
+
* Not the `close` that `actions` and `headerActions` receive — that is the dialog's
|
|
280
|
+
* own dismissal, and held like the rest.
|
|
281
|
+
*
|
|
282
|
+
* `DialogFrame`'s X is DISABLED while held, where under `busy` it stays enabled and its
|
|
283
|
+
* press is dropped: a request is over in a moment, a hold lasts until the user acts,
|
|
284
|
+
* and an X that does nothing for that long reads as broken. Disabled rather than
|
|
285
|
+
* hidden, so the header does not reflow the moment the hold lifts; a caller that wants
|
|
286
|
+
* it gone passes `closeButton={!held}`. Independent of `busy`: either one holds.
|
|
287
|
+
*/
|
|
288
|
+
held?: boolean;
|
|
262
289
|
/**
|
|
263
290
|
* The PANEL — the `role="dialog"` element the rest of the props go on, not the
|
|
264
291
|
* backdrop around it (0.35, round 0.35 R4, the follow-up to API-2; as on
|
|
@@ -325,6 +352,7 @@ function ModalPanel({
|
|
|
325
352
|
role = "dialog",
|
|
326
353
|
urlParam: _urlParam,
|
|
327
354
|
busy = false,
|
|
355
|
+
held = false,
|
|
328
356
|
"aria-busy": ariaBusy,
|
|
329
357
|
ownHistoryEntry = true,
|
|
330
358
|
ref,
|
|
@@ -371,13 +399,14 @@ function ModalPanel({
|
|
|
371
399
|
setDismissed(true);
|
|
372
400
|
onClose();
|
|
373
401
|
});
|
|
374
|
-
// `busy` (DUP-3)
|
|
375
|
-
// caller's `onClose` comes 220ms too late, after the panel has already gone down.
|
|
376
|
-
// A new function when
|
|
402
|
+
// `busy` (DUP-3) and `held` refuse HERE, before `lower` starts the exit: a refusal in
|
|
403
|
+
// the caller's `onClose` comes 220ms too late, after the panel has already gone down.
|
|
404
|
+
// A new function when the hold flips, so a close asked for from an effect while busy
|
|
377
405
|
// (a resolved action whose caller's `isPending` trails its promise) runs again.
|
|
406
|
+
const holding = busy || held;
|
|
378
407
|
const requestClose = useCallback(() => {
|
|
379
|
-
if (!
|
|
380
|
-
}, [
|
|
408
|
+
if (!holding) lower();
|
|
409
|
+
}, [holding, lower]);
|
|
381
410
|
// A panel on its way out after `open` turned false is already closed: a press on
|
|
382
411
|
// its fading backdrop must not report a second close.
|
|
383
412
|
const backdropClose = useBackdropClose(active ? requestClose : () => {});
|
|
@@ -385,8 +414,8 @@ function ModalPanel({
|
|
|
385
414
|
// this the only way out of a dialog is finding its close button — and Back, the
|
|
386
415
|
// gesture everyone reaches for, navigated the page underneath instead (#172).
|
|
387
416
|
// Held only while open: a closed `open={false}` dialog must not own a history entry.
|
|
388
|
-
// `held`: a Back while busy is refused, and the entry it took is put back.
|
|
389
|
-
useOverlayHistory(active && ownHistoryEntry, requestClose, { held:
|
|
417
|
+
// `held`: a Back while busy or held is refused, and the entry it took is put back.
|
|
418
|
+
useOverlayHistory(active && ownHistoryEntry, requestClose, { held: holding });
|
|
390
419
|
|
|
391
420
|
// Through the shared hook and not by hand: this component's own save/restore copy
|
|
392
421
|
// was one half of the pair that left the page permanently unscrollable when a dialog
|
|
@@ -59,7 +59,7 @@ export interface NavPillsProps<T extends string> extends Omit<
|
|
|
59
59
|
* 0.49: `wrap` (default) puts the pills on as many rows as they need. `scroll` keeps
|
|
60
60
|
* them on ONE row that scrolls sideways — faded at a cut edge, the current pill
|
|
61
61
|
* scrolled into view (the row only, never the page) — for a strip that must stay one
|
|
62
|
-
* line high, as
|
|
62
|
+
* line high, as a section's pages under a page title on a phone.
|
|
63
63
|
*/
|
|
64
64
|
layout?: "wrap" | "scroll";
|
|
65
65
|
/** The `<nav>` (or the `<div role="group">`) — hence `HTMLElement` (0.34, API-2,
|
|
@@ -222,9 +222,10 @@ const asTyped = (text: string) => text;
|
|
|
222
222
|
|
|
223
223
|
// ── The money guard (kastlan 5, Kurvenschmiede) ─────────────────────────────────
|
|
224
224
|
|
|
225
|
-
// The codes the kit's own currency pickers
|
|
226
|
-
// "EUR", "Kosten CHF".
|
|
227
|
-
//
|
|
225
|
+
// The codes the kit's own currency pickers know, as whole words: "Amount (CHF)",
|
|
226
|
+
// "EUR", "Kosten CHF". A `retired` one too (BGN): a label that names it is still money,
|
|
227
|
+
// whatever the pickers offer. Not every ISO 4217 code — "ALL", "TOP" and "PEN" are codes
|
|
228
|
+
// too, and an upper-case label would trip on them.
|
|
228
229
|
const CURRENCY_CODE = new RegExp(`(?:^|[^A-Za-z])(?:${CURRENCIES.map((c) => c.code).join("|")})(?![A-Za-z])`);
|
|
229
230
|
// Symbols no unit of measure is written with. Not "kr", "R" or "Fr": those are words
|
|
230
231
|
// and units as often as they are money.
|
|
@@ -212,7 +212,12 @@ export function anchoredBand(extent: readonly [number, number] | undefined): [nu
|
|
|
212
212
|
* it in its neighbour's colour on each click. Resolving the colours on the full list
|
|
213
213
|
* and filtering after keeps each series in the colour its legend entry shows.
|
|
214
214
|
*/
|
|
215
|
-
|
|
215
|
+
// Generic so a typed list comes back typed (0.53): a chart of `Month` rows takes it, and
|
|
216
|
+
// its `dot`s still read `point.row` as a `Month`.
|
|
217
|
+
export function visibleSeries<TRow extends object>(
|
|
218
|
+
series: readonly SeriesChartSeries<TRow>[],
|
|
219
|
+
hidden: ReadonlySet<string>,
|
|
220
|
+
): SeriesChartSeries<TRow>[] {
|
|
216
221
|
return series
|
|
217
222
|
.map((entry, index) => ({ ...entry, color: entry.color ?? paletteFor(index) }))
|
|
218
223
|
.filter((entry) => !hidden.has(entry.key));
|
|
@@ -129,7 +129,14 @@ import { useChartEdge, useEdgesInForce } from "./chart-edge";
|
|
|
129
129
|
import { cn } from "../lib/cn";
|
|
130
130
|
import { formatWithMinus } from "../lib/minus";
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
/**
|
|
133
|
+
* One line, area or bar run of a {@link SeriesChart}.
|
|
134
|
+
*
|
|
135
|
+
* `TRow` is the chart's row type (0.53, keksdose after 0.52), and only `dot` reads it: its
|
|
136
|
+
* `point.row` is that type. Without a type argument a series is what it was, and a chart
|
|
137
|
+
* of typed rows still takes it — see {@link SeriesChartProps}.
|
|
138
|
+
*/
|
|
139
|
+
export interface SeriesChartSeries<TRow extends object = object> {
|
|
133
140
|
/** The row key this line reads — and the `--color-<key>` it is painted with, so it
|
|
134
141
|
* must be a CSS identifier. Build keys out of data with {@link seriesKey}. */
|
|
135
142
|
key: string;
|
|
@@ -177,8 +184,9 @@ export interface SeriesChartSeries {
|
|
|
177
184
|
* point: `true` for the default dot, `false`/`null` for none, or any SVG node drawn
|
|
178
185
|
* as-is at `point.cx`/`point.cy` — a buy/sell marker on keksdose's paper price.
|
|
179
186
|
* Never called for a point with no value. Bars have no points, and ignore it.
|
|
187
|
+
* `point.row` is the caller's own row, typed as the chart's rows are (0.53).
|
|
180
188
|
*/
|
|
181
|
-
dot?: boolean | ((point: SeriesChartPoint) => ReactNode);
|
|
189
|
+
dot?: boolean | ((point: SeriesChartPoint<TRow>) => ReactNode);
|
|
182
190
|
/** Line (or area outline) weight in px. Default 2, or 1.5 for a `step`. keksdose
|
|
183
191
|
* draws the subject of a chart at 2.5 and the reference it is read against at 1.5. */
|
|
184
192
|
strokeWidth?: number;
|
|
@@ -213,14 +221,16 @@ export interface SeriesChartSeries {
|
|
|
213
221
|
/** What a series draws. See {@link SeriesChartSeries.type}. */
|
|
214
222
|
export type SeriesChartType = "line" | "bar" | "area";
|
|
215
223
|
|
|
216
|
-
/** One point of a series, as a `dot` function is handed it.
|
|
217
|
-
|
|
224
|
+
/** One point of a series, as a `dot` function is handed it. `TRow` is the chart's row
|
|
225
|
+
* type (0.53); without one, `row` is a {@link SeriesChartRow}, as before. */
|
|
226
|
+
export interface SeriesChartPoint<TRow extends object = object> {
|
|
218
227
|
/** The series' key. */
|
|
219
228
|
key: string;
|
|
220
229
|
/** The row's position in `rows`. */
|
|
221
230
|
index: number;
|
|
222
|
-
/** The caller's own row, with everything the chart did not plot still in it
|
|
223
|
-
|
|
231
|
+
/** The caller's own row, with everything the chart did not plot still in it: the very
|
|
232
|
+
* object in `rows`, typed as `rows` is. */
|
|
233
|
+
row: SeriesChartRowOf<TRow>;
|
|
224
234
|
/** The abscissa in the caller's terms: the number, the category, or (time) a Date. */
|
|
225
235
|
x: SeriesChartXValue;
|
|
226
236
|
value: number;
|
|
@@ -233,9 +243,32 @@ export interface SeriesChartPoint {
|
|
|
233
243
|
|
|
234
244
|
/** A row of a series chart: one abscissa value and the series' values at it. Anything
|
|
235
245
|
* else in it (a raw ISO period for a drilldown) rides along untouched and is handed
|
|
236
|
-
* back in {@link SeriesChartPoint.row} and {@link SeriesChartHit.row}
|
|
246
|
+
* back in {@link SeriesChartPoint.row} and {@link SeriesChartHit.row} — typed as the
|
|
247
|
+
* caller's rows are since 0.53, and as this where their type names no keys. */
|
|
237
248
|
export type SeriesChartRow = Readonly<Record<string, unknown>>;
|
|
238
249
|
|
|
250
|
+
/**
|
|
251
|
+
* The row a hit or a point hands back, for a chart whose rows are `TRow[]` (0.53, keksdose
|
|
252
|
+
* after 0.52): `TRow` itself, so a drilldown reads `hit.row.rawPeriod` as the string it
|
|
253
|
+
* is. A {@link SeriesChartRow} where `TRow` names no keys — `object`, the default, which
|
|
254
|
+
* keeps `SeriesChartHit` and `SeriesChartPoint` without a type argument what they were, and
|
|
255
|
+
* the `object[]` rows an app passes today; `{}`; and `any` or `never` (`rows={[]}`), which
|
|
256
|
+
* are both assignable to `null` while no object type is.
|
|
257
|
+
*
|
|
258
|
+
* Both tests take `TRow` in the CHECKED position, never in the `extends` one, so TypeScript
|
|
259
|
+
* measures every type below as covariant in it (`SeriesChartSeries`, through `dot`'s
|
|
260
|
+
* parameter, contravariant) and relates two of them by their type arguments alone: a
|
|
261
|
+
* `SeriesChartHit<Month>` is a `SeriesChartHit`, and a handler or a series typed without an
|
|
262
|
+
* argument fits a chart of `Month` rows, though an interface has no index signature and is
|
|
263
|
+
* not, field by field, a `SeriesChartRow`. Written `object extends TRow ?`, the type is
|
|
264
|
+
* invariant and both of those stop type-checking.
|
|
265
|
+
*/
|
|
266
|
+
type SeriesChartRowOf<TRow> = [TRow] extends [null]
|
|
267
|
+
? SeriesChartRow
|
|
268
|
+
: [keyof TRow] extends [never]
|
|
269
|
+
? SeriesChartRow
|
|
270
|
+
: TRow;
|
|
271
|
+
|
|
239
272
|
/** An abscissa value: a number (`x.type: "number"`), a category (a string, a number or
|
|
240
273
|
* a Date standing for a period), or a time (a Date, epoch ms or an ISO string). */
|
|
241
274
|
export type SeriesChartXValue = number | string | Date;
|
|
@@ -551,29 +584,42 @@ export interface SeriesChartMarker {
|
|
|
551
584
|
r?: number;
|
|
552
585
|
}
|
|
553
586
|
|
|
554
|
-
/** What a click on the plot picked.
|
|
555
|
-
|
|
587
|
+
/** What a click on the plot picked. `TRow` is the chart's row type (0.53); without one,
|
|
588
|
+
* `row` is a {@link SeriesChartRow}, as before. */
|
|
589
|
+
export interface SeriesChartHit<TRow extends object = object> {
|
|
556
590
|
/** The row's position in `rows`. */
|
|
557
591
|
index: number;
|
|
558
|
-
/** The caller's own row — with its `rawPeriod`, id or whatever the drilldown needs
|
|
559
|
-
|
|
592
|
+
/** The caller's own row — with its `rawPeriod`, id or whatever the drilldown needs: the
|
|
593
|
+
* very object in `rows`, typed as `rows` is, so it is read without a cast. */
|
|
594
|
+
row: SeriesChartRowOf<TRow>;
|
|
560
595
|
x: SeriesChartXValue;
|
|
561
596
|
/** The series, when the click landed ON a bar. A click anywhere else picks the slot
|
|
562
597
|
* (the period), not a series. */
|
|
563
598
|
key?: string;
|
|
564
599
|
}
|
|
565
600
|
|
|
566
|
-
|
|
601
|
+
/**
|
|
602
|
+
* The props of {@link SeriesChart} and {@link StaticSeriesChart}, for rows of type `TRow`.
|
|
603
|
+
*
|
|
604
|
+
* `TRow` is inferred from `rows` (0.53, keksdose after 0.52), and what the chart hands back
|
|
605
|
+
* — `hit.row` in `onPointClick`, `point.row` in a series' `dot` — is that type, so a
|
|
606
|
+
* drilldown reads its own fields without narrowing an `unknown`. Without a type argument,
|
|
607
|
+
* `SeriesChartProps` is what it was: any object is a row, and a row comes back as a
|
|
608
|
+
* {@link SeriesChartRow}. So is `ComponentProps<typeof SeriesChart>`, which reads the
|
|
609
|
+
* constraint, `object`. A series, a handler or a `dot` typed without an argument fits a
|
|
610
|
+
* chart of typed rows too.
|
|
611
|
+
*/
|
|
612
|
+
export interface SeriesChartProps<TRow extends object = object> {
|
|
567
613
|
/**
|
|
568
614
|
* One row per abscissa value. A missing key is a hole (see `connectNulls`).
|
|
569
615
|
*
|
|
570
616
|
* Any object (0.52, refactor audit 2026-10-30, API-2): an `interface` has no index
|
|
571
617
|
* signature, so a typed sweep did not fit `SeriesChartRow` and Kurvenschmiede cast its
|
|
572
618
|
* Ackermann rows `as unknown as Record<string, number>[]`. The chart reads the keys
|
|
573
|
-
* `series` and `x` name, and hands the row back as
|
|
619
|
+
* `series` and `x` name, and hands the row back typed as these are (0.53).
|
|
574
620
|
*/
|
|
575
|
-
rows: readonly
|
|
576
|
-
series: readonly SeriesChartSeries[];
|
|
621
|
+
rows: readonly TRow[];
|
|
622
|
+
series: readonly SeriesChartSeries<TRow>[];
|
|
577
623
|
/** Default: one untitled axis, `"y"`. */
|
|
578
624
|
axes?: readonly SeriesChartAxis[];
|
|
579
625
|
x?: SeriesChartX;
|
|
@@ -628,7 +674,7 @@ export interface SeriesChartProps {
|
|
|
628
674
|
* a chart without bars, named "period — series: value", behind ONE tab stop. With this
|
|
629
675
|
* they are buttons that Enter and Space activate; without, images to read.
|
|
630
676
|
*/
|
|
631
|
-
onPointClick?: (hit: SeriesChartHit) => void;
|
|
677
|
+
onPointClick?: (hit: SeriesChartHit<TRow>) => void;
|
|
632
678
|
/** Where the tooltip may go: for a chart inside a scroll wrapper. See
|
|
633
679
|
* {@link SeriesChartTooltip}. */
|
|
634
680
|
tooltip?: SeriesChartTooltip;
|
|
@@ -760,8 +806,10 @@ export interface SeriesLegendAxes {
|
|
|
760
806
|
* series whose axis is not drawn says the unit that axis would have: "Velocity (mm/s)"
|
|
761
807
|
* — see {@link SeriesChartAxis.unit}. Without them, the labels are as given.
|
|
762
808
|
*/
|
|
763
|
-
export function seriesLegendEntries(
|
|
764
|
-
|
|
809
|
+
export function seriesLegendEntries<TRow extends object>(
|
|
810
|
+
// Generic only so a typed list fits (0.53): `SeriesChartSeries<Month>` is not a
|
|
811
|
+
// `SeriesChartSeries`, whose `dot` would be handed rows that are not `Month`s.
|
|
812
|
+
series: readonly SeriesChartSeries<TRow>[],
|
|
765
813
|
{ axes, budgeted }: SeriesLegendAxes = {},
|
|
766
814
|
): LegendEntry[] {
|
|
767
815
|
const undrawn = new Map<string, string | undefined>();
|
|
@@ -1819,7 +1867,12 @@ const ZoomablePlot = withChartZoom(SeriesPlot);
|
|
|
1819
1867
|
* category or time chart otherwise re-maps every row on every render, and `maxRows`'
|
|
1820
1868
|
* envelope would be worked out again with it.
|
|
1821
1869
|
*/
|
|
1822
|
-
function usePlotProps(
|
|
1870
|
+
function usePlotProps<TRow extends object>(typed: SeriesChartProps<TRow>): PlotProps {
|
|
1871
|
+
// `TRow` types only what the caller is handed back, and that is its own row objects,
|
|
1872
|
+
// untouched (0.53): the plot reads them as the `object` rows they are. Through `unknown`
|
|
1873
|
+
// because the two are not comparable for an unresolved `TRow`: `SeriesChartSeries` is
|
|
1874
|
+
// contravariant in it (its `dot` is handed rows) and `rows` covariant.
|
|
1875
|
+
const props = typed as unknown as SeriesChartProps;
|
|
1823
1876
|
const x = props.x ?? {};
|
|
1824
1877
|
const { rows } = props;
|
|
1825
1878
|
const { type, key } = x;
|
|
@@ -1833,7 +1886,7 @@ function usePlotProps(props: SeriesChartProps): PlotProps {
|
|
|
1833
1886
|
* button. For a thumbnail, a print view, or a chart the consumer wraps in its own
|
|
1834
1887
|
* interaction.
|
|
1835
1888
|
*/
|
|
1836
|
-
export function StaticSeriesChart(props: SeriesChartProps) {
|
|
1889
|
+
export function StaticSeriesChart<TRow extends object = object>(props: SeriesChartProps<TRow>) {
|
|
1837
1890
|
return <SeriesPlot {...usePlotProps(props)} />;
|
|
1838
1891
|
}
|
|
1839
1892
|
|
|
@@ -1842,8 +1895,12 @@ export function StaticSeriesChart(props: SeriesChartProps) {
|
|
|
1842
1895
|
* axes), double-click or press the reset button to go back; wrap several in
|
|
1843
1896
|
* `SharedXZoom` to move their x windows together. Which axes zoom depends on the marks
|
|
1844
1897
|
* — see {@link SeriesChartProps.zoomAxes}.
|
|
1898
|
+
*
|
|
1899
|
+
* Generic in its rows (0.53): `hit.row` and a `dot`'s `point.row` are typed as `rows` is —
|
|
1900
|
+
* see {@link SeriesChartProps}. A plain function, so the generic survives: a `memo` or a
|
|
1901
|
+
* `forwardRef` around it would read `TRow` as its constraint.
|
|
1845
1902
|
*/
|
|
1846
|
-
export function SeriesChart(props: SeriesChartProps) {
|
|
1903
|
+
export function SeriesChart<TRow extends object = object>(props: SeriesChartProps<TRow>) {
|
|
1847
1904
|
// The zoom fits the PLOTTED rows — slot indices and epoch ms, not the caller's labels.
|
|
1848
1905
|
return <ZoomablePlot {...usePlotProps(props)} />;
|
|
1849
1906
|
}
|
|
@@ -229,8 +229,27 @@ export interface StatTileProps extends Omit<ComponentPropsWithRef<"div">, "child
|
|
|
229
229
|
/** Anything below the rest — a progress bar, a link row. */
|
|
230
230
|
footer?: ReactNode;
|
|
231
231
|
/** Tag every figure `data-private`, so the host's demo-mode rule blurs it — the
|
|
232
|
-
* same attribute `Tooltip redact` and the chart tooltip's value cell carry.
|
|
232
|
+
* same attribute `Tooltip redact` and the chart tooltip's value cell carry.
|
|
233
|
+
* The figures are the tile's own: value, delta, projection, sub-values and the
|
|
234
|
+
* trend. The `description` and `footer` are the app's nodes and stay readable;
|
|
235
|
+
* see {@link StatTileProps.descriptionSensitive} for the ones that carry an amount. */
|
|
233
236
|
sensitive?: boolean;
|
|
237
|
+
/**
|
|
238
|
+
* Tag the `description` line `data-private` too, as `sensitive` tags the figures.
|
|
239
|
+
* For a description that names an amount: keksdose's tax reserve tile states its
|
|
240
|
+
* basis there ("…applied to €42,000"), and its report KpiCard puts a `hintSensitive`
|
|
241
|
+
* hint there — both tag their own `<span data-private>` today
|
|
242
|
+
* (reports/tabs/tax-tab.tsx, reports/charts/kpi-card.tsx). Separate from
|
|
243
|
+
* `sensitive`, so a description of words ("Last 12 months") on a money tile stays
|
|
244
|
+
* readable in demo mode. keksdose audit follow-up, 2026-11-01.
|
|
245
|
+
*/
|
|
246
|
+
descriptionSensitive?: boolean;
|
|
247
|
+
/**
|
|
248
|
+
* Tag the `footer` `data-private`, as `sensitive` tags the figures — for a footer
|
|
249
|
+
* that carries an amount: keksdose's tax reserve tile's shortfall line ("Set aside
|
|
250
|
+
* €1,200 more…"), which it tags by hand. keksdose audit follow-up, 2026-11-01.
|
|
251
|
+
*/
|
|
252
|
+
footerSensitive?: boolean;
|
|
234
253
|
/** Skeleton in place of the value; delta, sub-values and trend are withheld. */
|
|
235
254
|
loading?: boolean;
|
|
236
255
|
/** `"sm"` for dense grids (a metrics wall); `"md"` the default KPI. */
|
|
@@ -317,6 +336,8 @@ export function StatTile({
|
|
|
317
336
|
icon,
|
|
318
337
|
footer,
|
|
319
338
|
sensitive = false,
|
|
339
|
+
descriptionSensitive = false,
|
|
340
|
+
footerSensitive = false,
|
|
320
341
|
loading = false,
|
|
321
342
|
size = "md",
|
|
322
343
|
variant = "card",
|
|
@@ -551,7 +572,11 @@ export function StatTile({
|
|
|
551
572
|
)}
|
|
552
573
|
|
|
553
574
|
{deltaNode}
|
|
554
|
-
{description != null &&
|
|
575
|
+
{description != null && (
|
|
576
|
+
<div data-private={descriptionSensitive ? "" : undefined} className="mt-1 text-xs text-muted">
|
|
577
|
+
{description}
|
|
578
|
+
</div>
|
|
579
|
+
)}
|
|
555
580
|
{!large && graphic && <div className="mt-2">{graphic}</div>}
|
|
556
581
|
|
|
557
582
|
{!loading && ((large && projections.length > 0) || (subValues && subValues.length > 0)) && (
|
|
@@ -572,7 +597,11 @@ export function StatTile({
|
|
|
572
597
|
</div>
|
|
573
598
|
)}
|
|
574
599
|
|
|
575
|
-
{footer != null &&
|
|
600
|
+
{footer != null && (
|
|
601
|
+
<div data-private={footerSensitive ? "" : undefined} className="mt-2">
|
|
602
|
+
{footer}
|
|
603
|
+
</div>
|
|
604
|
+
)}
|
|
576
605
|
</Card>
|
|
577
606
|
);
|
|
578
607
|
}
|
|
@@ -13,13 +13,18 @@ export interface TreemapLabels {
|
|
|
13
13
|
* `valueFormatter` when that returns text, else as the number in the provider's
|
|
14
14
|
* locale; `share` is the tile's share of what is drawn, already a formatted percent
|
|
15
15
|
* ("60 %" in German, "60%" in English) — the function only arranges them.
|
|
16
|
+
*
|
|
17
|
+
* `share` is `""` when the map's `shareInValue` says the formatter's text already holds
|
|
18
|
+
* it (0.53): leave out whatever frames it, as every catalogue drops its " (share)" —
|
|
19
|
+
* "Migros: CHF 412.30 · 12%", not "… · 12% ()".
|
|
16
20
|
*/
|
|
17
21
|
tile: (name: string, value: string, share: string) => string;
|
|
18
22
|
/**
|
|
19
23
|
* A group's accessible name (0.52, `TreemapNode.children`): its frame is a stop of the
|
|
20
24
|
* map's keyboard before its tiles, so a reader hears the group and its total once, on the
|
|
21
25
|
* way in. `value` and `share` as for {@link TreemapLabels.tile}, of the group's tiles
|
|
22
|
-
* together; the word "group" tells it from
|
|
26
|
+
* together, the share `""` under `shareInValue` as there; the word "group" tells it from
|
|
27
|
+
* a tile of the same shape.
|
|
23
28
|
*/
|
|
24
29
|
group: (name: string, value: string, share: string) => string;
|
|
25
30
|
/**
|
|
@@ -32,8 +37,11 @@ export interface TreemapLabels {
|
|
|
32
37
|
inGroup: (name: string, group: string) => string;
|
|
33
38
|
}
|
|
34
39
|
|
|
40
|
+
// The share in brackets only when there is one: `""` under the map's `shareInValue` (0.53,
|
|
41
|
+
// keksdose after 0.52), which the catalogues in src/i18n/locales follow, each in its own
|
|
42
|
+
// typography.
|
|
35
43
|
export const DEFAULT_TREEMAP_LABELS: TreemapLabels = {
|
|
36
|
-
tile: (name, value, share) => `${name}: ${value} (${share})`,
|
|
37
|
-
group: (name, value, share) => `${name}, group: ${value} (${share})`,
|
|
44
|
+
tile: (name, value, share) => `${name}: ${value}${share ? ` (${share})` : ""}`,
|
|
45
|
+
group: (name, value, share) => `${name}, group: ${value}${share ? ` (${share})` : ""}`,
|
|
38
46
|
inGroup: (name, group) => `${name}, in ${group}`,
|
|
39
47
|
};
|