@xiriframework/xiri-ng 0.2.26 → 0.2.28
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/fesm2022/xiriframework-xiri-ng.mjs +210 -210
- package/fesm2022/xiriframework-xiri-ng.mjs.map +1 -1
- package/package.json +3 -2
- 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
- package/types/xiriframework-xiri-ng.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiriframework/xiri-ng",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.28",
|
|
4
4
|
"description": "Angular UI component library for the Xiri Framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -54,5 +54,6 @@
|
|
|
54
54
|
"types": "./types/xiriframework-xiri-ng.d.ts",
|
|
55
55
|
"default": "./fesm2022/xiriframework-xiri-ng.mjs"
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
},
|
|
58
|
+
"type": "module"
|
|
58
59
|
}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: xiri-ng-expert
|
|
3
|
+
description: Experte für die xiri-ng Angular-Library. Verwende diesen Skill IMMER wenn Angular-Code geschrieben wird der @xiriframework/xiri-ng importiert, oder wenn der User nach xiri-Komponenten (xiri-dyncomponent, xiri-form-fields, xiri-table, xiri-card usw.), XiriDataService, XiriSnackbarService, provideXiriServices, Form-Feldern mit showWhen-Bedingungen, oder Server-Side-Tables fragt.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# xiri-ng Expert
|
|
7
|
+
|
|
8
|
+
Du bist Experte für die **xiri-ng** Library (`@xiriframework/xiri-ng`) — eine Angular-21-Komponentenbibliothek, die vom Go-Backend (`xiri-go`) erzeugte JSON-Strukturen in Material-Design-3-UIs rendert.
|
|
9
|
+
|
|
10
|
+
**Wichtig:** Diese Datei enthält die häufigsten API-Signaturen. Lies `references/*.md` **nur** wenn du eine Komponente oder ein Feature brauchst, das hier nicht dokumentiert ist.
|
|
11
|
+
|
|
12
|
+
## Architektur
|
|
13
|
+
|
|
14
|
+
Backend-JSON → `xiri-dyncomponent` → Standalone-Components → Material Design 3.
|
|
15
|
+
|
|
16
|
+
- **Angular 21** mit Standalone-Components, Signals, OnPush, `input()`/`output()` API
|
|
17
|
+
- **Angular Material 3** mit SCSS-Theming (`ThemeService` mit `mode`-Signal)
|
|
18
|
+
- **Reactive Forms** (`UntypedFormGroup`) für `XiriFormFieldsComponent`
|
|
19
|
+
- **JSON-Driven**: Backend schickt `XiriDynData[]` → Frontend rendert per `xiri-dyncomponent`
|
|
20
|
+
- **Public API**: `projects/xiri-ng/src/public-api.ts` — jede Komponente, jedes Interface wird dort exportiert
|
|
21
|
+
|
|
22
|
+
## Setup — provideXiriServices
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { provideXiriServices } from '@xiriframework/xiri-ng';
|
|
26
|
+
|
|
27
|
+
export const appConfig: ApplicationConfig = {
|
|
28
|
+
providers: [
|
|
29
|
+
provideHttpClient(),
|
|
30
|
+
provideXiriServices({ api: '/api/' }), // Default: '/api/'
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Alle Services sind `providedIn: 'root'` (automatisch verfügbar nach Provider-Registrierung).
|
|
36
|
+
|
|
37
|
+
## Services — Quick Reference
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import {
|
|
41
|
+
XiriDataService, XiriSnackbarService, XiriResponseHandlerService,
|
|
42
|
+
XiriFormService, XiriDownloadService, XiriDateService, XiriNumberService,
|
|
43
|
+
XiriLocalStorageService, XiriSessionStorageService, ThemeService,
|
|
44
|
+
} from '@xiriframework/xiri-ng';
|
|
45
|
+
|
|
46
|
+
// HTTP-Client mit Snackbar-Integration
|
|
47
|
+
data.get(url): Observable<Object>
|
|
48
|
+
data.post(url, payload): Observable<any>
|
|
49
|
+
data.postFile(url, payload): Observable<any> // responseType blob
|
|
50
|
+
data.postFileResponse(url, payload): Observable<HttpResponse> // für download service
|
|
51
|
+
data.getConfigApi(): string // Base-URL
|
|
52
|
+
|
|
53
|
+
// Toast-Notifications
|
|
54
|
+
snackbar.success(msg, duration?, action?)
|
|
55
|
+
snackbar.error(msg, duration?, action?)
|
|
56
|
+
snackbar.info(msg, duration?, action?)
|
|
57
|
+
snackbar.warning(msg, duration?, action?)
|
|
58
|
+
snackbar.handleResponse(response): boolean // parst response.message + response.messageType
|
|
59
|
+
|
|
60
|
+
// Backend-Response-Handler (navigation / page-refresh / table-refresh)
|
|
61
|
+
responseHandler.handle(result, { onTableRefresh?, onTableUpdate? })
|
|
62
|
+
|
|
63
|
+
// File-Download
|
|
64
|
+
download.download(httpResponse, filename, openInNewTab): boolean
|
|
65
|
+
|
|
66
|
+
// Theme (Signal-basiert)
|
|
67
|
+
theme.mode // Signal<'light' | 'dark' | 'auto'>
|
|
68
|
+
theme.isDark // computed Signal<boolean>
|
|
69
|
+
theme.setTheme('dark' | 'light' | 'auto')
|
|
70
|
+
theme.toggle()
|
|
71
|
+
theme.resetToAuto()
|
|
72
|
+
|
|
73
|
+
// Storage — wrapper mit in-memory-Fallback
|
|
74
|
+
localStorage.set(name, value)
|
|
75
|
+
localStorage.get(name)
|
|
76
|
+
localStorage.getTimeout(name, maxSeconds) // null wenn älter
|
|
77
|
+
localStorage.remove(name)
|
|
78
|
+
localStorage.clear()
|
|
79
|
+
// XiriSessionStorageService hat gleiche API
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## xiri-dyncomponent — der JSON-Renderer
|
|
83
|
+
|
|
84
|
+
```html
|
|
85
|
+
<xiri-dyncomponent [data]="components" [filterData]="filter"></xiri-dyncomponent>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { XiriDynData } from '@xiriframework/xiri-ng';
|
|
90
|
+
|
|
91
|
+
components: XiriDynData[] = [
|
|
92
|
+
{ type: 'card', data: { header: 'Titel', fields: [...], data: {...} } },
|
|
93
|
+
{ type: 'table', data: { url: '/api/items', fields: [...] } },
|
|
94
|
+
{ type: 'stat', data: { value: 42, label: 'Offen' } },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
// type ist einer von:
|
|
98
|
+
// card | buttonline | table | cardlink | links | form | query
|
|
99
|
+
// stepper | header | list | spacer | container | infopoint | multiprogress
|
|
100
|
+
// imagetext | tabs | expansion | infotext | html | stat | empty-state
|
|
101
|
+
// timeline | page-header | section | divider | stat-grid | toolbar
|
|
102
|
+
// description-list | barchart (barchart benötigt zusätzlich `mode`)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## xiri-form-fields — Forms
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<xiri-form-fields
|
|
109
|
+
[form]="fields"
|
|
110
|
+
[display]="'full'"
|
|
111
|
+
[disabled]="submitting()"
|
|
112
|
+
(formChange)="onFormChange($event)">
|
|
113
|
+
</xiri-form-fields>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { XiriFormField } from '@xiriframework/xiri-ng';
|
|
118
|
+
|
|
119
|
+
fields: XiriFormField[] = [
|
|
120
|
+
{ id: 'name', type: 'text', name: 'Name', required: true, validations: [...] },
|
|
121
|
+
{ id: 'email', type: 'text', subtype: 'email', name: 'E-Mail' },
|
|
122
|
+
{ id: 'group', type: 'model', name: 'Gruppe', url: '/api/groups' },
|
|
123
|
+
{ id: 'active',type: 'bool', name: 'Aktiv', value: true },
|
|
124
|
+
{ id: 'desc', type: 'textarea', name: 'Beschreibung', rows: 4,
|
|
125
|
+
showWhen: { field: 'active', operator: 'equals', value: true } },
|
|
126
|
+
];
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Verfügbare `type`-Werte: `text`, `email`, `password`, `textarea`, `number`, `bool`, `select`, `object`, `model`, `multiselect`, `treeselect`, `date`, `datetime`, `daterange`, `datetimerange`, `file`, `volume`, `timelimit`, `chips`, `question`, `waiting`, `header`.
|
|
130
|
+
|
|
131
|
+
`display` = `'full' | 'line' | 'small'` — Layout-Modus.
|
|
132
|
+
|
|
133
|
+
**showWhen-Operators:** `equals`, `notEquals`, `contains`, `greaterThan`, `lessThan`, `in`, `notEmpty`.
|
|
134
|
+
|
|
135
|
+
Zugriff auf die reactive Form:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
@ViewChild(XiriFormFieldsComponent) fieldsCmp!: XiriFormFieldsComponent;
|
|
139
|
+
|
|
140
|
+
submit() {
|
|
141
|
+
const values = this.fieldsCmp.formGroup.value;
|
|
142
|
+
if (this.fieldsCmp.formGroup.valid) { ... }
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## xiri-form — Backend-integriertes Formular
|
|
147
|
+
|
|
148
|
+
```html
|
|
149
|
+
<xiri-form [settings]="{
|
|
150
|
+
url: '/api/users/add',
|
|
151
|
+
load: true,
|
|
152
|
+
header: 'Neuer User'
|
|
153
|
+
}"></xiri-form>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Lädt Felder + Buttons vom Backend, submit → POST → response-handler navigiert/refresht.
|
|
157
|
+
|
|
158
|
+
## xiri-table — volle Datentabelle
|
|
159
|
+
|
|
160
|
+
```html
|
|
161
|
+
<xiri-table [settings]="tableSettings" (clickedRow)="openEdit($event)"></xiri-table>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
tableSettings = {
|
|
166
|
+
url: '/api/devices',
|
|
167
|
+
serverSide: true,
|
|
168
|
+
options: {
|
|
169
|
+
pagination: true, itemsPerPage: 50, sort: true, search: true,
|
|
170
|
+
saveState: true, saveStateId: 'devices-table',
|
|
171
|
+
select: true, selectButtons: [ ... ],
|
|
172
|
+
editUrl: '/api/devices/inline-edit',
|
|
173
|
+
buttons: { buttons: [...], class: 'small' },
|
|
174
|
+
},
|
|
175
|
+
fields: [
|
|
176
|
+
{ id: 'id', name: 'ID', format: 'number' },
|
|
177
|
+
{ id: 'name', name: 'Name', search: true, sort: true, sticky: true },
|
|
178
|
+
{ id: 'status', name: 'Status', format: 'icon', icons: [...] },
|
|
179
|
+
{ id: 'count', name: 'Anzahl', format: 'number', webformat: 'integer', align: 'right', footer: 'sum' },
|
|
180
|
+
{ id: 'note', name: 'Notiz', editable: true, inputType: 'text' },
|
|
181
|
+
],
|
|
182
|
+
};
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Öffentliche Methoden: `reload()`, `searchDo(text)`, `startInlineEdit(row, column)`, `cancelInlineEdit()`, `saveInlineEdit(row, column)`, `selection` (SelectionModel), `isAllSelected()`, `masterToggle()`.
|
|
186
|
+
|
|
187
|
+
## xiri-raw-table — minimale Tabelle
|
|
188
|
+
|
|
189
|
+
```html
|
|
190
|
+
<xiri-raw-table [settings]="{ data: rows, fields: cols, dense: 8 }"></xiri-raw-table>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Keine Pagination, kein Sort, keine API — nur Daten rendern.
|
|
194
|
+
|
|
195
|
+
## xiri-query — Suchformular mit Live-Ergebnis
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
<xiri-query [settings]="{
|
|
199
|
+
fields: filterFields,
|
|
200
|
+
url: '/api/search',
|
|
201
|
+
collapsed: false,
|
|
202
|
+
saveState: true, saveStateId: 'search-x',
|
|
203
|
+
buttonline: { ... }
|
|
204
|
+
}" (change)="onFilterChange($event)"></xiri-query>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Debounce 300ms, ergebnis unter dem Filter als `XiriDynData[]`.
|
|
208
|
+
|
|
209
|
+
## xiri-dialog — Modal-Dialog
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { MatDialog } from '@angular/material/dialog';
|
|
213
|
+
import { XiriDialogComponent } from '@xiriframework/xiri-ng';
|
|
214
|
+
|
|
215
|
+
constructor(private dialog: MatDialog) {}
|
|
216
|
+
|
|
217
|
+
open() {
|
|
218
|
+
this.dialog.open(XiriDialogComponent, {
|
|
219
|
+
data: { url: '/api/user/edit/42', type: 'form', size: '800px' },
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`type`: `'form' | 'data' | 'question' | 'waiting' | 'table'`.
|
|
225
|
+
|
|
226
|
+
## xiri-button / xiri-buttonline
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import { XiriButton, XiriButtonResult } from '@xiriframework/xiri-ng';
|
|
230
|
+
|
|
231
|
+
buttons: XiriButton[] = [
|
|
232
|
+
{ text: 'Speichern', type: 'raised', color: 'primary', action: 'api',
|
|
233
|
+
url: '/api/save', default: true },
|
|
234
|
+
{ text: 'Löschen', type: 'flat', color: 'warn', action: 'dialog',
|
|
235
|
+
url: '/api/delete/confirm' },
|
|
236
|
+
{ text: 'Zurück', type: 'flat', action: 'back' },
|
|
237
|
+
];
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
```html
|
|
241
|
+
<xiri-buttonline [settings]="{ buttons, class: 'small' }"
|
|
242
|
+
[filterData]="filter"
|
|
243
|
+
(result)="onResult($event)"></xiri-buttonline>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`action`: `'api' | 'dialog' | 'download' | 'link' | 'back' | 'close' | 'return' | 'menu' | …`.
|
|
247
|
+
|
|
248
|
+
`data?: Record<string, any>` — Custom-Payload, das beim Klick mit `filterData` gemerged in den POST-Body geht (für `action: 'api'` / `'download'`). Der Backend-Builder (`xiri-go`) setzt das via `button.WithData(...)`. Beispiel: CSV-Download-Button hat `data: { _csv: true }`, das Backend liest das Flag in `LoadFilterData` und schaltet auf CSV-Output.
|
|
249
|
+
|
|
250
|
+
## xiri-stepper — Multi-Step Wizard
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
stepperSettings = {
|
|
254
|
+
url: '/api/onboarding',
|
|
255
|
+
steps: [
|
|
256
|
+
{ title: 'Daten', fields: [...], buttons: [...] },
|
|
257
|
+
{ title: 'Kontakt', fields: [...], buttons: [...] },
|
|
258
|
+
{ title: 'Review', fields: [...], buttons: [...], extra: { finalize: true } },
|
|
259
|
+
],
|
|
260
|
+
};
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Layout / Container
|
|
264
|
+
|
|
265
|
+
```html
|
|
266
|
+
<xiri-page-header [settings]="{ title: 'Users', subtitle: '42 aktiv',
|
|
267
|
+
icon: 'group', buttons: { buttons: [...] } }"/>
|
|
268
|
+
<xiri-toolbar [settings]="{ title: 'Items', search: true, buttons: {...} }"
|
|
269
|
+
(searchChanged)="onSearch($event)"/>
|
|
270
|
+
<xiri-section [settings]="{ title: 'Details', collapsible: true, components: [...] }"/>
|
|
271
|
+
<xiri-divider [settings]="{ text: 'Weitere Optionen', spacing: 'normal' }"/>
|
|
272
|
+
<xiri-tabs [settings]="{ tabs: [...], lazy: true }"/>
|
|
273
|
+
<xiri-expansion [settings]="{ panels: [...], multi: false, lazy: true }"/>
|
|
274
|
+
<xiri-sidenav [settings]="{ prefix: '/app/', fields: navItems }"/>
|
|
275
|
+
<xiri-breadcrumb [settings]="breadcrumbItems"/>
|
|
276
|
+
<xiri-skeleton type="table-row" [lines]="5" [columns]="4"/>
|
|
277
|
+
<xiri-empty-state [settings]="{ icon: 'inbox', title: 'Leer', button: {...} }"/>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Info / Display
|
|
281
|
+
|
|
282
|
+
```html
|
|
283
|
+
<xiri-stat [settings]="{ value: 1234, label: 'Umsatz', prefix: '€', color: 'primary',
|
|
284
|
+
trend: { value: 12, direction: 'up' } }"/>
|
|
285
|
+
<xiri-stat-grid [settings]="{ stats: [...], columns: 4, title: 'KPIs' }"/>
|
|
286
|
+
<xiri-timeline [settings]="{ items: [...], orientation: 'vertical' }"/>
|
|
287
|
+
<xiri-barchart mode="simple" [settings]="{ title: 'Weekly', yMin: 0, yMax: 12, color: 'purple',
|
|
288
|
+
bars: [{ label: 'M', name: 'Monday', value: 3 }, ...] }"/>
|
|
289
|
+
<xiri-barchart mode="stacked" [settings]="{ bars: [{ label: 'M', name: 'Monday',
|
|
290
|
+
segments: [{ value: 2, color: 'green', name: 'Low strain' }, ...] }] }"/>
|
|
291
|
+
<xiri-barchart mode="heatmap" [settings]="{ points: [{ time: 1700000000000, value: 1, name: 'Repeat #1' }, ...] }"/>
|
|
292
|
+
<!-- echarts ist optional peerDependency: nur installieren wenn xiri-barchart genutzt wird -->
|
|
293
|
+
<xiri-description-list [settings]="{ items: [...], columns: 2, layout: 'horizontal' }"/>
|
|
294
|
+
<xiri-list [settings]="{ sections: [...] }"/>
|
|
295
|
+
<xiri-infopoint [settings]="{ text: 'Info', info: 'Detail', icon: 'info', iconColor: 'primary' }"/>
|
|
296
|
+
<xiri-multiprogress [settings]="{ data: [...], show: 5, header: 'Top 5' }"/>
|
|
297
|
+
<xiri-imagetext [settings]="{ url: '/img.png', info: '...', header: '...' }"/>
|
|
298
|
+
<xiri-header [settings]="{ text: 'Abschnitt', color: 'primary', size: 'h2' }"/>
|
|
299
|
+
<xiri-card [settings]="{ url: '/api/overview', header: 'Übersicht', collapsible: true }"/>
|
|
300
|
+
<xiri-cardlink [settings]="{ link: '/app/users', icon: 'group', iconSet: '', text: 'Users' }"/>
|
|
301
|
+
<xiri-links [settings]="{ data: [...], header: 'Schnellzugriff' }"/>
|
|
302
|
+
<xiri-done/>
|
|
303
|
+
<xiri-error text="Fehler beim Laden"/>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Farben
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { XiriColor } from '@xiriframework/xiri-ng';
|
|
310
|
+
|
|
311
|
+
// Theme: 'primary' | 'secondary' | 'tertiary' | 'accent' | 'warn' | 'error' | 'success'
|
|
312
|
+
// Extended: 'emerald' | 'red' | 'yellow' | 'green' | 'blue' | 'purple'
|
|
313
|
+
// 'gray' | 'lightgray' | 'darkgray' | 'orange' | 'white' | 'black' | 'inherit'
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Pipes
|
|
317
|
+
|
|
318
|
+
```html
|
|
319
|
+
{{ htmlString | safeHtml }} <!-- bypass DomSanitizer -->
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Typische Patterns
|
|
323
|
+
|
|
324
|
+
### 1. Seite rendert Backend-JSON
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
@Component({
|
|
328
|
+
selector: 'app-overview',
|
|
329
|
+
imports: [XiriDynComponentComponent],
|
|
330
|
+
template: `<xiri-dyncomponent [data]="components()"/>`,
|
|
331
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
332
|
+
})
|
|
333
|
+
export class OverviewComponent {
|
|
334
|
+
private data = inject(XiriDataService);
|
|
335
|
+
components = signal<XiriDynData[]>([]);
|
|
336
|
+
|
|
337
|
+
ngOnInit() {
|
|
338
|
+
this.data.get('/api/overview').subscribe((res: any) => this.components.set(res.data));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### 2. Form mit showWhen + Submit
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
onSubmit() {
|
|
347
|
+
if (this.fieldsCmp.formGroup.invalid) return;
|
|
348
|
+
this.data.post('/api/save', this.fieldsCmp.formGroup.value)
|
|
349
|
+
.subscribe(res => this.responseHandler.handle(res));
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### 3. Server-Side-Table mit Refresh nach Edit
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
@ViewChild(XiriTableComponent) table!: XiriTableComponent;
|
|
357
|
+
|
|
358
|
+
onEditSaved(res: any) {
|
|
359
|
+
this.responseHandler.handle(res, {
|
|
360
|
+
onTableRefresh: () => this.table.reload(),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### 4. Dialog öffnen für Edit
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
edit(row: any) {
|
|
369
|
+
this.dialog.open(XiriDialogComponent, {
|
|
370
|
+
data: { url: `/api/items/edit/${row.id}`, type: 'form', size: '700px' },
|
|
371
|
+
}).afterClosed().subscribe(() => this.table.reload());
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Wann Reference-Dateien lesen
|
|
376
|
+
|
|
377
|
+
| Datei | Wann |
|
|
378
|
+
| --------------------------- | ---------------------------------------------------------------- |
|
|
379
|
+
| `references/setup.md` | Alle Services im Detail (Methoden-Signaturen, Rückgabe-Typen) |
|
|
380
|
+
| `references/dyncomponent.md`| Vollständige XiriDynData-Type-Liste + Custom-Rendering |
|
|
381
|
+
| `references/form-fields.md` | Jeder Feldtyp im Detail, alle Validator-Keys, select-Directive |
|
|
382
|
+
| `references/table.md` | Table-Options-Felder, Inline-Edit, Selection, Footer-Aggregation |
|
|
383
|
+
| `references/components.md` | Kompakte Signatur-Liste aller 30+ Komponenten |
|
|
384
|
+
| `references/theming-i18n.md`| ThemeService, Colors, Date/Number-Locale-Propagation |
|
|
385
|
+
|
|
386
|
+
## Was NICHT tun
|
|
387
|
+
|
|
388
|
+
- Nicht `{ ... }`-Casts statt typisierter Interfaces (nutze `XiriDynData`, `XiriFormField`, `XiriTableField`)
|
|
389
|
+
- Nicht `NgModule` — alle xiri-Komponenten sind **standalone**
|
|
390
|
+
- Nicht `ChangeDetectionStrategy.Default` ohne Grund — `OnPush` ist Convention
|
|
391
|
+
- Nicht `FormBuilder` direkt — `XiriFormFieldsComponent` baut die `UntypedFormGroup` selbst
|
|
392
|
+
- Nicht API-Calls in Templates — in Component-Code via `XiriDataService`
|
|
393
|
+
- Keine erfundenen Selectors / Inputs — wenn unsicher, `public-api.ts` lesen
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "xiri-ng-expert",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 0,
|
|
6
|
+
"prompt": "Erstelle eine Angular-Standalone-Komponente, die vom Backend ein JSON-Array von XiriDynData lädt (/api/dashboard) und per xiri-dyncomponent rendert. Nutze XiriDataService, OnPush und Signals.",
|
|
7
|
+
"expected_output": "Standalone component with @Component decorator, imports XiriDynComponentComponent, injects XiriDataService, uses signal<XiriDynData[]>, loads via data.get(), uses OnPush change detection.",
|
|
8
|
+
"assertions": [
|
|
9
|
+
{"name": "standalone_component", "type": "code_check", "description": "Component ist standalone: true (Default in Angular 21) oder imports[] ist gesetzt"},
|
|
10
|
+
{"name": "uses_xiri_dyncomponent", "type": "code_check", "description": "Template enthält <xiri-dyncomponent [data]=... und XiriDynComponentComponent ist in imports"},
|
|
11
|
+
{"name": "uses_data_service", "type": "code_check", "description": "Injects XiriDataService via inject() oder constructor und ruft .get('/api/dashboard') auf"},
|
|
12
|
+
{"name": "uses_signals", "type": "code_check", "description": "Nutzt signal<XiriDynData[]>([]) für Daten-State und .set() zum Aktualisieren"},
|
|
13
|
+
{"name": "onpush_strategy", "type": "code_check", "description": "changeDetection: ChangeDetectionStrategy.OnPush"},
|
|
14
|
+
{"name": "correct_types", "type": "code_check", "description": "Importiert XiriDynData und nutzt es als Typ, keine any-Flut"},
|
|
15
|
+
{"name": "no_invented_api", "type": "code_check", "description": "Nutzt nur public-api.ts-Exports, keine erfundenen Services/Methoden"}
|
|
16
|
+
],
|
|
17
|
+
"files": []
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": 1,
|
|
21
|
+
"prompt": "Baue ein Add-Formular mit XiriFormFieldsComponent für ein Fahrzeug: Felder name (text, required), group (model via /api/groups), active (bool, default true), note (textarea, nur sichtbar wenn active=false). Submit-Button postet an /api/vehicles/add und handled die Response.",
|
|
22
|
+
"expected_output": "Component with XiriFormFieldsComponent in template, defines XiriFormField[] with correct types and validations, showWhen for note, submit method uses fieldsCmp.formGroup.valid + fieldsCmp.formGroup.value, posts via XiriDataService, handles response via XiriResponseHandlerService.",
|
|
23
|
+
"assertions": [
|
|
24
|
+
{"name": "uses_form_fields_component", "type": "code_check", "description": "Template enthält <xiri-form-fields [form]=... (formChange)=..."},
|
|
25
|
+
{"name": "correct_field_types", "type": "code_check", "description": "name: type='text', group: type='model' mit url, active: type='bool', note: type='textarea'"},
|
|
26
|
+
{"name": "has_required_validation", "type": "code_check", "description": "name hat required: true oder validations mit type: 'required'"},
|
|
27
|
+
{"name": "uses_show_when", "type": "code_check", "description": "note-Feld hat showWhen mit field: 'active', operator: 'equals', value: false"},
|
|
28
|
+
{"name": "viewchild_form_group", "type": "code_check", "description": "@ViewChild auf XiriFormFieldsComponent und Zugriff auf .formGroup.valid / .formGroup.value beim Submit"},
|
|
29
|
+
{"name": "submits_via_data_service", "type": "code_check", "description": "XiriDataService.post('/api/vehicles/add', ...) wird im Submit-Handler aufgerufen"},
|
|
30
|
+
{"name": "handles_response", "type": "code_check", "description": "XiriResponseHandlerService.handle(res) wird nach erfolgreichem Submit aufgerufen"}
|
|
31
|
+
],
|
|
32
|
+
"files": []
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": 2,
|
|
36
|
+
"prompt": "Baue eine Device-Übersicht mit xiri-page-header, einem xiri-query als Filter, und darunter einer xiri-table mit Server-Side-Pagination. Bei Klick auf eine Row öffnet sich ein Edit-Dialog. Nach erfolgreichem Edit wird die Tabelle reloaded.",
|
|
37
|
+
"expected_output": "Component with XiriPageHeaderComponent, XiriQueryComponent and XiriTableComponent in template. Filter from query drives filterData on table. clickedRow output opens XiriDialogComponent via MatDialog. afterClosed reloads table.",
|
|
38
|
+
"assertions": [
|
|
39
|
+
{"name": "uses_page_header", "type": "code_check", "description": "xiri-page-header mit settings.title gesetzt"},
|
|
40
|
+
{"name": "uses_xiri_query", "type": "code_check", "description": "xiri-query mit XiriQuerySettings (fields, url, collapsed optional)"},
|
|
41
|
+
{"name": "server_side_table", "type": "code_check", "description": "xiri-table mit settings.options.serverSide: true, pagination aktiv, settings.url gesetzt"},
|
|
42
|
+
{"name": "filter_wiring", "type": "code_check", "description": "query (change)-Output wird an table [filterData] oder einer Signal/Property gebunden"},
|
|
43
|
+
{"name": "row_click_opens_dialog", "type": "code_check", "description": "(clickedRow) Event-Handler öffnet via MatDialog.open(XiriDialogComponent, { data: { type: 'form', url: ... } })"},
|
|
44
|
+
{"name": "reload_after_edit", "type": "code_check", "description": "dialogRef.afterClosed().subscribe ruft @ViewChild XiriTableComponent.reload() auf"},
|
|
45
|
+
{"name": "correct_imports", "type": "code_check", "description": "Alle benötigten Komponenten in imports[] deklariert"}
|
|
46
|
+
],
|
|
47
|
+
"files": []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": 3,
|
|
51
|
+
"prompt": "Schreibe einen app.config.ts Eintrag, der xiri-ng korrekt initialisiert (api base '/api/v2/'), mit HttpClient, Router, und einem ThemeService-Toggle-Button in einer Header-Komponente die den aktuellen Theme-Mode-Signal anzeigt.",
|
|
52
|
+
"expected_output": "ApplicationConfig mit provideHttpClient, provideRouter, provideXiriServices({ api: '/api/v2/' }). Separate Header-Component injects ThemeService, uses theme.mode() signal in template for conditional display, calls theme.toggle() on button click.",
|
|
53
|
+
"assertions": [
|
|
54
|
+
{"name": "provide_xiri_services", "type": "code_check", "description": "appConfig.providers enthält provideXiriServices({ api: '/api/v2/' })"},
|
|
55
|
+
{"name": "provide_http_client", "type": "code_check", "description": "provideHttpClient() ist vor provideXiriServices registriert"},
|
|
56
|
+
{"name": "injects_theme_service", "type": "code_check", "description": "Header-Component nutzt inject(ThemeService)"},
|
|
57
|
+
{"name": "uses_mode_signal", "type": "code_check", "description": "Template liest theme.mode() oder theme.isDark() als Signal-Call"},
|
|
58
|
+
{"name": "toggle_method", "type": "code_check", "description": "Button ruft theme.toggle() auf"},
|
|
59
|
+
{"name": "correct_provider_import", "type": "code_check", "description": "provideXiriServices importiert aus '@xiriframework/xiri-ng'"}
|
|
60
|
+
],
|
|
61
|
+
"files": []
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|