@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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# xiri-dyncomponent — JSON-Renderer
|
|
2
|
+
|
|
3
|
+
Die `xiri-dyncomponent` ist der Kern des JSON-Driven-UI Patterns: ein Array von `XiriDynData`-Objekten wird zu einer Kette von xiri-Komponenten gerendert.
|
|
4
|
+
|
|
5
|
+
## Selector
|
|
6
|
+
|
|
7
|
+
`xiri-dyncomponent`
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
@input.required data: XiriDynData[];
|
|
13
|
+
@input filterData: any = undefined;
|
|
14
|
+
@input dyncomponent: TemplateRef<any> = undefined;
|
|
15
|
+
// Optional: TemplateRef für Custom-Rendering (type: 'custom' oder unbekannter type).
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Outputs
|
|
19
|
+
|
|
20
|
+
Keine.
|
|
21
|
+
|
|
22
|
+
## XiriDynData Interface
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
export interface XiriDynData {
|
|
26
|
+
id?: number; // Wird auto-generiert wenn fehlt.
|
|
27
|
+
type: XiriDynDataType | (string & {}); // Komponenten-Typ.
|
|
28
|
+
data?: any; // Settings-Objekt der Ziel-Komponente.
|
|
29
|
+
display?: string; // CSS-Klassen für Wrapper.
|
|
30
|
+
newRow?: boolean; // Grid-Layout: neue Zeile beginnen.
|
|
31
|
+
mode?: string; // Nur für 'barchart': 'simple' | 'stacked' | 'heatmap'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type XiriDynDataType =
|
|
35
|
+
| 'card' | 'buttonline' | 'table' | 'cardlink' | 'links'
|
|
36
|
+
| 'form' | 'query' | 'stepper' | 'header' | 'list'
|
|
37
|
+
| 'spacer' | 'container'
|
|
38
|
+
| 'infopoint' | 'multiprogress' | 'imagetext'
|
|
39
|
+
| 'tabs' | 'expansion' | 'infotext' | 'html'
|
|
40
|
+
| 'stat' | 'empty-state' | 'timeline'
|
|
41
|
+
| 'page-header' | 'section' | 'divider'
|
|
42
|
+
| 'stat-grid' | 'toolbar' | 'description-list'
|
|
43
|
+
| 'barchart' | 'linechart' | 'piechart' | 'gaugechart'
|
|
44
|
+
| 'heatmap' | 'calendar' | 'tree' | 'sankey' | 'gantt';
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Mapping type → Komponente
|
|
48
|
+
|
|
49
|
+
| `type` | Rendered Component | `data` = Settings-Interface |
|
|
50
|
+
| ------------------- | ---------------------- | ------------------------------------ |
|
|
51
|
+
| `card` | `xiri-card` | `XiriCardSettings` |
|
|
52
|
+
| `buttonline` | `xiri-buttonline` | `XiriButtonlineSettings` |
|
|
53
|
+
| `table` | `xiri-table` | `XiriTableSettings` |
|
|
54
|
+
| `cardlink` | `xiri-cardlink` | `XiriCardlinkSettings` |
|
|
55
|
+
| `links` | `xiri-links` | `XiriLinksSettings` |
|
|
56
|
+
| `form` | `xiri-form` | `XiriFormSettings` |
|
|
57
|
+
| `query` | `xiri-query` | `XiriQuerySettings` |
|
|
58
|
+
| `stepper` | `xiri-stepper` | `XiriStepperSettings` |
|
|
59
|
+
| `header` | `xiri-header` | `XiriHeaderSettings` |
|
|
60
|
+
| `list` | `xiri-list` | `XiriListSettings` |
|
|
61
|
+
| `infopoint` | `xiri-infopoint` | `XiriInfopointSettings` |
|
|
62
|
+
| `multiprogress` | `xiri-multiprogress` | `XiriMultiprogressSettings` |
|
|
63
|
+
| `imagetext` | `xiri-imagetext` | `XiriImagetextSettings` |
|
|
64
|
+
| `tabs` | `xiri-tabs` | `XiriTabsSettings` |
|
|
65
|
+
| `expansion` | `xiri-expansion` | `XiriExpansionSettings` |
|
|
66
|
+
| `stat` | `xiri-stat` | `XiriStatSettings` |
|
|
67
|
+
| `empty-state` | `xiri-empty-state` | `XiriEmptyStateSettings` |
|
|
68
|
+
| `timeline` | `xiri-timeline` | `XiriTimelineSettings` |
|
|
69
|
+
| `page-header` | `xiri-page-header` | `XiriPageHeaderSettings` |
|
|
70
|
+
| `section` | `xiri-section` | `XiriSectionSettings` (rekursiv!) |
|
|
71
|
+
| `divider` | `xiri-divider` | `XiriDividerSettings` |
|
|
72
|
+
| `stat-grid` | `xiri-stat-grid` | `XiriStatGridSettings` |
|
|
73
|
+
| `toolbar` | `xiri-toolbar` | `XiriToolbarSettings` |
|
|
74
|
+
| `description-list` | `xiri-description-list`| `XiriDescriptionListSettings` |
|
|
75
|
+
| `barchart` | `xiri-barchart` | `XiriBarChartSettings` (+ `mode` auf XiriDynData) |
|
|
76
|
+
| `linechart` | `xiri-linechart` | `XiriLineChartSettings` |
|
|
77
|
+
| `piechart` | `xiri-piechart` | `XiriPieChartSettings` |
|
|
78
|
+
| `gaugechart` | `xiri-gaugechart` | `XiriGaugeChartSettings` |
|
|
79
|
+
| `heatmap` | `xiri-heatmap` | `XiriHeatmapSettings` |
|
|
80
|
+
| `calendar` | `xiri-calendar` | `XiriCalendarSettings` |
|
|
81
|
+
| `tree` | `xiri-tree` | `XiriTreeSettings` |
|
|
82
|
+
| `sankey` | `xiri-sankey` | `XiriSankeySettings` |
|
|
83
|
+
| `gantt` | `xiri-gantt` | `XiriGanttSettings` |
|
|
84
|
+
| `spacer` | vertical spacer div | `{ size?: string }` |
|
|
85
|
+
| `container` | wrapper div | `{ components: XiriDynData[] }` |
|
|
86
|
+
| `infotext` | Text-Absatz | `{ text: string }` |
|
|
87
|
+
| `html` | innerHTML (SafeHtml) | `{ html: string }` |
|
|
88
|
+
|
|
89
|
+
## Custom-Rendering via TemplateRef
|
|
90
|
+
|
|
91
|
+
Für Typen außerhalb der Liste: übergib einen `dyncomponent` TemplateRef als Fallback.
|
|
92
|
+
|
|
93
|
+
```html
|
|
94
|
+
<xiri-dyncomponent [data]="components" [dyncomponent]="customTpl"/>
|
|
95
|
+
|
|
96
|
+
<ng-template #customTpl let-item>
|
|
97
|
+
<app-my-custom-widget [data]="item.data"/>
|
|
98
|
+
</ng-template>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Der TemplateRef erhält das gesamte `XiriDynData`-Item als Context (`$implicit`).
|
|
102
|
+
|
|
103
|
+
## Verschachtelung
|
|
104
|
+
|
|
105
|
+
`section`, `tabs`, `expansion` und `container` akzeptieren in ihren `data`-Feldern wieder `XiriDynData[]` (rekursiv) — die `xiri-dyncomponent` rendert alle Ebenen durch.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const page: XiriDynData[] = [
|
|
109
|
+
{ type: 'page-header', data: { title: 'Dashboard' } },
|
|
110
|
+
{ type: 'stat-grid', data: { stats: [...], columns: 4 } },
|
|
111
|
+
{ type: 'section', data: {
|
|
112
|
+
title: 'Details',
|
|
113
|
+
collapsible: true,
|
|
114
|
+
components: [
|
|
115
|
+
{ type: 'table', data: { url: '/api/items' } },
|
|
116
|
+
{ type: 'divider', data: {} },
|
|
117
|
+
{ type: 'infopoint', data: { text: 'Hinweis', icon: 'info' } },
|
|
118
|
+
],
|
|
119
|
+
}},
|
|
120
|
+
];
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## filterData — Weitergabe an Tabellen und Buttons
|
|
124
|
+
|
|
125
|
+
`filterData` wird automatisch an alle `xiri-table`-, `xiri-buttonline`- und `xiri-toolbar`-Kinder weitergereicht. Das ist wichtig, wenn ein `xiri-query` Filter-Werte setzt, die Tabellen oder API-Buttons konsumieren sollen.
|
|
126
|
+
|
|
127
|
+
```html
|
|
128
|
+
<xiri-query [settings]="qs" (change)="filter = $event"></xiri-query>
|
|
129
|
+
<xiri-dyncomponent [data]="results" [filterData]="filter"/>
|
|
130
|
+
```
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Form-Fields — xiri-ng Reference
|
|
2
|
+
|
|
3
|
+
## XiriFormFieldsComponent
|
|
4
|
+
|
|
5
|
+
Renderer für `XiriFormField[]`. Baut intern eine `UntypedFormGroup` mit Reactive Validators, handhabt `showWhen`, collapsible Headers und alle 19 Feldtypen.
|
|
6
|
+
|
|
7
|
+
### Selector
|
|
8
|
+
|
|
9
|
+
`xiri-form-fields`
|
|
10
|
+
|
|
11
|
+
### Inputs
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
@input form: XiriFormField[] | null = null
|
|
15
|
+
@input display: XiriFormFieldDisplay = 'full' // 'full' | 'line' | 'small'
|
|
16
|
+
@input disabled: boolean = false
|
|
17
|
+
@input check: Observable<void> = null // Trigger für Revalidierung
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Outputs
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
@output formChange: EventEmitter<any>
|
|
24
|
+
// Emittet die UntypedFormGroup (nicht nur values) bei Änderungen.
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Öffentliche API
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
formGroup: UntypedFormGroup // Reactive Form
|
|
31
|
+
|
|
32
|
+
isFieldVisible(field: XiriFormField): boolean
|
|
33
|
+
toggleSection(header: XiriFormField): void
|
|
34
|
+
isSectionCollapsed(headerId: string): boolean
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Zugriff auf Werte und Validität
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
@ViewChild(XiriFormFieldsComponent) fieldsCmp!: XiriFormFieldsComponent;
|
|
41
|
+
|
|
42
|
+
submit() {
|
|
43
|
+
if (this.fieldsCmp.formGroup.invalid) {
|
|
44
|
+
this.fieldsCmp.formGroup.markAllAsTouched();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const payload = this.fieldsCmp.formGroup.value;
|
|
48
|
+
this.data.post('/api/save', payload)
|
|
49
|
+
.subscribe(res => this.responseHandler.handle(res));
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## XiriFormField Interface
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
export interface XiriFormField {
|
|
57
|
+
// --- Basis ---
|
|
58
|
+
id: string;
|
|
59
|
+
type: string; // siehe Liste unten
|
|
60
|
+
subtype?: string; // z.B. 'email' subtype von 'text'
|
|
61
|
+
formtype?: string; // Alt-Weg, gleich wie type
|
|
62
|
+
|
|
63
|
+
// --- Label & Display ---
|
|
64
|
+
name?: string; // Label
|
|
65
|
+
hint?: string;
|
|
66
|
+
class?: string;
|
|
67
|
+
textPrefix?: string;
|
|
68
|
+
textSuffix?: string;
|
|
69
|
+
iconPrefix?: string;
|
|
70
|
+
iconSuffix?: string;
|
|
71
|
+
locale?: string;
|
|
72
|
+
placeholder?: string; // v.a. für bool / header
|
|
73
|
+
|
|
74
|
+
// --- Wert & Validierung ---
|
|
75
|
+
value?: any;
|
|
76
|
+
validations?: XiriFormValidator[];
|
|
77
|
+
list?: XiriFormFieldSelectOption[]; // für select/treeselect
|
|
78
|
+
texts?: object; // für timelimit
|
|
79
|
+
|
|
80
|
+
// --- State ---
|
|
81
|
+
hide?: boolean;
|
|
82
|
+
required?: boolean;
|
|
83
|
+
disabled?: boolean;
|
|
84
|
+
collapsible?: boolean; // header
|
|
85
|
+
collapsed?: boolean; // header initial
|
|
86
|
+
|
|
87
|
+
// --- Constraints ---
|
|
88
|
+
min?: number; // number | date | array-length
|
|
89
|
+
max?: number;
|
|
90
|
+
pattern?: string; // regex
|
|
91
|
+
|
|
92
|
+
// --- Select / Model / Treeselect ---
|
|
93
|
+
multiple?: boolean;
|
|
94
|
+
url?: string; // load options von Backend
|
|
95
|
+
search?: boolean;
|
|
96
|
+
serverSideSearch?: boolean;
|
|
97
|
+
params?: object;
|
|
98
|
+
|
|
99
|
+
// --- File ---
|
|
100
|
+
accept?: string;
|
|
101
|
+
pwdhide?: boolean; // password
|
|
102
|
+
|
|
103
|
+
// --- Question / Waiting ---
|
|
104
|
+
icon?: string;
|
|
105
|
+
iconColor?: XiriColor;
|
|
106
|
+
done?: boolean;
|
|
107
|
+
|
|
108
|
+
// --- Conditional Display ---
|
|
109
|
+
showWhen?: XiriFormFieldCondition | XiriFormFieldCondition[];
|
|
110
|
+
|
|
111
|
+
// --- Weitere ---
|
|
112
|
+
rows?: number; // textarea
|
|
113
|
+
array?: any[]; // alt zu list
|
|
114
|
+
tree?: boolean;
|
|
115
|
+
control?: FormControl; // Internal
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Feld-Typen
|
|
120
|
+
|
|
121
|
+
| `type` | Rendered als | Wichtige Properties |
|
|
122
|
+
| --------------- | ------------------------------------------------- | ---------------------------------- |
|
|
123
|
+
| `text` | Mat-Input | `subtype` (email/url), `pattern` |
|
|
124
|
+
| `email` | Mat-Input type=email | |
|
|
125
|
+
| `password` | Mat-Input type=password | `pwdhide` |
|
|
126
|
+
| `textarea` | Mat-Textarea | `rows` |
|
|
127
|
+
| `number` | Mat-Input type=number | `min`, `max`, `textSuffix` |
|
|
128
|
+
| `bool` | Mat-Checkbox / Slide-Toggle | `placeholder` für Begleittext |
|
|
129
|
+
| `select` | Mat-Select mit `list` | `multiple`, `search` |
|
|
130
|
+
| `multiselect` | Mat-Select mit `multiple: true` | `list` oder `url` |
|
|
131
|
+
| `model` | Mat-Select mit Backend-Load | `url`, `serverSideSearch` |
|
|
132
|
+
| `object` | Komplexes Objekt-Select (JSON-Value) | `url`, `list` |
|
|
133
|
+
| `treeselect` | Tree-Picker | `url`, `tree: true` |
|
|
134
|
+
| `date` | Mat-Datepicker (Unix-Timestamp) | `min`, `max` |
|
|
135
|
+
| `datetime` | Date + Time Picker | `min`, `max` |
|
|
136
|
+
| `daterange` | Start- + End-Date | `min`, `max` |
|
|
137
|
+
| `datetimerange` | Start- + End-DateTime | |
|
|
138
|
+
| `yearmonth` | Monats-Picker (Multi-Year-View, MM.yyyy) | `min`, `max` (Unix), `required` |
|
|
139
|
+
| `file` | File-Upload Button | `accept` |
|
|
140
|
+
| `volume` | Volume-Slider (mit Einheit via `textSuffix`) | `min`, `max` |
|
|
141
|
+
| `timelimit` | Time-Limit Auswahl | `texts` (Label-Overrides) |
|
|
142
|
+
| `chips` | Mat-Chip-List | `list`, `validations` |
|
|
143
|
+
| `question` | Read-Only Frage + Icon (z.B. Confirmation-Dialog) | `icon`, `iconColor`, `done` |
|
|
144
|
+
| `waiting` | Loading-Spinner | |
|
|
145
|
+
| `header` | Sektions-Header (mit optional Collapse) | `collapsible`, `collapsed` |
|
|
146
|
+
|
|
147
|
+
#### `yearmonth` — Monats-Auswahl
|
|
148
|
+
|
|
149
|
+
Render: eigener `<xiri-yearmonth>`, intern Mat-Datepicker mit `startView="multi-year"`.
|
|
150
|
+
Value wird auf den **1. des Monats 00:00 (lokal)** normalisiert und als Unix-Timestamp
|
|
151
|
+
(Sekunden) zurückgegeben. Anzeige-Format `MM.yyyy`, Picker-Labels `MMM yyyy` /
|
|
152
|
+
`MMMM yyyy`. Default `required: true`, wenn nicht explizit gesetzt.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
{ id: 'period', type: 'yearmonth', name: 'Berichtsmonat',
|
|
156
|
+
required: true,
|
|
157
|
+
min: <unixSeconds>, // optional, untere Schranke
|
|
158
|
+
max: <unixSeconds> // optional, obere Schranke
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Backend-Pendant: `field.NewYearMonthField(...)` (siehe xiri-go-expert).
|
|
163
|
+
|
|
164
|
+
### showWhen — Conditional Visibility
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
export interface XiriFormFieldCondition {
|
|
168
|
+
field: string;
|
|
169
|
+
operator: 'equals' | 'notEquals' | 'contains'
|
|
170
|
+
| 'greaterThan' | 'lessThan' | 'in' | 'notEmpty';
|
|
171
|
+
value?: any;
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Einzelne Bedingung:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
{ id: 'reason', type: 'text', name: 'Grund',
|
|
179
|
+
showWhen: { field: 'active', operator: 'equals', value: false } }
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Array = **UND**-Verknüpfung:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
{ id: 'priorityNote', type: 'textarea', name: 'Prio-Notiz',
|
|
186
|
+
showWhen: [
|
|
187
|
+
{ field: 'priority', operator: 'in', value: ['high', 'critical'] },
|
|
188
|
+
{ field: 'note', operator: 'notEmpty' },
|
|
189
|
+
]}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Select-Optionen
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
export interface XiriFormFieldSelectOption {
|
|
196
|
+
id: number;
|
|
197
|
+
name: string;
|
|
198
|
+
disabled?: boolean;
|
|
199
|
+
color?: XiriColor;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Inline (statisch):
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
{ id: 'status', type: 'select', name: 'Status',
|
|
207
|
+
list: [
|
|
208
|
+
{ id: 1, name: 'Aktiv', color: 'success' },
|
|
209
|
+
{ id: 2, name: 'Inaktiv', color: 'warn' },
|
|
210
|
+
{ id: 3, name: 'Gesperrt', color: 'error', disabled: true },
|
|
211
|
+
]}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Per Backend (`model` oder `select` mit `url`):
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
{ id: 'group', type: 'model', name: 'Gruppe',
|
|
218
|
+
url: '/api/groups/options',
|
|
219
|
+
serverSideSearch: true,
|
|
220
|
+
params: { tenant: 42 } }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Das Backend liefert `XiriFormFieldSelectOption[]` zurück.
|
|
224
|
+
|
|
225
|
+
## XiriFormValidator
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
export interface XiriFormValidator {
|
|
229
|
+
type: 'required' | 'minlength' | 'maxlength' | 'pattern' |
|
|
230
|
+
'min' | 'max' | 'email' | ...;
|
|
231
|
+
value?: any;
|
|
232
|
+
message?: string;
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Beispiel:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
{ id: 'pwd', type: 'password', name: 'Passwort',
|
|
240
|
+
validations: [
|
|
241
|
+
{ type: 'required' },
|
|
242
|
+
{ type: 'minlength', value: 8, message: 'Mind. 8 Zeichen' },
|
|
243
|
+
{ type: 'pattern', value: '.*[0-9].*', message: '≥1 Zahl' },
|
|
244
|
+
]}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## XiriSelectDirective
|
|
248
|
+
|
|
249
|
+
Interne Directive für Mat-Select mit Client- oder Server-side-Search. Wird normalerweise nicht direkt verwendet — `XiriFormFieldsComponent` setzt sie automatisch für `select`, `multiselect`, `model`, `object`.
|
|
250
|
+
|
|
251
|
+
### Selector
|
|
252
|
+
|
|
253
|
+
`[xiriSelect]` auf `<mat-select>`
|
|
254
|
+
|
|
255
|
+
### Inputs (mit Alias)
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
@input({ alias: 'values' })
|
|
259
|
+
values: XiriFormFieldSelectOption[] = [];
|
|
260
|
+
|
|
261
|
+
@input({ alias: 'serverSideSearch' })
|
|
262
|
+
serverSideSearch: boolean = false;
|
|
263
|
+
|
|
264
|
+
@input({ alias: 'serverUrl' })
|
|
265
|
+
serverUrl: string = '';
|
|
266
|
+
|
|
267
|
+
@input({ alias: 'serverParams' })
|
|
268
|
+
serverParams: any = {};
|
|
269
|
+
|
|
270
|
+
@input({ alias: 'predicate' })
|
|
271
|
+
predicate: XiriSelectPredicate = DEFAULT_PREDICATE; // (option, term) => boolean
|
|
272
|
+
|
|
273
|
+
@input({ alias: 'compare' })
|
|
274
|
+
compare: XiriSelectCompare = DEFAULT_COMPARE; // (a, b) => boolean
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### API
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
get formControl(): UntypedFormControl;
|
|
281
|
+
get filter(): Observable<XiriFormFieldSelectOption[]>;
|
|
282
|
+
```
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Setup & Services — xiri-ng Reference
|
|
2
|
+
|
|
3
|
+
## provideXiriServices
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { provideXiriServices } from '@xiriframework/xiri-ng';
|
|
7
|
+
|
|
8
|
+
export const appConfig: ApplicationConfig = {
|
|
9
|
+
providers: [
|
|
10
|
+
provideHttpClient(),
|
|
11
|
+
provideXiriServices({ api: '/api/' }),
|
|
12
|
+
],
|
|
13
|
+
};
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Signatur:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
function provideXiriServices(config: Partial<XiriDataServiceConfig>): EnvironmentProviders
|
|
20
|
+
|
|
21
|
+
class XiriDataServiceConfig {
|
|
22
|
+
api: string = '/api/';
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Alle xiri-Services sind `providedIn: 'root'` — kein manuelles Registrieren nötig.
|
|
27
|
+
|
|
28
|
+
## XiriDataService
|
|
29
|
+
|
|
30
|
+
HTTP-Client mit automatischer Snackbar-Integration (parst `response.message` + `response.messageType`).
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
get(url: string): Observable<Object>
|
|
34
|
+
post(url: string, data: any): Observable<any>
|
|
35
|
+
|
|
36
|
+
postFile(url: string, data: any): Observable<any>
|
|
37
|
+
// responseType: 'blob' — gibt rohes Blob zurück
|
|
38
|
+
|
|
39
|
+
postFileResponse(url: string, data: any): Observable<HttpResponse<Blob>>
|
|
40
|
+
// mit Headern (für Filename aus Content-Disposition) — nutze mit XiriDownloadService
|
|
41
|
+
|
|
42
|
+
getConfigApi(): string
|
|
43
|
+
// aktuelle Base-URL (z.B. '/api/')
|
|
44
|
+
|
|
45
|
+
postDownload(url: string, data: any): void
|
|
46
|
+
// DEPRECATED — nutze postFileResponse() + XiriDownloadService.download()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
URLs werden mit `getConfigApi()` prefixed, wenn sie nicht absolut sind.
|
|
50
|
+
|
|
51
|
+
## XiriSnackbarService
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
success(message: string, duration?: number, action?: string): MatSnackBarRef
|
|
55
|
+
error (message: string, duration?: number, action?: string): MatSnackBarRef
|
|
56
|
+
info (message: string, duration?: number, action?: string): MatSnackBarRef
|
|
57
|
+
warning(message: string, duration?: number, action?: string): MatSnackBarRef
|
|
58
|
+
|
|
59
|
+
handleResponse(response: any): boolean
|
|
60
|
+
// Gibt true zurück wenn response.message existiert und gezeigt wurde.
|
|
61
|
+
// messageType mapt auf success | error | warning | info.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Wird von `XiriDataService` intern aufgerufen — du musst `handleResponse` nur selber aufrufen, wenn du die Response manuell behandelst.
|
|
65
|
+
|
|
66
|
+
## XiriResponseHandlerService
|
|
67
|
+
|
|
68
|
+
Zentrale Logik für Backend-Responses aus `xiri-go` (`ReturnGoto`, `ReturnRefreshPage`, `ReturnRefreshTable`, `ReturnUpdateTableField`).
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
handle(result: any, callbacks?: {
|
|
72
|
+
onTableRefresh?: () => void;
|
|
73
|
+
onTableUpdate?: (id: any, field: string, content: any) => void;
|
|
74
|
+
}): void
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Verhalten je nach `result.refreshType` / `result.gotoUrl`:
|
|
78
|
+
- Navigation → `Router.navigateByUrl`
|
|
79
|
+
- Page-Refresh → `window.location.reload()`
|
|
80
|
+
- Table-Refresh → ruft `onTableRefresh()` wenn gesetzt
|
|
81
|
+
- Table-Update → ruft `onTableUpdate(id, field, content)` wenn gesetzt
|
|
82
|
+
|
|
83
|
+
## XiriFormService
|
|
84
|
+
|
|
85
|
+
Orchestriert Form-Fetch/Submit mit State-Persistierung.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
get(url: string, data?: any, extra?: any): Observable<XiriFormServiceData>
|
|
89
|
+
// GET wenn data === null, sonst POST. Gibt geparste FormServiceData zurück.
|
|
90
|
+
|
|
91
|
+
parse(res: XiriFormServiceReturn): XiriFormServiceData
|
|
92
|
+
// Parst rohe Backend-Response (type: 'form' | 'question' | 'waiting').
|
|
93
|
+
|
|
94
|
+
loadState(saveStateId: string | null | undefined,
|
|
95
|
+
fields: XiriFormField[]): XiriFormField[]
|
|
96
|
+
// Merged persistierte Values in die Fields (SessionStorage, 3600s Timeout).
|
|
97
|
+
|
|
98
|
+
saveState(saveStateId: string | null | undefined, values: any): void
|
|
99
|
+
// Persistiert in SessionStorage.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## XiriThemeService (ThemeService)
|
|
103
|
+
|
|
104
|
+
Signal-basiert, System-Präferenz-aware.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
readonly mode: Signal<ThemeMode> // 'light' | 'dark' | 'auto'
|
|
108
|
+
readonly isDark: Signal<boolean> // computed
|
|
109
|
+
readonly isLight: Signal<boolean> // computed
|
|
110
|
+
|
|
111
|
+
setTheme(mode: ThemeMode): void // persistiert in localStorage
|
|
112
|
+
toggle(): void // wechselt zwischen light/dark
|
|
113
|
+
resetToAuto(): void // auto = folgt OS
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Beispiel in einer Komponente:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
private theme = inject(ThemeService);
|
|
120
|
+
|
|
121
|
+
toggleTheme() { this.theme.toggle(); }
|
|
122
|
+
darkMode = computed(() => this.theme.isDark());
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## XiriDownloadService
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
download(result: HttpResponse<Blob>, filename: string, openInNewTab: boolean): boolean
|
|
129
|
+
// Erstellt blob-URL, triggert Download oder öffnet Tab. True bei Erfolg.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Kombination mit `XiriDataService.postFileResponse`:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
this.data.postFileResponse('/api/export', payload).subscribe(res => {
|
|
136
|
+
this.download.download(res, 'export.xlsx', false);
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## XiriDateService
|
|
141
|
+
|
|
142
|
+
Unix-Timestamp ↔ lokale Datums-Strings mit Timezone + Locale.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
setTimezone(tz: string): void
|
|
146
|
+
setLocale(localeString: string, locale: Locale): void // date-fns Locale
|
|
147
|
+
|
|
148
|
+
unixToLocal(stime: number): Date | null
|
|
149
|
+
unixToStringDateTime(stime: number): string // 'yyyy-MM-dd HH:mm'
|
|
150
|
+
unixToStringDate(stime: number): string // 'd. LLL.'
|
|
151
|
+
unixToStringDateYear(stime: number): string // 'd. LLL. yy'
|
|
152
|
+
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
|
+
|
|
157
|
+
## XiriNumberService
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
setLocale(locale: string): void // Default: 'de-DE'
|
|
161
|
+
formatNumber(value: number, webformat?: string): string
|
|
162
|
+
// webformat: 'integer' | 'float1' | 'float2' | 'float3' | 'float4'
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## XiriLocalStorageService / XiriSessionStorageService
|
|
166
|
+
|
|
167
|
+
Wrapper mit Timestamp-Metadaten und In-Memory-Fallback (falls localStorage blockiert).
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
set(name: string, value: any): void // speichert mit Timestamp
|
|
171
|
+
get(name: string): any // value oder null
|
|
172
|
+
getTimeout(name: string, maxSeconds: number): any // null wenn älter als maxSeconds
|
|
173
|
+
remove(name: string): void
|
|
174
|
+
clear(): void
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`XiriSessionStorageService` hat dieselbe API, wird v.a. für Form-State und Table-State genutzt (`saveState: true` in Table/Query).
|