@xiriframework/xiri-ng 0.2.26 → 0.2.27
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 +1 -1
- package/skills/xiri-ng-expert/SKILL.md +393 -0
- package/skills/xiri-ng-expert/evals/evals.json +64 -0
- package/skills/xiri-ng-expert/references/components.md +918 -0
- package/skills/xiri-ng-expert/references/dyncomponent.md +130 -0
- package/skills/xiri-ng-expert/references/form-fields.md +282 -0
- package/skills/xiri-ng-expert/references/setup.md +177 -0
- package/skills/xiri-ng-expert/references/table.md +277 -0
- package/skills/xiri-ng-expert/references/theming-i18n.md +120 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Tables — xiri-ng Reference
|
|
2
|
+
|
|
3
|
+
Zwei Komponenten: `xiri-raw-table` (minimal) und `xiri-table` (voll — Paging, Sort, Search, Inline-Edit, Selection, Server-Side).
|
|
4
|
+
|
|
5
|
+
## XiriRawTableComponent
|
|
6
|
+
|
|
7
|
+
### Selector
|
|
8
|
+
|
|
9
|
+
`xiri-raw-table`
|
|
10
|
+
|
|
11
|
+
### Inputs
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
@input.required settings: XiriRawTableSettings;
|
|
15
|
+
|
|
16
|
+
export interface XiriRawTableSettings {
|
|
17
|
+
data: any[]; // Zeilen
|
|
18
|
+
fields?: XiriTableField[]; // Spalten
|
|
19
|
+
dense?: number; // 6-12, CSS-Klasse 'dense-X'
|
|
20
|
+
forceMinWidth?: boolean;
|
|
21
|
+
showHeader?: boolean; // Default false. Bei true wird die <thead>-Zeile mit
|
|
22
|
+
// Spalten-Headern (column.header) gerendert.
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Outputs
|
|
27
|
+
|
|
28
|
+
Keine.
|
|
29
|
+
|
|
30
|
+
Kein Sorting, kein Paging, kein Editing. Nur Daten rendern.
|
|
31
|
+
|
|
32
|
+
**Header-Zeile standardmäßig aus.** Aufrufer (xiri-card, xiri-dialog) setzen `showHeader` aus dem Backend-Payload (xiri-go: `*Card.WithTableHeader()` bzw. `Dialog.WithTableHeader()`).
|
|
33
|
+
|
|
34
|
+
## XiriTableComponent
|
|
35
|
+
|
|
36
|
+
### Selector
|
|
37
|
+
|
|
38
|
+
`xiri-table`
|
|
39
|
+
|
|
40
|
+
### Inputs
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
@input.required settings: XiriTableSettings;
|
|
44
|
+
@input filterData: any = undefined;
|
|
45
|
+
@input dyncomponent: TemplateRef<any> = undefined; // Custom-Cell-Rendering
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Outputs
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
@output clickedRow: EventEmitter<any>; // volles Row-Object
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Öffentliche Methoden
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
reload(): void
|
|
58
|
+
searchDo(text: string): void
|
|
59
|
+
selection: SelectionModel<any> // Checkbox-Selection (wenn options.select)
|
|
60
|
+
isAllSelected(): boolean
|
|
61
|
+
masterToggle(): void
|
|
62
|
+
|
|
63
|
+
// Inline-Edit
|
|
64
|
+
startInlineEdit(row: any, column: XiriTableField, skipSavingCheck?: boolean): void
|
|
65
|
+
cancelInlineEdit(): void
|
|
66
|
+
saveInlineEdit(row: any, column: XiriTableField): void
|
|
67
|
+
isEditing(row: any, fieldId: string): boolean
|
|
68
|
+
isSaving(row: any, fieldId: string): boolean
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### XiriTableSettings
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
export interface XiriTableSettings {
|
|
75
|
+
url?: string; // API-Endpoint (POST mit filterData-payload)
|
|
76
|
+
data?: any[]; // Alternative zu url — statische Daten
|
|
77
|
+
fields?: XiriTableField[];
|
|
78
|
+
options?: XiriTableOptions;
|
|
79
|
+
hasFilter?: boolean; // True wenn filterData extern gesetzt
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### XiriTableOptions
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
export interface XiriTableOptions {
|
|
87
|
+
reload?: boolean;
|
|
88
|
+
dense?: boolean;
|
|
89
|
+
sort?: boolean = true;
|
|
90
|
+
search?: boolean = true;
|
|
91
|
+
class?: string;
|
|
92
|
+
|
|
93
|
+
pagination?: boolean = true;
|
|
94
|
+
itemsPerPage?: number = 50;
|
|
95
|
+
pageSizes?: number[] = [10, 25, 50, 100, 500];
|
|
96
|
+
|
|
97
|
+
select?: boolean; // Checkbox für Row-Selection
|
|
98
|
+
selectButtons?: XiriButton[]; // Buttons die auf Selection wirken
|
|
99
|
+
|
|
100
|
+
title?: string;
|
|
101
|
+
textNoData?: string = 'no data found';
|
|
102
|
+
emptyState?: XiriTableEmptyState;
|
|
103
|
+
|
|
104
|
+
buttons?: XiriButtonlineSettings; // Action-Buttons über der Tabelle
|
|
105
|
+
|
|
106
|
+
minWidth?: string;
|
|
107
|
+
scrollHeight?: string;
|
|
108
|
+
|
|
109
|
+
saveState?: boolean = false; // Persist in SessionStorage
|
|
110
|
+
saveStateId?: string;
|
|
111
|
+
|
|
112
|
+
// Inline-Edit
|
|
113
|
+
saveInput?: string;
|
|
114
|
+
saveInputUrl?: string;
|
|
115
|
+
editUrl?: string; // POST-URL für Inline-Edit-Save
|
|
116
|
+
|
|
117
|
+
borders?: boolean;
|
|
118
|
+
bordersHeader?: boolean;
|
|
119
|
+
footer?: boolean; // Footer-Row mit count/sum/static
|
|
120
|
+
|
|
121
|
+
serverSide?: boolean; // Paging/Sort/Search serverseitig
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### XiriTableField
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
export interface XiriTableField {
|
|
129
|
+
id: string;
|
|
130
|
+
name: string;
|
|
131
|
+
format?: string; // 'text' | 'number' | 'html' | 'icon' | 'button' | 'chips' | ...
|
|
132
|
+
search?: boolean;
|
|
133
|
+
sort?: boolean;
|
|
134
|
+
|
|
135
|
+
buttons?: XiriButton[]; // Action-Buttons in der Zelle
|
|
136
|
+
icons?: { icon: string; color?: string; hint?: string }[];
|
|
137
|
+
|
|
138
|
+
display?: string; // CSS-Klasse auf <td>
|
|
139
|
+
header?: string; // Custom Header-Text (falls ≠ name)
|
|
140
|
+
sticky?: boolean;
|
|
141
|
+
|
|
142
|
+
// Inline-Edit
|
|
143
|
+
inputType?: string;
|
|
144
|
+
inputRequired?: boolean;
|
|
145
|
+
inputLang?: string;
|
|
146
|
+
inputPaste?: boolean;
|
|
147
|
+
|
|
148
|
+
// Formatierung
|
|
149
|
+
textPrefix?: string;
|
|
150
|
+
textSuffix?: string;
|
|
151
|
+
width?: string;
|
|
152
|
+
minWidth?: string;
|
|
153
|
+
|
|
154
|
+
hide?: boolean;
|
|
155
|
+
headerSpan?: number; // colspan für Header-Gruppen
|
|
156
|
+
|
|
157
|
+
align?: 'left' | 'center' | 'right';
|
|
158
|
+
footer?: 'no' | 'count' | 'sum' | 'static';
|
|
159
|
+
webformat?: string; // 'integer' | 'float1'-'float4'
|
|
160
|
+
|
|
161
|
+
editable?: boolean;
|
|
162
|
+
editableOptions?: { value: string; label: string; color?: string }[];
|
|
163
|
+
editableOptionsUrl?: string;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Chips-Format (Status-Tags pro Zelle)
|
|
168
|
+
|
|
169
|
+
`format: 'chips'` rendert pro Zelle ein Array farbiger Pills. Der Cell-Wert ist `Array<{label: string, color?: XiriColor}>`. Single-Value-Spalten liefern ein 1-elementiges Array (z. B. `[{ label: '45%', color: 'red' }]`).
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
fields: [
|
|
173
|
+
{ id: 'state', name: 'State', format: 'chips' },
|
|
174
|
+
{ id: 'metric', name: 'Battery', format: 'chips' },
|
|
175
|
+
{ id: 'tags', name: 'Tags', format: 'chips' },
|
|
176
|
+
],
|
|
177
|
+
data: [
|
|
178
|
+
{ id: 1,
|
|
179
|
+
state: [ { label: 'Attention', color: 'warn' } ],
|
|
180
|
+
metric: [ { label: '45%', color: 'red' } ],
|
|
181
|
+
tags: [ { label: 'Frontend', color: 'primary' },
|
|
182
|
+
{ label: 'Angular', color: 'emerald' } ] },
|
|
183
|
+
]
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
CSS-Klasse pro Chip: `.xiri-chip-display.<color>` (z. B. `.xiri-chip-display.red`). Theme-Farben (`primary`/`accent`/`warn`/…) und Extended-Farben (`red`/`green`/`yellow`/`gray`/…) werden vom Theming bereitgestellt.
|
|
187
|
+
|
|
188
|
+
**Editierbar (Multi-Select-Chips):** zusätzlich `editable: true` + `editableOptions` (oder `editableOptionsUrl`) auf dem Field — der Inline-Editor öffnet einen Multi-Select. Der Cell-Wert beim Edit-Save ist das Array der ausgewählten Labels.
|
|
189
|
+
|
|
190
|
+
**Backend-Pendant in xiri-go:** `b.ChipsField(id, name, accessor)` wo `accessor func(T) []table.Chip` (siehe `xiri-go-expert references/table-builder.md`).
|
|
191
|
+
|
|
192
|
+
## Server-Side-Pagination — Flow
|
|
193
|
+
|
|
194
|
+
Wenn `options.serverSide: true`, postet die Tabelle bei jeder Änderung (Sort/Filter/Page) an `settings.url` mit Payload:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"pageIndex": 0,
|
|
199
|
+
"pageSize": 50,
|
|
200
|
+
"sortBy": "name",
|
|
201
|
+
"sortDir": "asc",
|
|
202
|
+
"search": "foo",
|
|
203
|
+
"filter": { ... filterData ... }
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Erwartete Response:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
{
|
|
211
|
+
data: any[];
|
|
212
|
+
total: number;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Beispiel — voll konfigurierte Tabelle
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
tableSettings: XiriTableSettings = {
|
|
220
|
+
url: '/api/devices',
|
|
221
|
+
options: {
|
|
222
|
+
serverSide: true,
|
|
223
|
+
pagination: true,
|
|
224
|
+
itemsPerPage: 50,
|
|
225
|
+
sort: true,
|
|
226
|
+
search: true,
|
|
227
|
+
saveState: true,
|
|
228
|
+
saveStateId: 'devices-table',
|
|
229
|
+
select: true,
|
|
230
|
+
selectButtons: [
|
|
231
|
+
{ text: 'Löschen', type: 'raised', color: 'warn',
|
|
232
|
+
action: 'api', url: '/api/devices/bulk-delete' },
|
|
233
|
+
],
|
|
234
|
+
editUrl: '/api/devices/inline-edit',
|
|
235
|
+
footer: true,
|
|
236
|
+
buttons: {
|
|
237
|
+
class: 'small',
|
|
238
|
+
buttons: [
|
|
239
|
+
{ text: 'Neu', type: 'raised', color: 'primary',
|
|
240
|
+
action: 'dialog', url: '/api/devices/add' },
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
fields: [
|
|
245
|
+
{ id: 'id', name: 'ID', format: 'number', sticky: true, width: '80px' },
|
|
246
|
+
{ id: 'name', name: 'Name', search: true, sort: true },
|
|
247
|
+
{ id: 'status', name: 'Status', format: 'icon',
|
|
248
|
+
icons: [
|
|
249
|
+
{ icon: 'check_circle', color: 'success', hint: 'Aktiv' },
|
|
250
|
+
{ icon: 'cancel', color: 'warn', hint: 'Inaktiv' },
|
|
251
|
+
]},
|
|
252
|
+
{ id: 'count', name: 'Anzahl', format: 'number',
|
|
253
|
+
webformat: 'integer', align: 'right', footer: 'sum' },
|
|
254
|
+
{ id: 'note', name: 'Notiz', editable: true, inputType: 'text' },
|
|
255
|
+
{ id: 'actions', name: '', format: 'button',
|
|
256
|
+
buttons: [
|
|
257
|
+
{ text: '', type: 'icon', icon: 'edit',
|
|
258
|
+
action: 'dialog', url: '/api/devices/edit' },
|
|
259
|
+
{ text: '', type: 'icon', icon: 'delete', color: 'warn',
|
|
260
|
+
action: 'dialog', url: '/api/devices/delete' },
|
|
261
|
+
]},
|
|
262
|
+
],
|
|
263
|
+
};
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Reload nach Edit
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
@ViewChild(XiriTableComponent) table!: XiriTableComponent;
|
|
270
|
+
|
|
271
|
+
onButtonResult(res: XiriButtonResult) {
|
|
272
|
+
this.responseHandler.handle(res.result, {
|
|
273
|
+
onTableRefresh: () => this.table.reload(),
|
|
274
|
+
onTableUpdate: (id, field, content) => { /* optimistic UI */ },
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
```
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Theming, Colors & i18n — xiri-ng Reference
|
|
2
|
+
|
|
3
|
+
## Farben
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
export type XiriThemeColor =
|
|
7
|
+
| 'primary' | 'secondary' | 'tertiary' | 'accent'
|
|
8
|
+
| 'warn' | 'error' | 'success';
|
|
9
|
+
|
|
10
|
+
export type XiriExtendedColor =
|
|
11
|
+
| 'emerald' | 'red' | 'yellow' | 'green' | 'blue' | 'purple'
|
|
12
|
+
| 'gray' | 'lightgray' | 'darkgray' | 'orange'
|
|
13
|
+
| 'white' | 'black' | 'inherit';
|
|
14
|
+
|
|
15
|
+
export type XiriColor = XiriThemeColor | XiriExtendedColor;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Wird in fast allen Settings-Interfaces genutzt (`iconColor`, `color`, `headerIconColor`). Für Backend-JSON: als String serialisiert (z.B. `"primary"`, `"warn"`).
|
|
19
|
+
|
|
20
|
+
## ThemeService
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { ThemeService } from '@xiriframework/xiri-ng';
|
|
24
|
+
|
|
25
|
+
private theme = inject(ThemeService);
|
|
26
|
+
|
|
27
|
+
theme.mode // Signal<'light' | 'dark' | 'auto'>
|
|
28
|
+
theme.isDark // computed Signal<boolean>
|
|
29
|
+
theme.isLight // computed Signal<boolean>
|
|
30
|
+
|
|
31
|
+
theme.setTheme('dark' | 'light' | 'auto'); // persistiert in localStorage
|
|
32
|
+
theme.toggle(); // light ↔ dark
|
|
33
|
+
theme.resetToAuto(); // folgt System-Präferenz
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Typischer Toolbar-Toggle:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
@Component({ ... })
|
|
40
|
+
export class HeaderComponent {
|
|
41
|
+
private theme = inject(ThemeService);
|
|
42
|
+
isDark = this.theme.isDark;
|
|
43
|
+
toggle() { this.theme.toggle(); }
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Material-Design-3 Theming
|
|
48
|
+
|
|
49
|
+
xiri-ng setzt auf Angular Material 3 mit SCSS-Theme-Konfiguration im konsumierenden Projekt. Typisches Setup:
|
|
50
|
+
|
|
51
|
+
```scss
|
|
52
|
+
// src/styles.scss
|
|
53
|
+
@use '@angular/material' as mat;
|
|
54
|
+
|
|
55
|
+
html {
|
|
56
|
+
@include mat.theme((
|
|
57
|
+
color: (
|
|
58
|
+
primary: mat.$azure-palette,
|
|
59
|
+
tertiary: mat.$blue-palette,
|
|
60
|
+
),
|
|
61
|
+
typography: Roboto,
|
|
62
|
+
density: 0,
|
|
63
|
+
));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Dark-Mode via ThemeService's 'dark'-Klasse auf <body>
|
|
67
|
+
body.dark {
|
|
68
|
+
color-scheme: dark;
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Die Komponenten nutzen `mat.*-theme` Tokens — kein Hart-kodiertes CSS.
|
|
73
|
+
|
|
74
|
+
## Locale & Sprache
|
|
75
|
+
|
|
76
|
+
Backend (`xiri-go`) schickt `UiContext` mit Locale/Timezone pro Response. Frontend propagiert das an `XiriDateService` und `XiriNumberService`:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// z.B. in einem auth-response-handler:
|
|
80
|
+
import { de } from 'date-fns/locale';
|
|
81
|
+
|
|
82
|
+
this.date.setLocale('de-DE', de);
|
|
83
|
+
this.date.setTimezone('Europe/Vienna');
|
|
84
|
+
this.number.setLocale('de-DE');
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Date-Format-Helper
|
|
88
|
+
|
|
89
|
+
```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
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Number-Format-Helper
|
|
98
|
+
|
|
99
|
+
```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'
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## SafehtmlPipe
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
{{ htmlString | safeHtml }}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Nur verwenden wenn der HTML-String vertrauenswürdig ist (nicht aus User-Input). Umgeht Angulars DomSanitizer.
|
|
112
|
+
|
|
113
|
+
## Conventions im Library-Code
|
|
114
|
+
|
|
115
|
+
- Alle Komponenten: **standalone**, `OnPush`, kebab-case-Selector mit `xiri-`-Prefix
|
|
116
|
+
- Alle Directives: camelCase-Selector (z.B. `[xiriSelect]`)
|
|
117
|
+
- Signals für State, `input()`/`output()` für Komponenten-API
|
|
118
|
+
- `UntypedFormGroup` für dynamisch konstruierte Forms (Field-Liste aus JSON)
|
|
119
|
+
- `Observable` für HTTP, keine Promises
|
|
120
|
+
- Kein NgModule — niemand importiert xiri-ng als Modul
|