@forgeroute/ui 0.4.0 → 0.4.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeroute/ui",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=22.0.0",
6
6
  "@angular/core": ">=22.0.0",
@@ -1,4 +1,37 @@
1
1
  import * as i0 from "@angular/core";
2
+ /**
3
+ * A darker shade (each channel x 0.8) of a hex (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`) or comma-separated `rgb()` / `rgba()` colour,
4
+ * returned as `rgb(r, g, b)` or `rgba(r, g, b, a)`. Returns `null` for anything else (named colours, `var(--x)`, `hsl()`, out-of-range
5
+ * channels, malformed text) because those can't be computed without a browser.
6
+ */
7
+ declare function darkenColor(input: string): string | null;
8
+ /** How the chart writes numbers: one for value labels and tooltips, one for the y axis ticks. */
9
+ interface ValueFormatter {
10
+ value: (n: number) => string;
11
+ tick: (n: number) => string;
12
+ }
13
+ type BarChartValueFormat = 'number' | 'currency' | 'percent';
14
+ /** One series of a grouped / stacked bar chart: its legend name, one value per label, and optional colours (any CSS colour). */
15
+ interface BarChartSeries {
16
+ name: string;
17
+ values: number[];
18
+ color?: string;
19
+ hoverColor?: string;
20
+ }
21
+ /**
22
+ * Default series colours of a multi-series chart (cycled). Same values as the pie chart's palette (kept as a local copy so a bar-only
23
+ * app never pulls the pie chart's chart.js parts into its bundle); each has at least 3:1 contrast against white.
24
+ */
25
+ declare const BAR_CHART_SERIES_PALETTE: readonly string[];
26
+ /** A multi-series chart draws value labels only up to this many bars in total (labels x series); denser groups overlap. */
27
+ declare const BAR_CHART_MAX_GROUPED_VALUE_LABELS = 24;
28
+ /** A series after clean-up: trimmed or fallback name, colours resolved, exactly one number per label. */
29
+ interface ResolvedSeries {
30
+ name: string;
31
+ data: number[];
32
+ color: string;
33
+ hover: string;
34
+ }
2
35
  /**
3
36
  * Bar chart of one series (e.g. calls per day). Pass the category `labels` and the matching `values`.
4
37
  *
@@ -10,6 +43,54 @@ import * as i0 from "@angular/core";
10
43
  * - With an `ariaLabel` the chart is an image for assistive tech (`role="img"` + that label; without a label no role is set, as an
11
44
  * unnamed image is an accessibility error); render the same figures as text next to it
12
45
  * (e.g. a visually hidden list) when the exact values matter.
46
+ * - Per-bar colours: `colors` gives bar i the colour `colors[i]` (any CSS colour, passed through untouched). Bars without an entry
47
+ * (list shorter than the bars, or a blank/whitespace entry) use `color`; extra entries are ignored. With no `colors` every bar uses
48
+ * `color` exactly as before. Colours are decorative only: never rely on colour alone, the values are always available as the
49
+ * value labels and tooltips (and in your own text alternative).
50
+ *
51
+ * <ui-bar-chart [labels]="['New', 'Won', 'Lost']" [values]="[4, 2, 1]" [colors]="['#3b82f6', '#16a34a', '#dc2626']" />
52
+ *
53
+ * - Number format: `valueFormat` ('number' default | 'currency' | 'percent') writes the value labels, the tooltip and the y axis ticks.
54
+ * 'number' is the original behaviour (raw numbers, whole-number ticks, "<value> <unit>" tooltip); `currency`, `locale` and `decimals`
55
+ * are ignored for it.
56
+ *
57
+ * <ui-bar-chart [labels]="months" [values]="revenue" valueFormat="currency" ariaLabel="Revenue by month" />
58
+ * <ui-bar-chart [labels]="months" [values]="revenue" valueFormat="currency" currency="EUR" locale="de-DE" [decimals]="0" />
59
+ * <ui-bar-chart [labels]="months" [values]="rates" valueFormat="percent" />
60
+ *
61
+ * - 'currency': `$1,234.50` (`currency` ISO 4217, default USD; `locale` default en-US). Zero is `$0.00`, negatives `-$5.00`.
62
+ * Default decimals are the currency's own (2 for USD). Axis ticks are not forced to whole numbers (values may be fractional);
63
+ * with no `decimals` a whole-number tick drops its cents (`$500`) and a fractional one keeps them (`$0.50`); an explicit `decimals`
64
+ * is used for every tick.
65
+ * - 'percent': the values are ALREADY percentages on the 0-100 scale, not fractions: 12.5 is `12.5%`, 100 is `100%`. Default is up
66
+ * to 1 decimal (12 -> `12%`, 12.5 -> `12.5%`); `decimals` fixes the number of decimals (0 -> `13%`). Ticks use the same format.
67
+ * - The tooltip of 'currency' / 'percent' is just the formatted figure (`$1,234.50`, `12.5%`); `unitSingular` / `unitPlural` are
68
+ * ignored. With 'number' the unit text is unchanged.
69
+ * - `decimals` (integer, clamped to 0-6; null or non-numeric = format default). An invalid `currency` or `locale` never throws:
70
+ * it falls back to USD / en-US. NaN and Infinity read as "NaN" / "Infinity" in the tooltip and get no value label.
71
+ * - `ariaLabel` is never formatted by the chart: if the description carries figures, format them in the host.
72
+ *
73
+ * - Hover: a bar with its own colour darkens it (see `darkenColor`: hex or comma-separated rgb()/rgba() get a shade 20% darker; a
74
+ * named colour, `var(--x)` or other unparseable value stays unchanged on hover). Bars using the fallback `color` use `hoverColor`.
75
+ * - Several series (grouped or stacked): pass `series` (it takes precedence over `values`, `color`, `hoverColor` and `colors`, which are
76
+ * ignored). Every series shares `labels`; bars of one label sit side by side (`stacked` = true piles them up instead).
77
+ *
78
+ * <ui-bar-chart [labels]="months"
79
+ * [series]="[{ name: 'Income', values: income, color: '#22c55e' }, { name: 'Expenses', values: expenses, color: '#ef4444' }]"
80
+ * valueFormat="currency" ariaLabel="Income and expenses by month" />
81
+ *
82
+ * - A series has `name` (blank = "Series N"), `values` (one per label: missing = 0, extra ignored, a non-number reads as 0; NaN and
83
+ * Infinity are kept as in a single series), optional `color` (default: `BAR_CHART_SERIES_PALETTE`, cycled) and `hoverColor`
84
+ * (default: a darker shade of the colour, see `darkenColor`; unparseable colours keep the colour on hover).
85
+ * - Legend: an HTML list below the chart (swatch + name, hidden from assistive tech, wraps on phones), shown for any non-empty
86
+ * `series` while `showLegend` is true (default). A single-series chart (`values`) never shows one.
87
+ * - Tooltip: "<series name>: <value>" with the same `valueFormat` / unit rules as above (units per value for 'number').
88
+ * - Value labels: each bar's value above it (`showValues`), only while labels x series <= 24 bars (`BAR_CHART_MAX_GROUPED_VALUE_LABELS`)
89
+ * and never when `stacked` (the segments would overlap their labels). Tooltips and the hidden list always carry the values.
90
+ * - Accessibility: the chart wrapper is unchanged (`role="img"` + `ariaLabel`); additionally a visually hidden list holds one entry per
91
+ * label and series ("Jan, Income: $100.00"), label by label, written as plain text.
92
+ * - Changing `series`, `stacked` or the formats at runtime updates the same chart; going back to no `series` restores the single-series chart.
93
+ *
13
94
  * - Height is `height` px (shorter on phones: 200px up to 768px wide, 160px up to 480px wide, when the default 240 is used);
14
95
  * the width follows the container. Needs the optional peer dependency `chart.js` (>= 4).
15
96
  */
@@ -20,7 +101,9 @@ export declare class BarChartComponent {
20
101
  readonly values: import("@angular/core").InputSignal<number[]>;
21
102
  /** Bar colour (any CSS colour). */
22
103
  readonly color: import("@angular/core").InputSignal<string>;
23
- /** Bar colour while hovered. */
104
+ /** Colour per bar by index (any CSS colour); missing or blank entries fall back to `color`, extra entries are ignored. Decorative only. */
105
+ readonly colors: import("@angular/core").InputSignal<string[]>;
106
+ /** Hover colour of the bars that use `color` (bars with their own `colors` entry get a darker shade of it). */
24
107
  readonly hoverColor: import("@angular/core").InputSignal<string>;
25
108
  /** Chart height in px. */
26
109
  readonly height: import("@angular/core").InputSignal<number>;
@@ -32,17 +115,48 @@ export declare class BarChartComponent {
32
115
  readonly unitSingular: import("@angular/core").InputSignal<string>;
33
116
  /** Tooltip unit for any other value (e.g. "calls"). */
34
117
  readonly unitPlural: import("@angular/core").InputSignal<string>;
118
+ /** How numbers are written on the value labels, tooltip and y axis: 'number' (default, raw numbers), 'currency' or 'percent' (values on the 0-100 scale). */
119
+ readonly valueFormat: import("@angular/core").InputSignal<BarChartValueFormat>;
120
+ /** ISO 4217 currency code for `valueFormat="currency"` (default USD; an invalid code falls back to USD). */
121
+ readonly currency: import("@angular/core").InputSignal<string>;
122
+ /** BCP 47 locale for 'currency' / 'percent' (default en-US; an invalid tag falls back to en-US). */
123
+ readonly locale: import("@angular/core").InputSignal<string>;
124
+ /** Fixed number of decimals for 'currency' / 'percent' (integer 0-6). null = the format's default. Ignored for 'number'. */
125
+ readonly decimals: import("@angular/core").InputSignal<number | null>;
126
+ /** Several series sharing `labels` (grouped side by side, or stacked). Non-empty = takes precedence over `values`, `color`, `hoverColor` and `colors`. Default: none (single series). */
127
+ readonly series: import("@angular/core").InputSignal<BarChartSeries[]>;
128
+ /** Multi-series only: true stacks the series on top of each other instead of grouping them side by side. */
129
+ readonly stacked: import("@angular/core").InputSignal<boolean>;
130
+ /** Multi-series only: show the HTML legend below the chart (a single series never has one). */
131
+ readonly showLegend: import("@angular/core").InputSignal<boolean>;
35
132
  private readonly canvas;
36
133
  private readonly zone;
37
134
  private chart;
135
+ private numberTickCallback;
136
+ /** Whether the chart currently holds grouped datasets (null = not created yet). */
137
+ private multiMode;
138
+ /** null for 'number' (original behaviour); otherwise the formatters, rebuilt only when an input changes. */
139
+ protected readonly formatter: import("@angular/core").Signal<ValueFormatter | null>;
140
+ /** The `series` input cleaned up for drawing; empty = single-series mode. */
141
+ protected readonly resolvedSeries: import("@angular/core").Signal<ResolvedSeries[]>;
142
+ protected readonly legendVisible: import("@angular/core").Signal<boolean>;
143
+ /** Visually hidden text of a multi-series chart: one entry per label and series, label by label. */
144
+ protected readonly srRows: import("@angular/core").Signal<string[]>;
38
145
  private readonly tooltipText;
39
- protected readonly series: import("@angular/core").Signal<{
146
+ protected readonly model: import("@angular/core").Signal<{
147
+ groups: ResolvedSeries[];
148
+ stacked: boolean;
40
149
  labels: string[];
41
150
  data: number[];
42
151
  color: string;
152
+ colors: string[];
43
153
  hoverColor: string;
44
154
  showValues: boolean;
155
+ formatter: ValueFormatter | null;
45
156
  }>;
157
+ /** Fill and hover colours: single values when no `colors` are set (unchanged behaviour), otherwise one entry per bar. */
158
+ private static fills;
159
+ private static groupDataset;
46
160
  constructor();
47
161
  static ɵfac: i0.ɵɵFactoryDeclaration<BarChartComponent, never>;
48
162
  static ɵcmp: i0.ɵɵComponentDeclaration<BarChartComponent, "ui-bar-chart", never, {
@@ -61,6 +175,11 @@ export declare class BarChartComponent {
61
175
  "required": false;
62
176
  "isSignal": true;
63
177
  };
178
+ "colors": {
179
+ "alias": "colors";
180
+ "required": false;
181
+ "isSignal": true;
182
+ };
64
183
  "hoverColor": {
65
184
  "alias": "hoverColor";
66
185
  "required": false;
@@ -91,5 +210,172 @@ export declare class BarChartComponent {
91
210
  "required": false;
92
211
  "isSignal": true;
93
212
  };
213
+ "valueFormat": {
214
+ "alias": "valueFormat";
215
+ "required": false;
216
+ "isSignal": true;
217
+ };
218
+ "currency": {
219
+ "alias": "currency";
220
+ "required": false;
221
+ "isSignal": true;
222
+ };
223
+ "locale": {
224
+ "alias": "locale";
225
+ "required": false;
226
+ "isSignal": true;
227
+ };
228
+ "decimals": {
229
+ "alias": "decimals";
230
+ "required": false;
231
+ "isSignal": true;
232
+ };
233
+ "series": {
234
+ "alias": "series";
235
+ "required": false;
236
+ "isSignal": true;
237
+ };
238
+ "stacked": {
239
+ "alias": "stacked";
240
+ "required": false;
241
+ "isSignal": true;
242
+ };
243
+ "showLegend": {
244
+ "alias": "showLegend";
245
+ "required": false;
246
+ "isSignal": true;
247
+ };
248
+ }, {}, never, never, true, never>;
249
+ }
250
+ /** Built-in slice colours (cycled). Every colour has at least 3:1 contrast against white and against a light page grey (#f7f7f5). */
251
+ declare const PIE_CHART_PALETTE: readonly string[];
252
+ /** One drawn slice. `index` is the position in the input `labels`, so colours stay stable when another slice drops to 0. */
253
+ interface PieSlice {
254
+ index: number;
255
+ label: string;
256
+ value: number;
257
+ percent: string;
258
+ color: string;
259
+ }
260
+ /** "62.5%", "100%", "<0.1%" (a non-zero share that rounds to nothing). Rounded to 1 decimal. */
261
+ declare function formatPercent(value: number, total: number): string;
262
+ /**
263
+ * Pie or doughnut chart of one series (e.g. payments by method).
264
+ *
265
+ * <ui-pie-chart [labels]="['Card', 'Cash']" [values]="[5, 3]" [doughnut]="true" centerLabel="Payments" centerValue="8"
266
+ * ariaLabel="Payments by method" unitSingular="payment" unitPlural="payments" />
267
+ *
268
+ * - Values must be finite and non-negative; anything else counts as 0. Slices with 0 are not drawn and not in the legend.
269
+ * Extra labels without a value count as 0; extra values without a label are ignored.
270
+ * - Empty or all-zero data shows a neutral grey ring with "No data" (no tooltip).
271
+ * - The tooltip reads "<value> <unit> (<share>)" (`unitSingular` for exactly 1, `unitPlural` otherwise; no unit = just the number;
272
+ * `showPercent=false` drops the share). The legend is real HTML below the chart and wraps on phones.
273
+ * - With an `ariaLabel` the chart is an image for assistive tech (`role="img"` + label). A visually hidden list next to it always
274
+ * gives every slice's label, value and share (plus the centre text), because an image hides its children.
275
+ * - Animation is off when the user prefers reduced motion. Height as ui-bar-chart: `height` px, default 240 shrinks to 200px up
276
+ * to 768px wide and 160px up to 480px wide. Needs the optional peer dependency `chart.js` (>= 4).
277
+ * - No click output: a slice is not keyboard reachable, so selecting is left to a list or table next to the chart.
278
+ */
279
+ export declare class PieChartComponent {
280
+ /** Slice names. */
281
+ readonly labels: import("@angular/core").InputSignal<string[]>;
282
+ /** Slice values, in the order of `labels`. */
283
+ readonly values: import("@angular/core").InputSignal<number[]>;
284
+ /** true = ring with a centre hole instead of a full pie. */
285
+ readonly doughnut: import("@angular/core").InputSignal<boolean>;
286
+ /** Per-slice CSS colours (cycled when shorter than the labels). Default: the built-in palette. */
287
+ readonly colors: import("@angular/core").InputSignal<string[]>;
288
+ /** Chart height in px (legend not included). */
289
+ readonly height: import("@angular/core").InputSignal<number>;
290
+ /** Spoken description of the chart. */
291
+ readonly ariaLabel: import("@angular/core").InputSignal<string>;
292
+ /** Show the legend below the chart. */
293
+ readonly showLegend: import("@angular/core").InputSignal<boolean>;
294
+ /** Show each slice's share in the tooltip, the legend and the hidden list. */
295
+ readonly showPercent: import("@angular/core").InputSignal<boolean>;
296
+ /** Tooltip unit for exactly 1 (e.g. "payment"). */
297
+ readonly unitSingular: import("@angular/core").InputSignal<string>;
298
+ /** Tooltip unit for any other value (e.g. "payments"). */
299
+ readonly unitPlural: import("@angular/core").InputSignal<string>;
300
+ /** Doughnut only: small text in the hole (e.g. "Total"). */
301
+ readonly centerLabel: import("@angular/core").InputSignal<string>;
302
+ /** Doughnut only: large text in the hole (e.g. "128"). */
303
+ readonly centerValue: import("@angular/core").InputSignal<string>;
304
+ private readonly canvas;
305
+ private readonly zone;
306
+ private readonly browser;
307
+ private chart;
308
+ /** Drawn slices (value > 0) with their colour and share. */
309
+ protected readonly slices: import("@angular/core").Signal<PieSlice[]>;
310
+ protected readonly isEmpty: import("@angular/core").Signal<boolean>;
311
+ protected readonly showCenter: import("@angular/core").Signal<boolean>;
312
+ /** "<value> <unit>" (`unitSingular` for exactly 1, `unitPlural` otherwise; no unit = just the number). */
313
+ protected readonly valueText: (value: number) => string;
314
+ private readonly tooltipText;
315
+ private readonly chartData;
316
+ constructor();
317
+ static ɵfac: i0.ɵɵFactoryDeclaration<PieChartComponent, never>;
318
+ static ɵcmp: i0.ɵɵComponentDeclaration<PieChartComponent, "ui-pie-chart", never, {
319
+ "labels": {
320
+ "alias": "labels";
321
+ "required": false;
322
+ "isSignal": true;
323
+ };
324
+ "values": {
325
+ "alias": "values";
326
+ "required": false;
327
+ "isSignal": true;
328
+ };
329
+ "doughnut": {
330
+ "alias": "doughnut";
331
+ "required": false;
332
+ "isSignal": true;
333
+ };
334
+ "colors": {
335
+ "alias": "colors";
336
+ "required": false;
337
+ "isSignal": true;
338
+ };
339
+ "height": {
340
+ "alias": "height";
341
+ "required": false;
342
+ "isSignal": true;
343
+ };
344
+ "ariaLabel": {
345
+ "alias": "ariaLabel";
346
+ "required": false;
347
+ "isSignal": true;
348
+ };
349
+ "showLegend": {
350
+ "alias": "showLegend";
351
+ "required": false;
352
+ "isSignal": true;
353
+ };
354
+ "showPercent": {
355
+ "alias": "showPercent";
356
+ "required": false;
357
+ "isSignal": true;
358
+ };
359
+ "unitSingular": {
360
+ "alias": "unitSingular";
361
+ "required": false;
362
+ "isSignal": true;
363
+ };
364
+ "unitPlural": {
365
+ "alias": "unitPlural";
366
+ "required": false;
367
+ "isSignal": true;
368
+ };
369
+ "centerLabel": {
370
+ "alias": "centerLabel";
371
+ "required": false;
372
+ "isSignal": true;
373
+ };
374
+ "centerValue": {
375
+ "alias": "centerValue";
376
+ "required": false;
377
+ "isSignal": true;
378
+ };
94
379
  }, {}, never, never, true, never>;
95
- }
380
+ }
381
+ export type { BarChartSeries, BarChartValueFormat };