@xiriframework/xiri-ng 0.2.48 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiriframework/xiri-ng",
3
- "version": "0.2.48",
3
+ "version": "0.3.0",
4
4
  "description": "Angular UI component library for the Xiri Framework",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -32,7 +32,7 @@
32
32
  "date-fns": "^4.3",
33
33
  "@date-fns/tz": "^1.5",
34
34
  "material-symbols": ">=0.44",
35
- "ngx-mat-select-search": "^8.0.6",
35
+ "ngx-mat-select-search": "^9.0.0",
36
36
  "rxjs": "^7.8.2",
37
37
  "echarts": "^6.1"
38
38
  },
@@ -39,7 +39,7 @@ Alle Services sind `providedIn: 'root'` (automatisch verfügbar nach Provider-Re
39
39
  ```typescript
40
40
  import {
41
41
  XiriDataService, XiriSnackbarService, XiriResponseHandlerService,
42
- XiriFormService, XiriDownloadService, XiriDateService, XiriNumberService,
42
+ XiriFormService, XiriDownloadService, XiriLocaleService,
43
43
  XiriLocalStorageService, XiriSessionStorageService, ThemeService,
44
44
  } from '@xiriframework/xiri-ng';
45
45
 
@@ -98,7 +98,7 @@ components: XiriDynData[] = [
98
98
  // card | buttonline | table | cardlink | links | form | query
99
99
  // stepper | header | list | spacer | container | infopoint | multiprogress
100
100
  // imagetext | tabs | expansion | infotext | html | stat | empty-state
101
- // timeline | page-header | section | divider | stat-grid | toolbar
101
+ // timeline | page-header | section | divider | stat-grid | multi-stat | toolbar
102
102
  // description-list | barchart (barchart benötigt zusätzlich `mode`)
103
103
  ```
104
104
 
@@ -292,7 +292,12 @@ stepperSettings = {
292
292
  ```html
293
293
  <xiri-stat [settings]="{ value: 1234, label: 'Umsatz', prefix: '€', color: 'primary',
294
294
  trend: { value: 12, direction: 'up' } }"/>
295
- <xiri-stat-grid [settings]="{ stats: [...], columns: 4, title: 'KPIs' }"/>
295
+ <!-- Mehrere zusammengehörige Zahlen EINE multi-stat statt mehrerer stat nebeneinander
296
+ oder eines stat-grid. Items horizontal (Standard), responsiv, klickbar (item.link). -->
297
+ <xiri-multi-stat [settings]="{ title: 'Bestellungen', icon: 'shopping_cart',
298
+ items: [ { value: 12, label: 'Offen', icon: 'inventory', color: 'orange', link: '/Orders?status=open' },
299
+ { value: 45, label: 'Fertig', color: 'green', trend: { value: 5, direction: 'up' } } ] }"/>
300
+ <xiri-stat-grid [settings]="{ stats: [...], columns: 4, title: 'KPIs' }"/> <!-- nur für eigenständige KPI-Karten -->
296
301
  <xiri-timeline [settings]="{ items: [...], orientation: 'vertical' }"/>
297
302
  <xiri-barchart mode="simple" [settings]="{ title: 'Weekly', yMin: 0, yMax: 12, color: 'purple',
298
303
  bars: [{ label: 'M', name: 'Monday', value: 3 }, ...] }"/>
@@ -632,6 +632,9 @@ interface XiriStatSettings {
632
632
  prefix?: string;
633
633
  suffix?: string;
634
634
  color?: XiriColor;
635
+ reference?: string; // muted Benchmark-/Anker-Zeile neben dem Wert
636
+ link?: string; // macht den Wert klickbar (routerLink + Query-Params);
637
+ // v. a. pro Zahl in xiri-multi-stat
635
638
  compact?: boolean; // Skipt eigene mat-card-Hülle, kleinere Schrift —
636
639
  // für Stats die in einer äußeren Card geschachtelt sind
637
640
  // (Card-in-Card-Look vermeiden).
@@ -643,8 +646,35 @@ interface XiriStatTrend {
643
646
  }
644
647
  ```
645
648
 
649
+ ### xiri-multi-stat
650
+
651
+ **Standard für mehrere zusammengehörige Zahlen.** Nimm **eine** `xiri-multi-stat`
652
+ statt mehrerer `xiri-stat` nebeneinander (eigene xcol-Spalten) oder `xiri-stat-grid`
653
+ (N Karten), wenn die Zahlen inhaltlich zusammengehören: gemeinsamer Header,
654
+ kompakter, responsiv (Items brechen auf schmalen Breiten sauber um, die Trennlinie
655
+ am Reihenanfang wird ausgeblendet).
656
+
657
+ ```typescript
658
+ @input.required settings: XiriMultiStatSettings;
659
+
660
+ interface XiriMultiStatSettings {
661
+ items?: XiriStatSettings[]; // je Zahl eine XiriStatSettings (value/label/icon/
662
+ // color/prefix/suffix/trend/link — link macht sie klickbar)
663
+ title?: string; // Header-Titel
664
+ icon?: string; // Header-Icon
665
+ iconColor?: XiriColor;
666
+ verticalItems?: boolean; // Standard horizontal (Icon links neben Wert/Label);
667
+ // true = gestapelt (Icon oben)
668
+ url?: string; // AJAX: lädt items per POST (Card-Muster),
669
+ // Header bleibt sofort sichtbar
670
+ reload?: boolean; // manueller Reload-Button (nur zusammen mit url)
671
+ }
672
+ ```
673
+
646
674
  ### xiri-stat-grid
647
675
 
676
+ Für mehrere **eigenständige** Stat-Karten. Zusammengehörige Zahlen → `xiri-multi-stat`.
677
+
648
678
  ```typescript
649
679
  @input.required settings: XiriStatGridSettings;
650
680
 
@@ -137,31 +137,37 @@ this.data.postFileResponse('/api/export', payload).subscribe(res => {
137
137
  });
138
138
  ```
139
139
 
140
- ## XiriDateService
140
+ ## XiriLocaleService
141
141
 
142
- Unix-Timestamp lokale Datums-Strings mit Timezone + Locale.
142
+ Zentraler i18n-/Kontext-Dienst: aktive Sprache, Locale, Timezone, Datums- und Zahlenformatierung sowie lokalisierte Validierungsmeldungen. Ab v0.3.0 sind die früheren `XiriDateService` und `XiriNumberService` hier aufgegangen.
143
143
 
144
144
  ```typescript
145
+ // Sprache / Locale
146
+ language: Signal<XiriLanguage> // 'de' | 'en' | (string & {})
147
+ localeString: Signal<string> // z.B. 'de-DE', 'en-GB'
148
+ dateFnsLocale: Signal<Locale | undefined> // aktives date-fns-Locale
149
+ setLanguage(lang: XiriLanguage): void // unbekannt → console.warn, kein Wechsel
150
+ validationMessagesFor(): XiriValidationMessages
151
+
152
+ // Beliebige weitere Sprachen registrieren (vor setLanguage / erstem Formular, im App-Init):
153
+ registerLanguage(code: XiriLanguage, def: XiriLanguageDefinition): void
154
+ // XiriLanguageDefinition = { localeString: string; dateFnsLocale?: Locale; validationMessages: XiriValidationMessages }
155
+
156
+ // Timezone + Datum (war XiriDateService)
145
157
  setTimezone(tz: string): void
146
- setLocale(localeString: string, locale: Locale): void // date-fns Locale
147
-
148
158
  unixToLocal(stime: number): Date | null
149
159
  unixToStringDateTime(stime: number): string // 'yyyy-MM-dd HH:mm'
150
- unixToStringDate(stime: number): string // 'd. LLL.'
160
+ unixToStringDate(stime: number): string // 'd. LLL.' (Monatsnamen folgen der Sprache)
151
161
  unixToStringDateYear(stime: number): string // 'd. LLL. yy'
152
162
  dateToUnix(date: Date): number
153
- ```
154
-
155
- Der Backend-`UiContext` schickt Locale/Timezone als Teil der JSON-Responses → Kalibrierung via `setLocale` / `setTimezone` erfolgt typischerweise in einem Auth-/Startup-Flow.
156
163
 
157
- ## XiriNumberService
158
-
159
- ```typescript
160
- setLocale(locale: string): void // Default: 'de-DE'
164
+ // Zahlen (war XiriNumberService) — folgt der aktiven Sprache
161
165
  formatNumber(value: number, webformat?: string): string
162
166
  // webformat: 'integer' | 'float1' | 'float2' | 'float3' | 'float4'
163
167
  ```
164
168
 
169
+ Das Backend liefert sichtbare Texte + Zahlen/Datum bereits sprachrichtig; die App ruft beim Login `setLanguage()` / `setTimezone()`. Der Material-Datepicker-Locale wird per `effect` automatisch angeglichen. Für zusätzliche Sprachen liefert der Client date-fns-Locale + Validierungstexte via `registerLanguage()`.
170
+
165
171
  ## XiriLocalStorageService / XiriSessionStorageService
166
172
 
167
173
  Wrapper mit Timestamp-Metadaten und In-Memory-Fallback (falls localStorage blockiert).
@@ -73,33 +73,44 @@ Die Komponenten nutzen `mat.*-theme` Tokens — kein Hart-kodiertes CSS.
73
73
 
74
74
  ## Locale & Sprache
75
75
 
76
- Backend (`xiri-go`) schickt `UiContext` mit Locale/Timezone pro Response. Frontend propagiert das an `XiriDateService` und `XiriNumberService`:
76
+ Alles läuft über den zentralen `XiriLocaleService` (ab v0.3.0; früher getrennte `XiriDateService`/`XiriNumberService`). Backend (`xiri-go`) liefert sichtbare Texte + Zahlen/Datum bereits sprachrichtig; die App setzt beim Login nur Sprache + Timezone:
77
77
 
78
78
  ```typescript
79
79
  // z.B. in einem auth-response-handler:
80
- import { de } from 'date-fns/locale';
80
+ this.locale.setLanguage('de'); // 'de' | 'en' eingebaut
81
+ this.locale.setTimezone('Europe/Vienna');
82
+ // Datepicker-Locale + Validierungstexte + Zahlen/Datumsformate folgen automatisch.
83
+ ```
84
+
85
+ ### Weitere Sprachen registrieren (Client-erweiterbar)
81
86
 
82
- this.date.setLocale('de-DE', de);
83
- this.date.setTimezone('Europe/Vienna');
84
- this.number.setLocale('de-DE');
87
+ ```typescript
88
+ import { fr } from 'date-fns/locale/fr';
89
+
90
+ this.locale.registerLanguage('fr', {
91
+ localeString: 'fr-FR',
92
+ dateFnsLocale: fr, // optional (Datepicker + Monatsnamen)
93
+ validationMessages: { required: 'Champ requis', /* … alle 14 Keys */ },
94
+ });
95
+ this.locale.setLanguage('fr');
85
96
  ```
86
97
 
87
98
  ### Date-Format-Helper
88
99
 
89
100
  ```typescript
90
- date.unixToStringDateTime(1708800000) // '2024-02-24 18:00'
91
- date.unixToStringDate(1708800000) // '24. Feb.'
92
- date.unixToStringDateYear(1708800000) // '24. Feb. 24'
93
- date.unixToLocal(1708800000) // Date-Objekt oder null
94
- date.dateToUnix(new Date()) // number
101
+ locale.unixToStringDateTime(1708800000) // '2024-02-24 18:00'
102
+ locale.unixToStringDate(1708800000) // '24. Feb.' (Monatsnamen folgen der Sprache)
103
+ locale.unixToStringDateYear(1708800000) // '24. Feb. 24'
104
+ locale.unixToLocal(1708800000) // Date-Objekt oder null
105
+ locale.dateToUnix(new Date()) // number
95
106
  ```
96
107
 
97
108
  ### Number-Format-Helper
98
109
 
99
110
  ```typescript
100
- number.formatNumber(1234.567, 'integer') // '1.235'
101
- number.formatNumber(1234.567, 'float2') // '1.234,57'
102
- number.formatNumber(1234.567, 'float4') // '1.234,5670'
111
+ locale.formatNumber(1234.567, 'integer') // '1.235' (folgt der aktiven Sprache)
112
+ locale.formatNumber(1234.567, 'float2') // '1.234,57'
113
+ locale.formatNumber(1234.567, 'float4') // '1.234,5670'
103
114
  ```
104
115
 
105
116
  ## SafehtmlPipe
@@ -2,6 +2,17 @@
2
2
  // Global Accessibility Styles
3
3
  // ============================================
4
4
 
5
+ // Base line-height for body text (WCAG 1.4.12 recommendation).
6
+ // Only body + native headings — Material components set their own
7
+ // line-height (sometimes 1) deliberately, do not override those here.
8
+ body {
9
+ line-height: 1.5;
10
+ }
11
+
12
+ h1, h2, h3, h4, h5, h6 {
13
+ line-height: 1.25;
14
+ }
15
+
5
16
  // Focus-visible indicator for keyboard navigation
6
17
  // Uses :focus-visible to only show on keyboard focus, not mouse clicks
7
18
  *:focus-visible {
@@ -33,6 +44,19 @@
33
44
  // Material handles this internally, no override needed
34
45
  }
35
46
 
47
+ // Windows High Contrast Mode / forced-colors basics
48
+ @media (forced-colors: active) {
49
+ *:focus-visible {
50
+ outline: 2px solid;
51
+ }
52
+
53
+ // State conveyed only via background-color would otherwise disappear;
54
+ // keep it legible with an explicit border.
55
+ .xiri-chip-display {
56
+ border: 1px solid;
57
+ }
58
+ }
59
+
36
60
  // Skip link for keyboard users
37
61
  .xiri-skip-link {
38
62
  position: absolute;
@@ -0,0 +1,8 @@
1
+ // ============================================
2
+ // Shared Mixins
3
+ // ============================================
4
+
5
+ // Fixed-width digits for numeric columns / counters (avoids layout jitter).
6
+ @mixin xiri-tabular-nums {
7
+ font-variant-numeric: tabular-nums;
8
+ }
package/styles/grid.scss CHANGED
@@ -21,6 +21,11 @@
21
21
  }
22
22
  }
23
23
 
24
+ .xrow-cq {
25
+ container-type: inline-size;
26
+ container-name: xrow;
27
+ }
28
+
24
29
  .xcol {
25
30
  grid-column-end: span 12;
26
31
  max-width: 100%;
@@ -68,6 +73,34 @@
68
73
  }
69
74
  }
70
75
 
76
+ // Reset viewport-based spans for .xrow-cq children (specificity 0,2,0 beats the
77
+ // always-active .xcol-{bp}-{i} media rule above at 0,1,0), so a container narrower
78
+ // than the viewport breakpoint falls back to the mobile default instead of inheriting
79
+ // a span meant for a wide viewport. The @container rules below share this same
80
+ // specificity and come later in source order, so they win once the container matches.
81
+ .xrow-cq > [class*='xcol-'] {
82
+ grid-column-end: span 12;
83
+ }
84
+
85
+ @each $abbr, $size in $breakpoints {
86
+ @container xrow (min-width: #{$size}) {
87
+ // Container query variants — opt-in via .xrow-cq
88
+ @for $i from 1 through 12 {
89
+ .xrow-cq > .xcol-#{$abbr}-#{$i} {
90
+ grid-column-end: span $i;
91
+ }
92
+ .xrow-cq > .xcol-middle-#{$abbr}-#{$i} {
93
+ grid-column-start: math.ceil(calc((12 - $i )/2)) + 1;
94
+ grid-column-end: math.ceil(calc((12 - $i )/2)) + $i + 1;
95
+ }
96
+ .xrow-cq > .xcol-right-#{$abbr}-#{$i} {
97
+ grid-column-start: calc(12 - $i + 1);
98
+ grid-column-end: 13;
99
+ }
100
+ }
101
+ }
102
+ }
103
+
71
104
  .x-none, .x-xs-none {
72
105
  display: none !important;
73
106
  }
@@ -1,6 +1,7 @@
1
1
  @use 'sass:list';
2
2
  @use 'sass:map';
3
3
  @use 'colors' as *;
4
+ @use 'mixins';
4
5
 
5
6
  // ============================================
6
7
  // Color Variant Mixins
@@ -109,7 +110,7 @@ mat-icon {
109
110
  // ============================================
110
111
  .mat-small-icon-button {
111
112
  --mat-icon-button-state-layer-size: 28px;
112
- --mat-icon-button-touch-target-size: 28px;
113
+ --mat-icon-button-touch-target-size: 44px; // WCAG 2.5.5: Hit-Area ≥ 44px, Icon bleibt klein
113
114
  --mat-icon-button-icon-size: 18px;
114
115
 
115
116
  .mat-icon {
@@ -122,7 +123,7 @@ mat-icon {
122
123
 
123
124
  .mat-medium-icon-button {
124
125
  --mat-icon-button-state-layer-size: 36px;
125
- --mat-icon-button-touch-target-size: 36px;
126
+ --mat-icon-button-touch-target-size: 44px; // WCAG 2.5.5: Hit-Area ≥ 44px, Icon bleibt klein
126
127
  --mat-icon-button-icon-size: 22px;
127
128
 
128
129
  .mat-icon {
@@ -174,12 +175,19 @@ td.align-right .xiri-chips-display {
174
175
  display: inline-block;
175
176
  padding: 2px 8px;
176
177
  border-radius: 12px;
177
- font-size: 12px;
178
+ font-size: var(--xiri-font-size-helper);
178
179
  line-height: 1.6;
179
180
  background-color: var(--surface-variant, #DEE3EB);
180
181
  color: var(--on-surface-variant, #42474E);
181
182
  }
182
183
 
184
+ // ============================================
185
+ // Tabular Numbers Utility
186
+ // ============================================
187
+ .xiri-tabular-nums {
188
+ @include mixins.xiri-tabular-nums;
189
+ }
190
+
183
191
  // ============================================
184
192
  // Tab Body Content Padding
185
193
  // ============================================