@ng-json-render/primitives 0.0.2

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.
@@ -0,0 +1,986 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, ChangeDetectionStrategy, Component, model, inject } from '@angular/core';
3
+ import { JR_CONTEXT, defineRegistry } from '@ng-json-render/core';
4
+
5
+ /** Centered, max-width page container. */
6
+ class JrContainer {
7
+ maxWidth = input(960, ...(ngDevMode ? [{ debugName: "maxWidth" }] : /* istanbul ignore next */ []));
8
+ maxWidthValue = computed(() => {
9
+ const w = this.maxWidth();
10
+ return typeof w === 'number' ? `${w}px` : w;
11
+ }, ...(ngDevMode ? [{ debugName: "maxWidthValue" }] : /* istanbul ignore next */ []));
12
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrContainer, deps: [], target: i0.ɵɵFactoryTarget.Component });
13
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JrContainer, isStandalone: true, selector: "jr-container", inputs: { maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.max-width": "maxWidthValue()", "style.margin-inline": "\"auto\"" }, classAttribute: "block w-full" }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
14
+ }
15
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrContainer, decorators: [{
16
+ type: Component,
17
+ args: [{
18
+ selector: 'jr-container',
19
+ template: `<ng-content />`,
20
+ changeDetection: ChangeDetectionStrategy.OnPush,
21
+ host: {
22
+ class: 'block w-full',
23
+ '[style.max-width]': 'maxWidthValue()',
24
+ '[style.margin-inline]': '"auto"',
25
+ },
26
+ }]
27
+ }], propDecorators: { maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }] } });
28
+ /** Flexbox stack (row or column) with configurable gap and alignment. */
29
+ class JrStack {
30
+ direction = input('column', ...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
31
+ gap = input(12, ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
32
+ align = input(...(ngDevMode ? [undefined, { debugName: "align" }] : /* istanbul ignore next */ []));
33
+ justify = input(...(ngDevMode ? [undefined, { debugName: "justify" }] : /* istanbul ignore next */ []));
34
+ wrap = input(false, ...(ngDevMode ? [{ debugName: "wrap" }] : /* istanbul ignore next */ []));
35
+ gapValue = computed(() => {
36
+ const g = this.gap();
37
+ return typeof g === 'number' ? `${g}px` : g;
38
+ }, ...(ngDevMode ? [{ debugName: "gapValue" }] : /* istanbul ignore next */ []));
39
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrStack, deps: [], target: i0.ɵɵFactoryTarget.Component });
40
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JrStack, isStandalone: true, selector: "jr-stack", inputs: { direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, justify: { classPropertyName: "justify", publicName: "justify", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.flex-direction": "direction()", "style.gap": "gapValue()", "style.align-items": "align()", "style.justify-content": "justify()", "style.flex-wrap": "wrap() ? \"wrap\" : null" }, styleAttribute: "display: flex; box-sizing: border-box;" }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
41
+ }
42
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrStack, decorators: [{
43
+ type: Component,
44
+ args: [{
45
+ selector: 'jr-stack',
46
+ template: `<ng-content />`,
47
+ changeDetection: ChangeDetectionStrategy.OnPush,
48
+ host: {
49
+ style: 'display: flex; box-sizing: border-box;',
50
+ '[style.flex-direction]': 'direction()',
51
+ '[style.gap]': 'gapValue()',
52
+ '[style.align-items]': 'align()',
53
+ '[style.justify-content]': 'justify()',
54
+ '[style.flex-wrap]': 'wrap() ? "wrap" : null',
55
+ },
56
+ }]
57
+ }], propDecorators: { direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], justify: [{ type: i0.Input, args: [{ isSignal: true, alias: "justify", required: false }] }], wrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrap", required: false }] }] } });
58
+ /**
59
+ * Responsive grid. `columns` is the target column count at full width; the grid
60
+ * collapses to fewer columns as the container narrows (each column never shrinks
61
+ * below `minItemWidth`), so specs stay responsive without media queries.
62
+ */
63
+ class JrGrid {
64
+ columns = input(2, ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
65
+ gap = input(16, ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
66
+ /** Minimum width a column may shrink to before the grid drops a column. */
67
+ minItemWidth = input(200, ...(ngDevMode ? [{ debugName: "minItemWidth" }] : /* istanbul ignore next */ []));
68
+ gapValue = computed(() => {
69
+ const g = this.gap();
70
+ return typeof g === 'number' ? `${g}px` : g;
71
+ }, ...(ngDevMode ? [{ debugName: "gapValue" }] : /* istanbul ignore next */ []));
72
+ templateColumns = computed(() => {
73
+ const cols = Math.max(1, this.columns());
74
+ if (cols === 1)
75
+ return '1fr';
76
+ const gap = this.gapValue();
77
+ const min = this.minItemWidth();
78
+ // Width of one column when `cols` fit; `auto-fit` drops columns once this
79
+ // would fall below `minItemWidth`. `min(100%, …)` avoids overflow on tiny screens.
80
+ const ideal = `calc((100% - (${cols} - 1) * ${gap}) / ${cols})`;
81
+ return `repeat(auto-fit, minmax(min(100%, max(${min}px, ${ideal})), 1fr))`;
82
+ }, ...(ngDevMode ? [{ debugName: "templateColumns" }] : /* istanbul ignore next */ []));
83
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrGrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
84
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JrGrid, isStandalone: true, selector: "jr-grid", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, minItemWidth: { classPropertyName: "minItemWidth", publicName: "minItemWidth", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.grid-template-columns": "templateColumns()", "style.gap": "gapValue()" }, styleAttribute: "display: grid; box-sizing: border-box;" }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
85
+ }
86
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrGrid, decorators: [{
87
+ type: Component,
88
+ args: [{
89
+ selector: 'jr-grid',
90
+ template: `<ng-content />`,
91
+ changeDetection: ChangeDetectionStrategy.OnPush,
92
+ host: {
93
+ style: 'display: grid; box-sizing: border-box;',
94
+ '[style.grid-template-columns]': 'templateColumns()',
95
+ '[style.gap]': 'gapValue()',
96
+ },
97
+ }]
98
+ }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], minItemWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minItemWidth", required: false }] }] } });
99
+ /** Bordered surface with an optional title/subtitle. */
100
+ class JrCard {
101
+ title = input(...(ngDevMode ? [undefined, { debugName: "title" }] : /* istanbul ignore next */ []));
102
+ subtitle = input(...(ngDevMode ? [undefined, { debugName: "subtitle" }] : /* istanbul ignore next */ []));
103
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
104
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JrCard, isStandalone: true, selector: "jr-card", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block rounded-xl border border-zinc-200 bg-white p-5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900" }, ngImport: i0, template: `
105
+ @if (title() || subtitle()) {
106
+ <div class="mb-3">
107
+ @if (title()) {
108
+ <div
109
+ class="text-sm font-semibold text-zinc-900 dark:text-zinc-100"
110
+ >
111
+ {{ title() }}
112
+ </div>
113
+ }
114
+ @if (subtitle()) {
115
+ <div class="text-xs text-zinc-500 dark:text-zinc-400">
116
+ {{ subtitle() }}
117
+ </div>
118
+ }
119
+ </div>
120
+ }
121
+ <ng-content />
122
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
123
+ }
124
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrCard, decorators: [{
125
+ type: Component,
126
+ args: [{
127
+ selector: 'jr-card',
128
+ template: `
129
+ @if (title() || subtitle()) {
130
+ <div class="mb-3">
131
+ @if (title()) {
132
+ <div
133
+ class="text-sm font-semibold text-zinc-900 dark:text-zinc-100"
134
+ >
135
+ {{ title() }}
136
+ </div>
137
+ }
138
+ @if (subtitle()) {
139
+ <div class="text-xs text-zinc-500 dark:text-zinc-400">
140
+ {{ subtitle() }}
141
+ </div>
142
+ }
143
+ </div>
144
+ }
145
+ <ng-content />
146
+ `,
147
+ changeDetection: ChangeDetectionStrategy.OnPush,
148
+ host: {
149
+ class: 'block rounded-xl border border-zinc-200 bg-white p-5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900',
150
+ },
151
+ }]
152
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }] } });
153
+ /** Horizontal rule. */
154
+ class JrDivider {
155
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrDivider, deps: [], target: i0.ɵɵFactoryTarget.Component });
156
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: JrDivider, isStandalone: true, selector: "jr-divider", host: { attributes: { "role": "separator" }, classAttribute: "block border-t border-zinc-200 dark:border-zinc-800" }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
157
+ }
158
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrDivider, decorators: [{
159
+ type: Component,
160
+ args: [{
161
+ selector: 'jr-divider',
162
+ template: '',
163
+ changeDetection: ChangeDetectionStrategy.OnPush,
164
+ host: {
165
+ class: 'block border-t border-zinc-200 dark:border-zinc-800',
166
+ role: 'separator',
167
+ },
168
+ }]
169
+ }] });
170
+
171
+ /** Section heading (levels 1–4). */
172
+ class JrHeading {
173
+ value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
174
+ level = input(2, ...(ngDevMode ? [{ debugName: "level" }] : /* istanbul ignore next */ []));
175
+ sizeClass = computed(() => {
176
+ const base = 'block font-semibold tracking-tight text-zinc-900 dark:text-zinc-50 ';
177
+ return (base +
178
+ { 1: 'text-3xl', 2: 'text-2xl', 3: 'text-lg', 4: 'text-base' }[this.level()]);
179
+ }, ...(ngDevMode ? [{ debugName: "sizeClass" }] : /* istanbul ignore next */ []));
180
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrHeading, deps: [], target: i0.ɵɵFactoryTarget.Component });
181
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JrHeading, isStandalone: true, selector: "jr-heading", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "sizeClass()" } }, ngImport: i0, template: `{{ value() }}<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
182
+ }
183
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrHeading, decorators: [{
184
+ type: Component,
185
+ args: [{
186
+ selector: 'jr-heading',
187
+ template: `{{ value() }}<ng-content />`,
188
+ changeDetection: ChangeDetectionStrategy.OnPush,
189
+ host: {
190
+ '[class]': 'sizeClass()',
191
+ },
192
+ }]
193
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: false }] }] } });
194
+ /** Body text. */
195
+ class JrText {
196
+ value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
197
+ weight = input('normal', ...(ngDevMode ? [{ debugName: "weight" }] : /* istanbul ignore next */ []));
198
+ size = input('base', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
199
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrText, deps: [], target: i0.ɵɵFactoryTarget.Component });
200
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JrText, isStandalone: true, selector: "jr-text", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, weight: { classPropertyName: "weight", publicName: "weight", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.font-medium": "weight() === \"medium\"", "class.font-semibold": "weight() === \"bold\"", "class.text-sm": "size() === \"sm\"", "class.text-xs": "size() === \"xs\"", "class.text-lg": "size() === \"lg\"" }, classAttribute: "text-zinc-600 dark:text-zinc-300" }, ngImport: i0, template: `{{ value() }}<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
201
+ }
202
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrText, decorators: [{
203
+ type: Component,
204
+ args: [{
205
+ selector: 'jr-text',
206
+ template: `{{ value() }}<ng-content />`,
207
+ changeDetection: ChangeDetectionStrategy.OnPush,
208
+ host: {
209
+ class: 'text-zinc-600 dark:text-zinc-300',
210
+ '[class.font-medium]': 'weight() === "medium"',
211
+ '[class.font-semibold]': 'weight() === "bold"',
212
+ '[class.text-sm]': 'size() === "sm"',
213
+ '[class.text-xs]': 'size() === "xs"',
214
+ '[class.text-lg]': 'size() === "lg"',
215
+ },
216
+ }]
217
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], weight: [{ type: i0.Input, args: [{ isSignal: true, alias: "weight", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
218
+ const BADGE_TONES = {
219
+ neutral: 'bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300',
220
+ success: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400',
221
+ warning: 'bg-amber-100 text-amber-700 dark:bg-amber-500/15 dark:text-amber-400',
222
+ danger: 'bg-red-100 text-red-700 dark:bg-red-500/15 dark:text-red-400',
223
+ info: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300',
224
+ };
225
+ /** Small status pill. */
226
+ class JrBadge {
227
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
228
+ tone = input('neutral', ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
229
+ toneClass = computed(() => 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ' +
230
+ (BADGE_TONES[this.tone()] ?? BADGE_TONES['neutral']), ...(ngDevMode ? [{ debugName: "toneClass" }] : /* istanbul ignore next */ []));
231
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrBadge, deps: [], target: i0.ɵɵFactoryTarget.Component });
232
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JrBadge, isStandalone: true, selector: "jr-badge", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "toneClass()" } }, ngImport: i0, template: `{{ label() }}<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
233
+ }
234
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrBadge, decorators: [{
235
+ type: Component,
236
+ args: [{
237
+ selector: 'jr-badge',
238
+ template: `{{ label() }}<ng-content />`,
239
+ changeDetection: ChangeDetectionStrategy.OnPush,
240
+ host: {
241
+ '[class]': 'toneClass()',
242
+ },
243
+ }]
244
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }] } });
245
+ /** KPI tile: a big value with a label and an optional delta. */
246
+ class JrStat {
247
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
248
+ value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
249
+ delta = input(null, ...(ngDevMode ? [{ debugName: "delta" }] : /* istanbul ignore next */ []));
250
+ absDelta = computed(() => Math.abs(this.delta() ?? 0), ...(ngDevMode ? [{ debugName: "absDelta" }] : /* istanbul ignore next */ []));
251
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrStat, deps: [], target: i0.ɵɵFactoryTarget.Component });
252
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JrStat, isStandalone: true, selector: "jr-stat", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, delta: { classPropertyName: "delta", publicName: "delta", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
253
+ <div class="text-sm font-medium text-zinc-500 dark:text-zinc-400">
254
+ {{ label() }}
255
+ </div>
256
+ <div class="mt-1 flex items-baseline gap-2">
257
+ <span class="text-2xl font-semibold text-zinc-900 dark:text-zinc-50">
258
+ {{ value() }}
259
+ </span>
260
+ @if (delta() !== null) {
261
+ <span
262
+ class="text-xs font-medium"
263
+ [class.text-emerald-600]="(delta() ?? 0) >= 0"
264
+ [class.text-red-600]="(delta() ?? 0) < 0"
265
+ >
266
+ {{ (delta() ?? 0) >= 0 ? '▲' : '▼' }} {{ absDelta() }}%
267
+ </span>
268
+ }
269
+ </div>
270
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
271
+ }
272
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrStat, decorators: [{
273
+ type: Component,
274
+ args: [{
275
+ selector: 'jr-stat',
276
+ template: `
277
+ <div class="text-sm font-medium text-zinc-500 dark:text-zinc-400">
278
+ {{ label() }}
279
+ </div>
280
+ <div class="mt-1 flex items-baseline gap-2">
281
+ <span class="text-2xl font-semibold text-zinc-900 dark:text-zinc-50">
282
+ {{ value() }}
283
+ </span>
284
+ @if (delta() !== null) {
285
+ <span
286
+ class="text-xs font-medium"
287
+ [class.text-emerald-600]="(delta() ?? 0) >= 0"
288
+ [class.text-red-600]="(delta() ?? 0) < 0"
289
+ >
290
+ {{ (delta() ?? 0) >= 0 ? '▲' : '▼' }} {{ absDelta() }}%
291
+ </span>
292
+ }
293
+ </div>
294
+ `,
295
+ changeDetection: ChangeDetectionStrategy.OnPush,
296
+ host: { class: 'block' },
297
+ }]
298
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], delta: [{ type: i0.Input, args: [{ isSignal: true, alias: "delta", required: false }] }] } });
299
+
300
+ const ALERT_TONES = {
301
+ info: 'border-indigo-200 bg-indigo-50 text-indigo-800 dark:border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-200',
302
+ success: 'border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-200',
303
+ warning: 'border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200',
304
+ danger: 'border-red-200 bg-red-50 text-red-800 dark:border-red-500/30 dark:bg-red-500/10 dark:text-red-200',
305
+ };
306
+ /** Inline callout / alert banner. */
307
+ class JrAlert {
308
+ title = input(...(ngDevMode ? [undefined, { debugName: "title" }] : /* istanbul ignore next */ []));
309
+ message = input('', ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
310
+ tone = input('info', ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
311
+ toneClass = computed(() => 'block rounded-lg border px-4 py-3 ' +
312
+ (ALERT_TONES[this.tone()] ?? ALERT_TONES['info']), ...(ngDevMode ? [{ debugName: "toneClass" }] : /* istanbul ignore next */ []));
313
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrAlert, deps: [], target: i0.ɵɵFactoryTarget.Component });
314
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JrAlert, isStandalone: true, selector: "jr-alert", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "alert" }, properties: { "class": "toneClass()" } }, ngImport: i0, template: `
315
+ @if (title()) {
316
+ <div class="font-medium">{{ title() }}</div>
317
+ }
318
+ <div class="text-sm" [class.mt-0.5]="!!title()">
319
+ {{ message() }}<ng-content />
320
+ </div>
321
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
322
+ }
323
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrAlert, decorators: [{
324
+ type: Component,
325
+ args: [{
326
+ selector: 'jr-alert',
327
+ template: `
328
+ @if (title()) {
329
+ <div class="font-medium">{{ title() }}</div>
330
+ }
331
+ <div class="text-sm" [class.mt-0.5]="!!title()">
332
+ {{ message() }}<ng-content />
333
+ </div>
334
+ `,
335
+ changeDetection: ChangeDetectionStrategy.OnPush,
336
+ host: {
337
+ role: 'alert',
338
+ '[class]': 'toneClass()',
339
+ },
340
+ }]
341
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }] } });
342
+ /** Determinate progress bar (0–100). */
343
+ class JrProgress {
344
+ value = input(0, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
345
+ label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
346
+ clamped = computed(() => Math.max(0, Math.min(100, Math.round(this.value()))), ...(ngDevMode ? [{ debugName: "clamped" }] : /* istanbul ignore next */ []));
347
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrProgress, deps: [], target: i0.ɵɵFactoryTarget.Component });
348
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JrProgress, isStandalone: true, selector: "jr-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
349
+ <div
350
+ class="h-2 w-full overflow-hidden rounded-full bg-zinc-200 dark:bg-zinc-800"
351
+ >
352
+ <div
353
+ class="h-full rounded-full bg-indigo-600 transition-[width] duration-300"
354
+ [style.width.%]="clamped()"
355
+ ></div>
356
+ </div>
357
+ @if (label()) {
358
+ <div class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">
359
+ {{ label() }} · {{ clamped() }}%
360
+ </div>
361
+ }
362
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
363
+ }
364
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrProgress, decorators: [{
365
+ type: Component,
366
+ args: [{
367
+ selector: 'jr-progress',
368
+ template: `
369
+ <div
370
+ class="h-2 w-full overflow-hidden rounded-full bg-zinc-200 dark:bg-zinc-800"
371
+ >
372
+ <div
373
+ class="h-full rounded-full bg-indigo-600 transition-[width] duration-300"
374
+ [style.width.%]="clamped()"
375
+ ></div>
376
+ </div>
377
+ @if (label()) {
378
+ <div class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">
379
+ {{ label() }} · {{ clamped() }}%
380
+ </div>
381
+ }
382
+ `,
383
+ changeDetection: ChangeDetectionStrategy.OnPush,
384
+ host: { class: 'block' },
385
+ }]
386
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
387
+
388
+ /** Responsive CSS bar chart (no chart library). */
389
+ class JrBarChart {
390
+ data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
391
+ height = input(160, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
392
+ max = computed(() => Math.max(1, ...this.data().map((d) => d.value)), ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
393
+ pct(value) {
394
+ return Math.max(2, (value / this.max()) * 100);
395
+ }
396
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrBarChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
397
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JrBarChart, isStandalone: true, selector: "jr-bar-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
398
+ <div class="flex items-end gap-2" [style.height.px]="height()">
399
+ @for (d of data(); track $index) {
400
+ <div class="flex h-full flex-1 items-end">
401
+ <div
402
+ class="w-full rounded-t-md bg-indigo-500 transition-[height] duration-300 dark:bg-indigo-400"
403
+ [style.height.%]="pct(d.value)"
404
+ [title]="d.label + ': ' + d.value"
405
+ ></div>
406
+ </div>
407
+ }
408
+ </div>
409
+ <div class="mt-1.5 flex gap-2">
410
+ @for (d of data(); track $index) {
411
+ <div
412
+ class="flex-1 truncate text-center text-xs text-zinc-500 dark:text-zinc-400"
413
+ >
414
+ {{ d.label }}
415
+ </div>
416
+ }
417
+ </div>
418
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
419
+ }
420
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrBarChart, decorators: [{
421
+ type: Component,
422
+ args: [{
423
+ selector: 'jr-bar-chart',
424
+ template: `
425
+ <div class="flex items-end gap-2" [style.height.px]="height()">
426
+ @for (d of data(); track $index) {
427
+ <div class="flex h-full flex-1 items-end">
428
+ <div
429
+ class="w-full rounded-t-md bg-indigo-500 transition-[height] duration-300 dark:bg-indigo-400"
430
+ [style.height.%]="pct(d.value)"
431
+ [title]="d.label + ': ' + d.value"
432
+ ></div>
433
+ </div>
434
+ }
435
+ </div>
436
+ <div class="mt-1.5 flex gap-2">
437
+ @for (d of data(); track $index) {
438
+ <div
439
+ class="flex-1 truncate text-center text-xs text-zinc-500 dark:text-zinc-400"
440
+ >
441
+ {{ d.label }}
442
+ </div>
443
+ }
444
+ </div>
445
+ `,
446
+ changeDetection: ChangeDetectionStrategy.OnPush,
447
+ host: { class: 'block' },
448
+ }]
449
+ }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }] } });
450
+ /** Responsive SVG line/area chart (no chart library). */
451
+ class JrLineChart {
452
+ data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
453
+ height = input(120, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
454
+ geom = computed(() => {
455
+ const d = this.data();
456
+ if (d.length < 2)
457
+ return { line: '', area: '' };
458
+ const w = 300;
459
+ const h = 100;
460
+ const max = Math.max(...d);
461
+ const min = Math.min(...d);
462
+ const range = max - min || 1;
463
+ const step = w / (d.length - 1);
464
+ const pts = d.map((v, i) => {
465
+ const x = i * step;
466
+ const y = h - ((v - min) / range) * (h * 0.9) - h * 0.05;
467
+ return `${x.toFixed(1)},${y.toFixed(1)}`;
468
+ });
469
+ return { line: pts.join(' '), area: `0,${h} ${pts.join(' ')} ${w},${h}` };
470
+ }, ...(ngDevMode ? [{ debugName: "geom" }] : /* istanbul ignore next */ []));
471
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrLineChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
472
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JrLineChart, isStandalone: true, selector: "jr-line-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
473
+ <svg
474
+ viewBox="0 0 300 100"
475
+ preserveAspectRatio="none"
476
+ class="w-full"
477
+ [style.height.px]="height()"
478
+ aria-hidden="true"
479
+ >
480
+ @if (geom().line) {
481
+ <polygon
482
+ [attr.points]="geom().area"
483
+ class="fill-indigo-500/10 dark:fill-indigo-400/10"
484
+ />
485
+ <polyline
486
+ [attr.points]="geom().line"
487
+ fill="none"
488
+ class="stroke-indigo-500 dark:stroke-indigo-400"
489
+ stroke-width="2"
490
+ stroke-linejoin="round"
491
+ stroke-linecap="round"
492
+ vector-effect="non-scaling-stroke"
493
+ />
494
+ }
495
+ </svg>
496
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
497
+ }
498
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrLineChart, decorators: [{
499
+ type: Component,
500
+ args: [{
501
+ selector: 'jr-line-chart',
502
+ template: `
503
+ <svg
504
+ viewBox="0 0 300 100"
505
+ preserveAspectRatio="none"
506
+ class="w-full"
507
+ [style.height.px]="height()"
508
+ aria-hidden="true"
509
+ >
510
+ @if (geom().line) {
511
+ <polygon
512
+ [attr.points]="geom().area"
513
+ class="fill-indigo-500/10 dark:fill-indigo-400/10"
514
+ />
515
+ <polyline
516
+ [attr.points]="geom().line"
517
+ fill="none"
518
+ class="stroke-indigo-500 dark:stroke-indigo-400"
519
+ stroke-width="2"
520
+ stroke-linejoin="round"
521
+ stroke-linecap="round"
522
+ vector-effect="non-scaling-stroke"
523
+ />
524
+ }
525
+ </svg>
526
+ `,
527
+ changeDetection: ChangeDetectionStrategy.OnPush,
528
+ host: { class: 'block' },
529
+ }]
530
+ }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }] } });
531
+
532
+ /** Simple data table. */
533
+ class JrTable {
534
+ columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
535
+ rows = input([], ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
536
+ cols = computed(() => this.columns().map((c) => typeof c === 'string'
537
+ ? { key: c, label: c }
538
+ : { key: c.key, label: c.label ?? c.key }), ...(ngDevMode ? [{ debugName: "cols" }] : /* istanbul ignore next */ []));
539
+ cell(row, key) {
540
+ const v = row[key];
541
+ return v == null ? '' : String(v);
542
+ }
543
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
544
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JrTable, isStandalone: true, selector: "jr-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
545
+ <div
546
+ class="overflow-x-auto rounded-xl border border-zinc-200 dark:border-zinc-800"
547
+ >
548
+ <table class="w-full border-collapse text-sm">
549
+ <thead>
550
+ <tr class="bg-zinc-50 dark:bg-zinc-800/50">
551
+ @for (col of cols(); track col.key) {
552
+ <th
553
+ class="px-4 py-2.5 text-left font-medium text-zinc-500 dark:text-zinc-400"
554
+ >
555
+ {{ col.label }}
556
+ </th>
557
+ }
558
+ </tr>
559
+ </thead>
560
+ <tbody>
561
+ @for (row of rows(); track $index) {
562
+ <tr
563
+ class="border-t border-zinc-100 dark:border-zinc-800/70"
564
+ >
565
+ @for (col of cols(); track col.key) {
566
+ <td class="px-4 py-2.5 text-zinc-700 dark:text-zinc-300">
567
+ {{ cell(row, col.key) }}
568
+ </td>
569
+ }
570
+ </tr>
571
+ }
572
+ </tbody>
573
+ </table>
574
+ </div>
575
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
576
+ }
577
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrTable, decorators: [{
578
+ type: Component,
579
+ args: [{
580
+ selector: 'jr-table',
581
+ template: `
582
+ <div
583
+ class="overflow-x-auto rounded-xl border border-zinc-200 dark:border-zinc-800"
584
+ >
585
+ <table class="w-full border-collapse text-sm">
586
+ <thead>
587
+ <tr class="bg-zinc-50 dark:bg-zinc-800/50">
588
+ @for (col of cols(); track col.key) {
589
+ <th
590
+ class="px-4 py-2.5 text-left font-medium text-zinc-500 dark:text-zinc-400"
591
+ >
592
+ {{ col.label }}
593
+ </th>
594
+ }
595
+ </tr>
596
+ </thead>
597
+ <tbody>
598
+ @for (row of rows(); track $index) {
599
+ <tr
600
+ class="border-t border-zinc-100 dark:border-zinc-800/70"
601
+ >
602
+ @for (col of cols(); track col.key) {
603
+ <td class="px-4 py-2.5 text-zinc-700 dark:text-zinc-300">
604
+ {{ cell(row, col.key) }}
605
+ </td>
606
+ }
607
+ </tr>
608
+ }
609
+ </tbody>
610
+ </table>
611
+ </div>
612
+ `,
613
+ changeDetection: ChangeDetectionStrategy.OnPush,
614
+ host: { class: 'block' },
615
+ }]
616
+ }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }] } });
617
+
618
+ // Note: these controls expose a `value` / `checked` model(), which *structurally*
619
+ // satisfies Angular's Signal Forms `FormValueControl` / `FormCheckboxControl`
620
+ // contracts — so on Angular 21 they work with the `[field]` directive — without
621
+ // importing `@angular/forms/signals`, keeping the package usable on Angular 19+.
622
+ const FIELD_LABEL = 'text-sm font-medium text-zinc-700 dark:text-zinc-300';
623
+ const CONTROL = 'w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm text-zinc-900 shadow-sm outline-none placeholder:text-zinc-400 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100';
624
+ /**
625
+ * Text input. A Signal Forms Signal Forms value control — usable with the `[field]`
626
+ * directive, or driven by `$bindState` through the renderer.
627
+ */
628
+ class JrInput {
629
+ value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
630
+ label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
631
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
632
+ type = input('text', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
633
+ hint = input(...(ngDevMode ? [undefined, { debugName: "hint" }] : /* istanbul ignore next */ []));
634
+ labelClass = FIELD_LABEL;
635
+ controlClass = CONTROL;
636
+ read(e) {
637
+ return e.target.value;
638
+ }
639
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
640
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JrInput, isStandalone: true, selector: "jr-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { classAttribute: "block" }, ngImport: i0, template: `
641
+ <label class="flex flex-col gap-1.5">
642
+ @if (label()) {
643
+ <span [class]="labelClass">{{ label() }}</span>
644
+ }
645
+ <input
646
+ [type]="type()"
647
+ [value]="value()"
648
+ [placeholder]="placeholder()"
649
+ [class]="controlClass"
650
+ (input)="value.set(read($event))"
651
+ />
652
+ @if (hint()) {
653
+ <span class="text-xs text-zinc-500 dark:text-zinc-400">{{
654
+ hint()
655
+ }}</span>
656
+ }
657
+ </label>
658
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
659
+ }
660
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrInput, decorators: [{
661
+ type: Component,
662
+ args: [{
663
+ selector: 'jr-input',
664
+ template: `
665
+ <label class="flex flex-col gap-1.5">
666
+ @if (label()) {
667
+ <span [class]="labelClass">{{ label() }}</span>
668
+ }
669
+ <input
670
+ [type]="type()"
671
+ [value]="value()"
672
+ [placeholder]="placeholder()"
673
+ [class]="controlClass"
674
+ (input)="value.set(read($event))"
675
+ />
676
+ @if (hint()) {
677
+ <span class="text-xs text-zinc-500 dark:text-zinc-400">{{
678
+ hint()
679
+ }}</span>
680
+ }
681
+ </label>
682
+ `,
683
+ changeDetection: ChangeDetectionStrategy.OnPush,
684
+ host: { class: 'block' },
685
+ }]
686
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }] } });
687
+ /** Multi-line text input (Signal Forms value control). */
688
+ class JrTextarea {
689
+ value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
690
+ label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
691
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
692
+ rows = input(3, ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
693
+ labelClass = FIELD_LABEL;
694
+ controlClass = CONTROL;
695
+ read(e) {
696
+ return e.target.value;
697
+ }
698
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrTextarea, deps: [], target: i0.ɵɵFactoryTarget.Component });
699
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JrTextarea, isStandalone: true, selector: "jr-textarea", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { classAttribute: "block" }, ngImport: i0, template: `
700
+ <label class="flex flex-col gap-1.5">
701
+ @if (label()) {
702
+ <span [class]="labelClass">{{ label() }}</span>
703
+ }
704
+ <textarea
705
+ [value]="value()"
706
+ [placeholder]="placeholder()"
707
+ [rows]="rows()"
708
+ [class]="controlClass"
709
+ (input)="value.set(read($event))"
710
+ ></textarea>
711
+ </label>
712
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
713
+ }
714
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrTextarea, decorators: [{
715
+ type: Component,
716
+ args: [{
717
+ selector: 'jr-textarea',
718
+ template: `
719
+ <label class="flex flex-col gap-1.5">
720
+ @if (label()) {
721
+ <span [class]="labelClass">{{ label() }}</span>
722
+ }
723
+ <textarea
724
+ [value]="value()"
725
+ [placeholder]="placeholder()"
726
+ [rows]="rows()"
727
+ [class]="controlClass"
728
+ (input)="value.set(read($event))"
729
+ ></textarea>
730
+ </label>
731
+ `,
732
+ changeDetection: ChangeDetectionStrategy.OnPush,
733
+ host: { class: 'block' },
734
+ }]
735
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }] } });
736
+ /** Dropdown select (Signal Forms value control). */
737
+ class JrSelect {
738
+ value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
739
+ label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
740
+ placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
741
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
742
+ labelClass = FIELD_LABEL;
743
+ controlClass = CONTROL;
744
+ normalized = computed(() => this.options().map((o) => typeof o === 'string'
745
+ ? { value: o, label: o }
746
+ : { value: o.value, label: o.label ?? o.value }), ...(ngDevMode ? [{ debugName: "normalized" }] : /* istanbul ignore next */ []));
747
+ read(e) {
748
+ return e.target.value;
749
+ }
750
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
751
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JrSelect, isStandalone: true, selector: "jr-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { classAttribute: "block" }, ngImport: i0, template: `
752
+ <label class="flex flex-col gap-1.5">
753
+ @if (label()) {
754
+ <span [class]="labelClass">{{ label() }}</span>
755
+ }
756
+ <select
757
+ [value]="value()"
758
+ [class]="controlClass"
759
+ (change)="value.set(read($event))"
760
+ >
761
+ @if (placeholder()) {
762
+ <option value="" disabled>{{ placeholder() }}</option>
763
+ }
764
+ @for (opt of normalized(); track opt.value) {
765
+ <option [value]="opt.value">{{ opt.label }}</option>
766
+ }
767
+ </select>
768
+ </label>
769
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
770
+ }
771
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrSelect, decorators: [{
772
+ type: Component,
773
+ args: [{
774
+ selector: 'jr-select',
775
+ template: `
776
+ <label class="flex flex-col gap-1.5">
777
+ @if (label()) {
778
+ <span [class]="labelClass">{{ label() }}</span>
779
+ }
780
+ <select
781
+ [value]="value()"
782
+ [class]="controlClass"
783
+ (change)="value.set(read($event))"
784
+ >
785
+ @if (placeholder()) {
786
+ <option value="" disabled>{{ placeholder() }}</option>
787
+ }
788
+ @for (opt of normalized(); track opt.value) {
789
+ <option [value]="opt.value">{{ opt.label }}</option>
790
+ }
791
+ </select>
792
+ </label>
793
+ `,
794
+ changeDetection: ChangeDetectionStrategy.OnPush,
795
+ host: { class: 'block' },
796
+ }]
797
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }] } });
798
+ /** Checkbox (Signal Forms checkbox control). */
799
+ class JrCheckbox {
800
+ checked = model(false, ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
801
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
802
+ read(e) {
803
+ return e.target.checked;
804
+ }
805
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrCheckbox, deps: [], target: i0.ɵɵFactoryTarget.Component });
806
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JrCheckbox, isStandalone: true, selector: "jr-checkbox", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange" }, host: { classAttribute: "block" }, ngImport: i0, template: `
807
+ <label class="flex cursor-pointer items-center gap-2.5">
808
+ <input
809
+ type="checkbox"
810
+ class="h-4 w-4 rounded border-zinc-300 text-indigo-600 focus:ring-indigo-500/40 dark:border-zinc-600 dark:bg-zinc-800"
811
+ [checked]="checked()"
812
+ (change)="checked.set(read($event))"
813
+ />
814
+ <span class="text-sm text-zinc-700 dark:text-zinc-300">{{
815
+ label()
816
+ }}</span>
817
+ </label>
818
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
819
+ }
820
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrCheckbox, decorators: [{
821
+ type: Component,
822
+ args: [{
823
+ selector: 'jr-checkbox',
824
+ template: `
825
+ <label class="flex cursor-pointer items-center gap-2.5">
826
+ <input
827
+ type="checkbox"
828
+ class="h-4 w-4 rounded border-zinc-300 text-indigo-600 focus:ring-indigo-500/40 dark:border-zinc-600 dark:bg-zinc-800"
829
+ [checked]="checked()"
830
+ (change)="checked.set(read($event))"
831
+ />
832
+ <span class="text-sm text-zinc-700 dark:text-zinc-300">{{
833
+ label()
834
+ }}</span>
835
+ </label>
836
+ `,
837
+ changeDetection: ChangeDetectionStrategy.OnPush,
838
+ host: { class: 'block' },
839
+ }]
840
+ }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
841
+ /** Toggle switch (Signal Forms checkbox control). */
842
+ class JrSwitch {
843
+ checked = model(false, ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
844
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
845
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrSwitch, deps: [], target: i0.ɵɵFactoryTarget.Component });
846
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JrSwitch, isStandalone: true, selector: "jr-switch", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange" }, host: { classAttribute: "block" }, ngImport: i0, template: `
847
+ <div class="flex cursor-pointer items-center justify-between gap-3">
848
+ <span class="text-sm text-zinc-700 dark:text-zinc-300">{{
849
+ label()
850
+ }}</span>
851
+ <button
852
+ type="button"
853
+ role="switch"
854
+ [attr.aria-checked]="checked()"
855
+ (click)="checked.set(!checked())"
856
+ class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors"
857
+ [class.bg-indigo-600]="checked()"
858
+ [class.bg-zinc-300]="!checked()"
859
+ [class.dark:bg-zinc-700]="!checked()"
860
+ >
861
+ <span
862
+ class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform"
863
+ [class.translate-x-6]="checked()"
864
+ [class.translate-x-1]="!checked()"
865
+ ></span>
866
+ </button>
867
+ </div>
868
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
869
+ }
870
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrSwitch, decorators: [{
871
+ type: Component,
872
+ args: [{
873
+ selector: 'jr-switch',
874
+ template: `
875
+ <div class="flex cursor-pointer items-center justify-between gap-3">
876
+ <span class="text-sm text-zinc-700 dark:text-zinc-300">{{
877
+ label()
878
+ }}</span>
879
+ <button
880
+ type="button"
881
+ role="switch"
882
+ [attr.aria-checked]="checked()"
883
+ (click)="checked.set(!checked())"
884
+ class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors"
885
+ [class.bg-indigo-600]="checked()"
886
+ [class.bg-zinc-300]="!checked()"
887
+ [class.dark:bg-zinc-700]="!checked()"
888
+ >
889
+ <span
890
+ class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform"
891
+ [class.translate-x-6]="checked()"
892
+ [class.translate-x-1]="!checked()"
893
+ ></span>
894
+ </button>
895
+ </div>
896
+ `,
897
+ changeDetection: ChangeDetectionStrategy.OnPush,
898
+ host: { class: 'block' },
899
+ }]
900
+ }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
901
+ const BUTTON_VARIANTS = {
902
+ primary: 'bg-indigo-600 text-white hover:bg-indigo-500 focus-visible:ring-indigo-500/40',
903
+ secondary: 'border border-zinc-300 bg-white text-zinc-800 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:hover:bg-zinc-800',
904
+ ghost: 'text-zinc-700 hover:bg-zinc-100 dark:text-zinc-200 dark:hover:bg-zinc-800',
905
+ danger: 'bg-red-600 text-white hover:bg-red-500 focus-visible:ring-red-500/40',
906
+ };
907
+ /** Button that emits the `press` action. */
908
+ class JrButton {
909
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
910
+ variant = input('primary', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
911
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
912
+ ctx = inject(JR_CONTEXT);
913
+ buttonClass = computed(() => 'inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium transition-colors outline-none focus-visible:ring-2 disabled:opacity-50 disabled:pointer-events-none ' +
914
+ (BUTTON_VARIANTS[this.variant()] ?? BUTTON_VARIANTS['primary']), ...(ngDevMode ? [{ debugName: "buttonClass" }] : /* istanbul ignore next */ []));
915
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
916
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JrButton, isStandalone: true, selector: "jr-button", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "inline-block" }, ngImport: i0, template: `
917
+ <button
918
+ type="button"
919
+ [class]="buttonClass()"
920
+ [disabled]="disabled()"
921
+ (click)="ctx.emit('press')"
922
+ >
923
+ {{ label() }}<ng-content />
924
+ </button>
925
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
926
+ }
927
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JrButton, decorators: [{
928
+ type: Component,
929
+ args: [{
930
+ selector: 'jr-button',
931
+ template: `
932
+ <button
933
+ type="button"
934
+ [class]="buttonClass()"
935
+ [disabled]="disabled()"
936
+ (click)="ctx.emit('press')"
937
+ >
938
+ {{ label() }}<ng-content />
939
+ </button>
940
+ `,
941
+ changeDetection: ChangeDetectionStrategy.OnPush,
942
+ host: { class: 'inline-block' },
943
+ }]
944
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
945
+
946
+ /**
947
+ * Registry of all built-in primitive components, keyed by catalog `type`.
948
+ * Pass to `<jr-renderer [registry]="primitivesRegistry">` or
949
+ * `provideJsonRender({ registry: primitivesRegistry })`.
950
+ */
951
+ const primitivesRegistry = defineRegistry({
952
+ // layout
953
+ Container: JrContainer,
954
+ Stack: JrStack,
955
+ Grid: JrGrid,
956
+ Card: JrCard,
957
+ Divider: JrDivider,
958
+ // content
959
+ Heading: JrHeading,
960
+ Text: JrText,
961
+ Badge: JrBadge,
962
+ Stat: JrStat,
963
+ // feedback
964
+ Alert: JrAlert,
965
+ Progress: JrProgress,
966
+ // data
967
+ Table: JrTable,
968
+ BarChart: JrBarChart,
969
+ LineChart: JrLineChart,
970
+ // forms
971
+ Input: JrInput,
972
+ Textarea: JrTextarea,
973
+ Select: JrSelect,
974
+ Checkbox: JrCheckbox,
975
+ Switch: JrSwitch,
976
+ Button: JrButton,
977
+ });
978
+
979
+ // layout
980
+
981
+ /**
982
+ * Generated bundle index. Do not edit.
983
+ */
984
+
985
+ export { JrAlert, JrBadge, JrBarChart, JrButton, JrCard, JrCheckbox, JrContainer, JrDivider, JrGrid, JrHeading, JrInput, JrLineChart, JrProgress, JrSelect, JrStack, JrStat, JrSwitch, JrTable, JrText, JrTextarea, primitivesRegistry };
986
+ //# sourceMappingURL=ng-json-render-primitives.mjs.map