@xiriframework/xiri-ng 0.2.18 → 0.2.20

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @xiriframework/xiri-ng
2
2
 
3
- Angular UI component library for the Xiri Framework.
3
+ Angular UI component library for the [Xiri Framework](https://github.com/xiriframework) — a JSON-driven approach where your backend defines the UI and `xiri-ng` renders it. Pairs with [xiri-go](https://github.com/xiriframework/xiri-go) (Go backend) or any backend that emits the expected JSON shape.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,61 +8,156 @@ Angular UI component library for the Xiri Framework.
8
8
  npm install @xiriframework/xiri-ng
9
9
  ```
10
10
 
11
- ## Configuration
11
+ ## Setup
12
+
13
+ Register the Xiri services in your application config. `api` is the base URL prepended to HTTP requests made by `XiriDataService`:
12
14
 
13
15
  ```typescript
16
+ import { ApplicationConfig } from '@angular/core';
17
+ import { provideHttpClient } from '@angular/common/http';
18
+ import { provideRouter } from '@angular/router';
14
19
  import { provideXiriServices } from '@xiriframework/xiri-ng';
15
20
 
16
- bootstrapApplication(AppComponent, {
21
+ export const appConfig: ApplicationConfig = {
17
22
  providers: [
23
+ provideHttpClient(),
24
+ provideRouter(routes),
18
25
  provideXiriServices({ api: '/api/' }),
19
- ]
20
- });
26
+ ],
27
+ };
21
28
  ```
22
29
 
23
- The `api` configuration sets the base URL for the `XiriDataService`, which prepends this path to all HTTP requests.
30
+ ## The main entry point: `xiri-dyncomponent`
24
31
 
25
- ## Components
32
+ `xiri-ng` is designed around a central component that dynamically renders an array of JSON component definitions:
33
+
34
+ ```html
35
+ <xiri-dyncomponent [data]="components"></xiri-dyncomponent>
36
+ ```
37
+
38
+ ```typescript
39
+ import { XiriDynComponentComponent, XiriDynData } from '@xiriframework/xiri-ng';
40
+
41
+ components: XiriDynData[] = [
42
+ { type: 'page-header', data: { title: 'Dashboard' } },
43
+ { type: 'stat-grid', data: { stats: [...], columns: 4 } },
44
+ { type: 'table', data: { url: '/api/devices', ... } },
45
+ ];
46
+ ```
26
47
 
27
- Import components individually for optimal tree-shaking:
48
+ Individual components can also be used directly (all are standalone):
28
49
 
29
50
  ```typescript
30
- import { XiriTableComponent, XiriFormComponent } from '@xiriframework/xiri-ng';
51
+ import { XiriTableComponent } from '@xiriframework/xiri-ng';
52
+
53
+ @Component({
54
+ selector: 'app-devices',
55
+ imports: [XiriTableComponent],
56
+ template: `<xiri-table [settings]="tableSettings" />`,
57
+ })
58
+ export class DevicesComponent { /* ... */ }
31
59
  ```
32
60
 
33
- ### Available Components
34
-
35
- - **XiriFormComponent** - Dynamic form generation from configuration
36
- - **XiriTableComponent** - Feature-rich data table with server-side operations
37
- - **XiriRawTableComponent** - Basic table for simple use cases
38
- - **XiriCardComponent** - Card-based layouts
39
- - **XiriDialogComponent** - Modal dialogs
40
- - **XiriStepperComponent** - Multi-step workflows
41
- - **XiriTabsComponent** - Tabbed layouts
42
- - **XiriHeaderComponent** - Page headers
43
- - **XiriSidenavComponent** - Navigation sidebar
44
- - **XiriButtonlineComponent** - Button groups
45
- - **XiriSearchComponent** - Search input
46
- - **XiriQueryComponent** - Query/filter component
47
- - **XiriAlertComponent** - Status messages
48
- - **XiriListComponent** - List display
49
-
50
- ### Available Services
51
-
52
- - **XiriDataService** - Central HTTP service
53
- - **XiriDateService** - Date manipulation (wraps date-fns)
54
- - **XiriFormService** - Form state management
55
- - **XiriNumberService** - Number formatting
56
- - **XiriLocalStorageService** - Type-safe localStorage wrapper
57
- - **XiriSessionStorageService** - Type-safe sessionStorage wrapper
58
- - **ThemeService** - Theme management
59
-
60
- ## Peer Dependencies
61
+ ## Components
62
+
63
+ 40+ standalone Material-Design components. Import individually for tree-shaking.
64
+
65
+ ### Forms & data
66
+
67
+ | Component | Use case |
68
+ | ---------------------------- | -------------------------------------------------- |
69
+ | `XiriFormComponent` | Dynamic form with backend-driven fields + submit |
70
+ | `XiriFormFieldsComponent` | Just the fields — full control over submit flow |
71
+ | `XiriQueryComponent` | Filter form + live results via `xiri-dyncomponent` |
72
+ | `XiriTableComponent` | Full data table — sort, paginate, filter, inline-edit, export |
73
+ | `XiriRawTableComponent` | Minimal table for static data |
74
+ | `XiriDialogComponent` | Modal dialogs (question / form / table / waiting) |
75
+ | `XiriStepperComponent` | Multi-step wizard |
76
+
77
+ ### Layout & navigation
78
+
79
+ | Component | Use case |
80
+ | ---------------------------- | -------------------------------------------------- |
81
+ | `XiriPageHeaderComponent` | Page title + subtitle + actions |
82
+ | `XiriToolbarComponent` | Toolbar with title, search, buttons |
83
+ | `XiriSectionComponent` | Collapsible content sections |
84
+ | `XiriDividerComponent` | Section divider with optional text/icon |
85
+ | `XiriTabsComponent` | Material tabs with lazy-loading |
86
+ | `XiriExpansionComponent` | Accordion panels with lazy-loading |
87
+ | `XiriSidenavComponent` | Side navigation with nested items |
88
+ | `XiriBreadcrumbComponent` | Breadcrumb trail |
89
+
90
+ ### Cards, lists, info
91
+
92
+ | Component | Use case |
93
+ | --------------------------------- | --------------------------------------------- |
94
+ | `XiriCardComponent` | Card container with header + content |
95
+ | `XiriCardlinkComponent` | Clickable card as a navigation link |
96
+ | `XiriLinksComponent` | List of navigation links/buttons |
97
+ | `XiriListComponent` | Sectioned list with favorites |
98
+ | `XiriInfopointComponent` | Info card (icon + text + optional link) |
99
+ | `XiriImagetextComponent` | Image + text card |
100
+ | `XiriDescriptionListComponent` | Key/value pairs in 1-3 columns |
101
+
102
+ ### Stats, progress, feedback
103
+
104
+ | Component | Use case |
105
+ | ----------------------------- | ------------------------------------------------- |
106
+ | `XiriStatComponent` | Single KPI tile with value + label + trend |
107
+ | `XiriStatGridComponent` | Grid of KPI tiles |
108
+ | `XiriMultiprogressComponent` | Multi-row progress bars |
109
+ | `XiriTimelineComponent` | Vertical or horizontal timeline |
110
+ | `XiriEmptyStateComponent` | Empty-state placeholder with optional action |
111
+ | `XiriSkeletonComponent` | Loading skeleton (text/circle/rect/table-row) |
112
+ | `XiriAlertComponent` | Alert / confirmation dialog content |
113
+ | `XiriDoneComponent` | Success checkmark |
114
+ | `XiriErrorComponent` | Error message display |
115
+ | `XiriHeaderComponent` | In-page section header |
116
+
117
+ ### Buttons & search
118
+
119
+ | Component | Use case |
120
+ | ----------------------------- | ------------------------------------------------- |
121
+ | `XiriButtonComponent` | Single button with action handling |
122
+ | `XiriButtonlineComponent` | Horizontal button line |
123
+ | `XiriButtonstyleComponent` | Internal — button visual renderer |
124
+ | `XiriSearchComponent` | Debounced search input |
125
+
126
+ ## Services
127
+
128
+ All services are `providedIn: 'root'` and are activated by `provideXiriServices()`:
129
+
130
+ | Service | Purpose |
131
+ | ------------------------------- | ----------------------------------------------------------------- |
132
+ | `XiriDataService` | HTTP client with automatic snackbar response handling |
133
+ | `XiriSnackbarService` | Toast notifications (success, error, info, warning) |
134
+ | `XiriResponseHandlerService` | Parses backend responses (refresh, goto, table-update) centrally |
135
+ | `XiriFormService` | Form data fetch/submit + state persistence |
136
+ | `XiriDownloadService` | File download handling (blob → browser) |
137
+ | `XiriDateService` | Unix timestamp ↔ locale-formatted strings (via `date-fns`) |
138
+ | `XiriNumberService` | Locale-aware number formatting |
139
+ | `XiriLocalStorageService` | Typed localStorage wrapper with timestamp-based expiry |
140
+ | `XiriSessionStorageService` | Typed sessionStorage wrapper |
141
+ | `ThemeService` | Theme management (light / dark / auto) via signals |
142
+
143
+ ## Pipes
144
+
145
+ - `SafehtmlPipe` (`| safeHtml`) — bypass DomSanitizer for trusted HTML. Never feed user input into this pipe.
146
+
147
+ ## Claude Code support
148
+
149
+ The main repository ships a [Claude Code](https://claude.com/claude-code) skill (`xiri-ng-expert`) that teaches Claude the full API, patterns, and conventions. See the [main README](https://github.com/xiriframework/xiri-ng#claude-code-integration) for installation.
150
+
151
+ ## Peer dependencies
61
152
 
62
153
  - Angular 21+
63
154
  - Angular Material 21+
64
- - date-fns 4+
65
- - RxJS 7+
155
+ - Angular CDK 21+
156
+ - RxJS 7.8+
157
+ - `date-fns` 4+
158
+ - `@date-fns/tz` 1+
159
+ - `ngx-mat-select-search` 8+
160
+ - `material-symbols` ≥ 0.40
66
161
 
67
162
  ## License
68
163
 
@@ -3362,6 +3362,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
3362
3362
  }], propDecorators: { settings: [{ type: i0.Input, args: [{ isSignal: true, alias: "settings", required: true }] }], buttonResult: [{ type: i0.Output, args: ["buttonResult"] }] } });
3363
3363
 
3364
3364
  class XiriTableComponent {
3365
+ tableStateKey() {
3366
+ return this.options.saveStateId ? this.options.saveStateId + ':table' : null;
3367
+ }
3365
3368
  // Inline edit signals delegated to inlineEdit service
3366
3369
  get editingCell() { return this.inlineEdit.editingCell; }
3367
3370
  get editingChipsValues() { return this.inlineEdit.editingChipsValues; }
@@ -3500,8 +3503,8 @@ class XiriTableComponent {
3500
3503
  .pipe(takeUntilDestroyed(this.destroyRef))
3501
3504
  .subscribe(data => {
3502
3505
  this._displayeddata = data || [];
3503
- if (this.options.saveState && this._firstData === false) {
3504
- this.sessionStorageService.set(this.options.saveStateId, {
3506
+ if (this.options.saveState && this._firstData === false && this.tableStateKey()) {
3507
+ this.sessionStorageService.set(this.tableStateKey(), {
3505
3508
  pageIndex: this.paginator()?.pageIndex,
3506
3509
  pageSize: this.paginator()?.pageSize,
3507
3510
  filter: this.searchText,
@@ -3630,8 +3633,8 @@ class XiriTableComponent {
3630
3633
  this._alldata.set(data);
3631
3634
  if (this._firstData) {
3632
3635
  this._firstData = false;
3633
- if (this.options.saveState) {
3634
- let data = this.sessionStorageService.getTimeout(this.options.saveStateId, 3600);
3636
+ if (this.options.saveState && this.tableStateKey()) {
3637
+ let data = this.sessionStorageService.getTimeout(this.tableStateKey(), 3600);
3635
3638
  if (!data)
3636
3639
  return;
3637
3640
  if (data.filter !== undefined) {
@@ -4211,20 +4214,23 @@ class XiriQueryComponent {
4211
4214
  this.filterData = signal(null, ...(ngDevMode ? [{ debugName: "filterData" }] : /* istanbul ignore next */ []));
4212
4215
  this.loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
4213
4216
  }
4217
+ get effectiveSaveKey() {
4218
+ return this.saveStateId ? this.saveStateId + ':filter' : null;
4219
+ }
4214
4220
  ngOnInit() {
4215
4221
  let settings = this.settings();
4216
4222
  if (settings.dyn !== undefined && settings.dyn !== null && settings.dyn.length != 0)
4217
4223
  this.dynData.data = settings.dyn;
4218
4224
  this.saveState = settings.saveState === undefined ? false : settings.saveState;
4219
4225
  this.saveStateId = settings.saveStateId === undefined ? null : settings.saveStateId;
4220
- this.formFields.set(this.formService.loadState(settings.saveStateId, settings.fields));
4226
+ this.formFields.set(this.formService.loadState(this.effectiveSaveKey, settings.fields));
4221
4227
  this.extra = settings.extra || null;
4222
4228
  this.waiter.pipe(debounceTime(300), takeUntilDestroyed(this.destroyRef)).subscribe(event => {
4223
4229
  if (event.valid) {
4224
4230
  this.dynData.filterData = this.filterData();
4225
4231
  this.change.emit(this.filterData());
4226
4232
  if (this.saveState)
4227
- this.formService.saveState(this.saveStateId, event.value);
4233
+ this.formService.saveState(this.effectiveSaveKey, event.value);
4228
4234
  if (this.settings().url)
4229
4235
  this.load();
4230
4236
  }
@@ -4252,7 +4258,7 @@ class XiriQueryComponent {
4252
4258
  this.dynData.filterData = this.filterData();
4253
4259
  this.change.emit(this.filterData());
4254
4260
  if (this.saveState)
4255
- this.formService.saveState(this.saveStateId, event.value);
4261
+ this.formService.saveState(this.effectiveSaveKey, event.value);
4256
4262
  if (this.settings().url)
4257
4263
  this.load();
4258
4264
  }
@@ -5149,11 +5155,11 @@ class XiriTimelineComponent {
5149
5155
  this.settings = input.required(...(ngDevMode ? [{ debugName: "settings" }] : /* istanbul ignore next */ []));
5150
5156
  }
5151
5157
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: XiriTimelineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5152
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.2", type: XiriTimelineComponent, isStandalone: true, selector: "xiri-timeline", inputs: { settings: { classPropertyName: "settings", publicName: "settings", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"timeline\">\n\t@for (item of settings().items; track $index; let last = $last) {\n\t\t<div class=\"timeline-item\" [class.last]=\"last\">\n\t\t\t<div class=\"timeline-marker\">\n\t\t\t\t<div class=\"timeline-dot\" [class]=\"item.iconColor || 'primary'\">\n\t\t\t\t\t@if (item.icon) {\n\t\t\t\t\t\t<mat-icon>{{ item.icon }}</mat-icon>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t@if (!last) {\n\t\t\t\t\t<div class=\"timeline-line\"></div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t\t<div class=\"timeline-content\">\n\t\t\t\t<div class=\"timeline-header\">\n\t\t\t\t\t<span class=\"timeline-title\">{{ item.title }}</span>\n\t\t\t\t\t@if (item.datetime) {\n\t\t\t\t\t\t<span class=\"timeline-datetime\">{{ item.datetime }}</span>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t@if (item.description) {\n\t\t\t\t\t<div class=\"timeline-description\">{{ item.description }}</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</div>\n\t}\n</div>\n", styles: [".timeline{display:flex;flex-direction:column}.timeline-item{display:flex;gap:var(--xiri-spacing-md);animation:fadeInUp var(--xiri-duration-normal, .3s) var(--xiri-easing-decelerate, cubic-bezier(0, 0, .2, 1)) both}.timeline-item:nth-child(1){animation-delay:0ms}.timeline-item:nth-child(2){animation-delay:50ms}.timeline-item:nth-child(3){animation-delay:.1s}.timeline-item:nth-child(4){animation-delay:.15s}.timeline-item:nth-child(5){animation-delay:.2s}.timeline-item:nth-child(6){animation-delay:.25s}.timeline-item:nth-child(7){animation-delay:.3s}.timeline-item:nth-child(8){animation-delay:.35s}.timeline-item:nth-child(9){animation-delay:.4s}.timeline-item:nth-child(10){animation-delay:.45s}.timeline-item:nth-child(11){animation-delay:.5s}.timeline-item:nth-child(12){animation-delay:.55s}.timeline-item:nth-child(13){animation-delay:.6s}.timeline-item:nth-child(14){animation-delay:.65s}.timeline-item:nth-child(15){animation-delay:.7s}.timeline-item:nth-child(16){animation-delay:.75s}.timeline-item:nth-child(17){animation-delay:.8s}.timeline-item:nth-child(18){animation-delay:.85s}.timeline-item:nth-child(19){animation-delay:.9s}.timeline-item:nth-child(20){animation-delay:.95s}.timeline-item.last .timeline-marker .timeline-line{display:none}.timeline-marker{display:flex;flex-direction:column;align-items:center;flex-shrink:0;width:2.5rem}.timeline-dot{width:2.5rem;height:2.5rem;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;background:var(--primary, #2892D9);color:var(--on-primary, white)}.timeline-dot mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}.timeline-dot.primary{background:var(--primary)}.timeline-dot.secondary{background:var(--secondary)}.timeline-dot.tertiary{background:var(--tertiary)}.timeline-dot.accent{background:var(--secondary)}.timeline-dot.warn,.timeline-dot.error{background:var(--error)}.timeline-dot.success{background:var(--color-success)}.timeline-dot.emerald{background:#2892d9}.timeline-dot.red{background:#e74c3c}.timeline-dot.yellow{background:#f1c40f}.timeline-dot.green{background:#2ecc71}.timeline-dot.blue{background:#3498db}.timeline-dot.purple{background:#9b59b6}.timeline-dot.gray{background:#95a5a6}.timeline-dot.lightgray{background:#bdc3c7}.timeline-dot.darkgray{background:#7f8c8d}.timeline-dot.orange{background:#e67e22}.timeline-dot.white{background:#fff}.timeline-dot.black{background:#000}.timeline-dot.inherit{background:inherit}.timeline-line{width:2px;flex:1;min-height:var(--xiri-spacing-lg);background:var(--mat-sys-outline-variant, rgba(0, 0, 0, .12))}.timeline-content{padding-bottom:var(--xiri-spacing-lg);flex:1;min-width:0}.timeline-header{display:flex;align-items:baseline;gap:.75rem;flex-wrap:wrap;min-height:2.5rem;align-content:center}.timeline-title{font-weight:500;font-size:var(--xiri-font-size-body, .875rem);color:var(--mat-sys-on-surface, rgba(0, 0, 0, .87))}.timeline-datetime{font-size:var(--xiri-font-size-helper, .75rem);color:var(--mat-sys-on-surface-variant, rgba(0, 0, 0, .6))}.timeline-description{margin-top:.25rem;font-size:var(--xiri-font-size-body, .875rem);color:var(--mat-sys-on-surface-variant, rgba(0, 0, 0, .6));line-height:1.5}\n"], dependencies: [{ kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5158
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.2", type: XiriTimelineComponent, isStandalone: true, selector: "xiri-timeline", inputs: { settings: { classPropertyName: "settings", publicName: "settings", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"timeline\" [class.horizontal]=\"settings().orientation === 'horizontal'\">\n\t@for (item of settings().items; track $index; let last = $last; let first = $first) {\n\t\t<div class=\"timeline-item\" [class.last]=\"last\" [class.first]=\"first\">\n\t\t\t<div class=\"timeline-marker\">\n\t\t\t\t<div class=\"timeline-line line-before\"></div>\n\t\t\t\t<div class=\"timeline-dot\" [class]=\"item.iconColor || 'primary'\">\n\t\t\t\t\t@if (item.icon) {\n\t\t\t\t\t\t<mat-icon>{{ item.icon }}</mat-icon>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t<div class=\"timeline-line line-after\"></div>\n\t\t\t</div>\n\t\t\t<div class=\"timeline-content\">\n\t\t\t\t<div class=\"timeline-header\">\n\t\t\t\t\t<span class=\"timeline-title\">{{ item.title }}</span>\n\t\t\t\t\t@if (item.datetime) {\n\t\t\t\t\t\t<span class=\"timeline-datetime\">{{ item.datetime }}</span>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t@if (item.description && settings().orientation !== 'horizontal') {\n\t\t\t\t\t<div class=\"timeline-description\">{{ item.description }}</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</div>\n\t}\n</div>\n", styles: [":host{display:block;container-type:inline-size}.timeline{display:flex;flex-direction:column}.timeline-item{display:flex;gap:var(--xiri-spacing-md);animation:fadeInUp var(--xiri-duration-normal, .3s) var(--xiri-easing-decelerate, cubic-bezier(0, 0, .2, 1)) both}.timeline-item:nth-child(1){animation-delay:0ms}.timeline-item:nth-child(2){animation-delay:50ms}.timeline-item:nth-child(3){animation-delay:.1s}.timeline-item:nth-child(4){animation-delay:.15s}.timeline-item:nth-child(5){animation-delay:.2s}.timeline-item:nth-child(6){animation-delay:.25s}.timeline-item:nth-child(7){animation-delay:.3s}.timeline-item:nth-child(8){animation-delay:.35s}.timeline-item:nth-child(9){animation-delay:.4s}.timeline-item:nth-child(10){animation-delay:.45s}.timeline-item:nth-child(11){animation-delay:.5s}.timeline-item:nth-child(12){animation-delay:.55s}.timeline-item:nth-child(13){animation-delay:.6s}.timeline-item:nth-child(14){animation-delay:.65s}.timeline-item:nth-child(15){animation-delay:.7s}.timeline-item:nth-child(16){animation-delay:.75s}.timeline-item:nth-child(17){animation-delay:.8s}.timeline-item:nth-child(18){animation-delay:.85s}.timeline-item:nth-child(19){animation-delay:.9s}.timeline-item:nth-child(20){animation-delay:.95s}.timeline-item .timeline-line.line-before,.timeline-item.last .timeline-line.line-after{display:none}.timeline-marker{display:flex;flex-direction:column;align-items:center;flex-shrink:0;width:2.5rem}.timeline-dot{width:2.5rem;height:2.5rem;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;background:var(--primary, #2892D9);color:var(--on-primary, white)}.timeline-dot mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}.timeline-dot.primary{background:var(--primary)}.timeline-dot.secondary{background:var(--secondary)}.timeline-dot.tertiary{background:var(--tertiary)}.timeline-dot.accent{background:var(--secondary)}.timeline-dot.warn,.timeline-dot.error{background:var(--error)}.timeline-dot.success{background:var(--color-success)}.timeline-dot.emerald{background:#2892d9}.timeline-dot.red{background:#e74c3c}.timeline-dot.yellow{background:#f1c40f}.timeline-dot.green{background:#2ecc71}.timeline-dot.blue{background:#3498db}.timeline-dot.purple{background:#9b59b6}.timeline-dot.gray{background:#95a5a6}.timeline-dot.lightgray{background:#bdc3c7}.timeline-dot.darkgray{background:#7f8c8d}.timeline-dot.orange{background:#e67e22}.timeline-dot.white{background:#fff}.timeline-dot.black{background:#000}.timeline-dot.inherit{background:inherit}.timeline-line{width:2px;flex:1;min-height:var(--xiri-spacing-lg);background:var(--mat-sys-outline-variant, rgba(0, 0, 0, .12))}.timeline-content{padding-bottom:var(--xiri-spacing-lg);flex:1;min-width:0}.timeline-header{display:flex;align-items:baseline;gap:.75rem;flex-wrap:wrap;min-height:2.5rem;align-content:center}.timeline-title{font-weight:500;font-size:var(--xiri-font-size-body, .875rem);color:var(--mat-sys-on-surface, rgba(0, 0, 0, .87))}.timeline-datetime{font-size:var(--xiri-font-size-helper, .75rem);color:var(--mat-sys-on-surface-variant, rgba(0, 0, 0, .6))}.timeline-description{margin-top:.25rem;font-size:var(--xiri-font-size-body, .875rem);color:var(--mat-sys-on-surface-variant, rgba(0, 0, 0, .6));line-height:1.5}@container (min-width: 481px){.timeline.horizontal{flex-direction:row}.timeline.horizontal .timeline-item{flex:1;min-width:0;flex-direction:column;align-items:center;gap:var(--xiri-spacing-xs);padding-bottom:0}.timeline.horizontal .timeline-item .timeline-line.line-before{display:block}.timeline.horizontal .timeline-item.first .timeline-line.line-before{visibility:hidden}.timeline.horizontal .timeline-item.last .timeline-line.line-after{display:block;visibility:hidden}.timeline.horizontal .timeline-content,.timeline.horizontal .timeline-header{display:contents}.timeline.horizontal .timeline-title{order:-2;text-align:center;min-height:2.5em;display:flex;align-items:center;justify-content:center;padding:0 var(--xiri-spacing-xs)}.timeline.horizontal .timeline-marker{order:-1;width:100%;flex-direction:row;align-items:center;gap:0}.timeline.horizontal .timeline-datetime{order:1;text-align:center;min-height:1.5em;display:flex;align-items:center;justify-content:center}.timeline.horizontal .timeline-line{flex:1;width:auto;height:2px;min-height:0}}\n"], dependencies: [{ kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5153
5159
  }
5154
5160
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: XiriTimelineComponent, decorators: [{
5155
5161
  type: Component,
5156
- args: [{ selector: 'xiri-timeline', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatIcon], template: "<div class=\"timeline\">\n\t@for (item of settings().items; track $index; let last = $last) {\n\t\t<div class=\"timeline-item\" [class.last]=\"last\">\n\t\t\t<div class=\"timeline-marker\">\n\t\t\t\t<div class=\"timeline-dot\" [class]=\"item.iconColor || 'primary'\">\n\t\t\t\t\t@if (item.icon) {\n\t\t\t\t\t\t<mat-icon>{{ item.icon }}</mat-icon>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t@if (!last) {\n\t\t\t\t\t<div class=\"timeline-line\"></div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t\t<div class=\"timeline-content\">\n\t\t\t\t<div class=\"timeline-header\">\n\t\t\t\t\t<span class=\"timeline-title\">{{ item.title }}</span>\n\t\t\t\t\t@if (item.datetime) {\n\t\t\t\t\t\t<span class=\"timeline-datetime\">{{ item.datetime }}</span>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t@if (item.description) {\n\t\t\t\t\t<div class=\"timeline-description\">{{ item.description }}</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</div>\n\t}\n</div>\n", styles: [".timeline{display:flex;flex-direction:column}.timeline-item{display:flex;gap:var(--xiri-spacing-md);animation:fadeInUp var(--xiri-duration-normal, .3s) var(--xiri-easing-decelerate, cubic-bezier(0, 0, .2, 1)) both}.timeline-item:nth-child(1){animation-delay:0ms}.timeline-item:nth-child(2){animation-delay:50ms}.timeline-item:nth-child(3){animation-delay:.1s}.timeline-item:nth-child(4){animation-delay:.15s}.timeline-item:nth-child(5){animation-delay:.2s}.timeline-item:nth-child(6){animation-delay:.25s}.timeline-item:nth-child(7){animation-delay:.3s}.timeline-item:nth-child(8){animation-delay:.35s}.timeline-item:nth-child(9){animation-delay:.4s}.timeline-item:nth-child(10){animation-delay:.45s}.timeline-item:nth-child(11){animation-delay:.5s}.timeline-item:nth-child(12){animation-delay:.55s}.timeline-item:nth-child(13){animation-delay:.6s}.timeline-item:nth-child(14){animation-delay:.65s}.timeline-item:nth-child(15){animation-delay:.7s}.timeline-item:nth-child(16){animation-delay:.75s}.timeline-item:nth-child(17){animation-delay:.8s}.timeline-item:nth-child(18){animation-delay:.85s}.timeline-item:nth-child(19){animation-delay:.9s}.timeline-item:nth-child(20){animation-delay:.95s}.timeline-item.last .timeline-marker .timeline-line{display:none}.timeline-marker{display:flex;flex-direction:column;align-items:center;flex-shrink:0;width:2.5rem}.timeline-dot{width:2.5rem;height:2.5rem;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;background:var(--primary, #2892D9);color:var(--on-primary, white)}.timeline-dot mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}.timeline-dot.primary{background:var(--primary)}.timeline-dot.secondary{background:var(--secondary)}.timeline-dot.tertiary{background:var(--tertiary)}.timeline-dot.accent{background:var(--secondary)}.timeline-dot.warn,.timeline-dot.error{background:var(--error)}.timeline-dot.success{background:var(--color-success)}.timeline-dot.emerald{background:#2892d9}.timeline-dot.red{background:#e74c3c}.timeline-dot.yellow{background:#f1c40f}.timeline-dot.green{background:#2ecc71}.timeline-dot.blue{background:#3498db}.timeline-dot.purple{background:#9b59b6}.timeline-dot.gray{background:#95a5a6}.timeline-dot.lightgray{background:#bdc3c7}.timeline-dot.darkgray{background:#7f8c8d}.timeline-dot.orange{background:#e67e22}.timeline-dot.white{background:#fff}.timeline-dot.black{background:#000}.timeline-dot.inherit{background:inherit}.timeline-line{width:2px;flex:1;min-height:var(--xiri-spacing-lg);background:var(--mat-sys-outline-variant, rgba(0, 0, 0, .12))}.timeline-content{padding-bottom:var(--xiri-spacing-lg);flex:1;min-width:0}.timeline-header{display:flex;align-items:baseline;gap:.75rem;flex-wrap:wrap;min-height:2.5rem;align-content:center}.timeline-title{font-weight:500;font-size:var(--xiri-font-size-body, .875rem);color:var(--mat-sys-on-surface, rgba(0, 0, 0, .87))}.timeline-datetime{font-size:var(--xiri-font-size-helper, .75rem);color:var(--mat-sys-on-surface-variant, rgba(0, 0, 0, .6))}.timeline-description{margin-top:.25rem;font-size:var(--xiri-font-size-body, .875rem);color:var(--mat-sys-on-surface-variant, rgba(0, 0, 0, .6));line-height:1.5}\n"] }]
5162
+ args: [{ selector: 'xiri-timeline', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatIcon], template: "<div class=\"timeline\" [class.horizontal]=\"settings().orientation === 'horizontal'\">\n\t@for (item of settings().items; track $index; let last = $last; let first = $first) {\n\t\t<div class=\"timeline-item\" [class.last]=\"last\" [class.first]=\"first\">\n\t\t\t<div class=\"timeline-marker\">\n\t\t\t\t<div class=\"timeline-line line-before\"></div>\n\t\t\t\t<div class=\"timeline-dot\" [class]=\"item.iconColor || 'primary'\">\n\t\t\t\t\t@if (item.icon) {\n\t\t\t\t\t\t<mat-icon>{{ item.icon }}</mat-icon>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t<div class=\"timeline-line line-after\"></div>\n\t\t\t</div>\n\t\t\t<div class=\"timeline-content\">\n\t\t\t\t<div class=\"timeline-header\">\n\t\t\t\t\t<span class=\"timeline-title\">{{ item.title }}</span>\n\t\t\t\t\t@if (item.datetime) {\n\t\t\t\t\t\t<span class=\"timeline-datetime\">{{ item.datetime }}</span>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t@if (item.description && settings().orientation !== 'horizontal') {\n\t\t\t\t\t<div class=\"timeline-description\">{{ item.description }}</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</div>\n\t}\n</div>\n", styles: [":host{display:block;container-type:inline-size}.timeline{display:flex;flex-direction:column}.timeline-item{display:flex;gap:var(--xiri-spacing-md);animation:fadeInUp var(--xiri-duration-normal, .3s) var(--xiri-easing-decelerate, cubic-bezier(0, 0, .2, 1)) both}.timeline-item:nth-child(1){animation-delay:0ms}.timeline-item:nth-child(2){animation-delay:50ms}.timeline-item:nth-child(3){animation-delay:.1s}.timeline-item:nth-child(4){animation-delay:.15s}.timeline-item:nth-child(5){animation-delay:.2s}.timeline-item:nth-child(6){animation-delay:.25s}.timeline-item:nth-child(7){animation-delay:.3s}.timeline-item:nth-child(8){animation-delay:.35s}.timeline-item:nth-child(9){animation-delay:.4s}.timeline-item:nth-child(10){animation-delay:.45s}.timeline-item:nth-child(11){animation-delay:.5s}.timeline-item:nth-child(12){animation-delay:.55s}.timeline-item:nth-child(13){animation-delay:.6s}.timeline-item:nth-child(14){animation-delay:.65s}.timeline-item:nth-child(15){animation-delay:.7s}.timeline-item:nth-child(16){animation-delay:.75s}.timeline-item:nth-child(17){animation-delay:.8s}.timeline-item:nth-child(18){animation-delay:.85s}.timeline-item:nth-child(19){animation-delay:.9s}.timeline-item:nth-child(20){animation-delay:.95s}.timeline-item .timeline-line.line-before,.timeline-item.last .timeline-line.line-after{display:none}.timeline-marker{display:flex;flex-direction:column;align-items:center;flex-shrink:0;width:2.5rem}.timeline-dot{width:2.5rem;height:2.5rem;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;background:var(--primary, #2892D9);color:var(--on-primary, white)}.timeline-dot mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}.timeline-dot.primary{background:var(--primary)}.timeline-dot.secondary{background:var(--secondary)}.timeline-dot.tertiary{background:var(--tertiary)}.timeline-dot.accent{background:var(--secondary)}.timeline-dot.warn,.timeline-dot.error{background:var(--error)}.timeline-dot.success{background:var(--color-success)}.timeline-dot.emerald{background:#2892d9}.timeline-dot.red{background:#e74c3c}.timeline-dot.yellow{background:#f1c40f}.timeline-dot.green{background:#2ecc71}.timeline-dot.blue{background:#3498db}.timeline-dot.purple{background:#9b59b6}.timeline-dot.gray{background:#95a5a6}.timeline-dot.lightgray{background:#bdc3c7}.timeline-dot.darkgray{background:#7f8c8d}.timeline-dot.orange{background:#e67e22}.timeline-dot.white{background:#fff}.timeline-dot.black{background:#000}.timeline-dot.inherit{background:inherit}.timeline-line{width:2px;flex:1;min-height:var(--xiri-spacing-lg);background:var(--mat-sys-outline-variant, rgba(0, 0, 0, .12))}.timeline-content{padding-bottom:var(--xiri-spacing-lg);flex:1;min-width:0}.timeline-header{display:flex;align-items:baseline;gap:.75rem;flex-wrap:wrap;min-height:2.5rem;align-content:center}.timeline-title{font-weight:500;font-size:var(--xiri-font-size-body, .875rem);color:var(--mat-sys-on-surface, rgba(0, 0, 0, .87))}.timeline-datetime{font-size:var(--xiri-font-size-helper, .75rem);color:var(--mat-sys-on-surface-variant, rgba(0, 0, 0, .6))}.timeline-description{margin-top:.25rem;font-size:var(--xiri-font-size-body, .875rem);color:var(--mat-sys-on-surface-variant, rgba(0, 0, 0, .6));line-height:1.5}@container (min-width: 481px){.timeline.horizontal{flex-direction:row}.timeline.horizontal .timeline-item{flex:1;min-width:0;flex-direction:column;align-items:center;gap:var(--xiri-spacing-xs);padding-bottom:0}.timeline.horizontal .timeline-item .timeline-line.line-before{display:block}.timeline.horizontal .timeline-item.first .timeline-line.line-before{visibility:hidden}.timeline.horizontal .timeline-item.last .timeline-line.line-after{display:block;visibility:hidden}.timeline.horizontal .timeline-content,.timeline.horizontal .timeline-header{display:contents}.timeline.horizontal .timeline-title{order:-2;text-align:center;min-height:2.5em;display:flex;align-items:center;justify-content:center;padding:0 var(--xiri-spacing-xs)}.timeline.horizontal .timeline-marker{order:-1;width:100%;flex-direction:row;align-items:center;gap:0}.timeline.horizontal .timeline-datetime{order:1;text-align:center;min-height:1.5em;display:flex;align-items:center;justify-content:center}.timeline.horizontal .timeline-line{flex:1;width:auto;height:2px;min-height:0}}\n"] }]
5157
5163
  }], propDecorators: { settings: [{ type: i0.Input, args: [{ isSignal: true, alias: "settings", required: true }] }] } });
5158
5164
 
5159
5165
  var timeline_component = /*#__PURE__*/Object.freeze({