@xiriframework/xiri-ng 0.2.17 → 0.2.19

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
 
@@ -2776,8 +2776,12 @@ class XiriResponseHandlerService {
2776
2776
  return;
2777
2777
  if (result.page == 'refresh' || result.refresh == 'page')
2778
2778
  this.router.navigate([this.router.url]).then();
2779
- else if (result.table == 'refresh' || result.refresh == 'table')
2780
- callbacks?.onTableRefresh?.() ?? this.router.navigate([this.router.url]).then();
2779
+ else if (result.table == 'refresh' || result.refresh == 'table') {
2780
+ if (callbacks?.onTableRefresh)
2781
+ callbacks.onTableRefresh();
2782
+ else
2783
+ this.router.navigate([this.router.url]).then();
2784
+ }
2781
2785
  else if (result.goto)
2782
2786
  this.router.navigate([result.goto]).then();
2783
2787
  else if (result.table == 'update' || result.update == 'table')
@@ -5145,11 +5149,11 @@ class XiriTimelineComponent {
5145
5149
  this.settings = input.required(...(ngDevMode ? [{ debugName: "settings" }] : /* istanbul ignore next */ []));
5146
5150
  }
5147
5151
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: XiriTimelineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5148
- 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 }); }
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\" [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 }); }
5149
5153
  }
5150
5154
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: XiriTimelineComponent, decorators: [{
5151
5155
  type: Component,
5152
- 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"] }]
5156
+ 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"] }]
5153
5157
  }], propDecorators: { settings: [{ type: i0.Input, args: [{ isSignal: true, alias: "settings", required: true }] }] } });
5154
5158
 
5155
5159
  var timeline_component = /*#__PURE__*/Object.freeze({