@kinetixui/angular 0.24.0
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/.npmignore +2 -0
- package/LICENSE +21 -0
- package/fesm2022/kinetixui-angular.mjs +1962 -0
- package/fesm2022/kinetixui-angular.mjs.map +1 -0
- package/package.json +48 -0
- package/styles.css +782 -0
- package/types/kinetixui-angular.d.ts +801 -0
|
@@ -0,0 +1,1962 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, computed, Directive, ChangeDetectionStrategy, Component, model, booleanAttribute, output, signal, forwardRef, contentChildren, effect, inject, ElementRef } from '@angular/core';
|
|
3
|
+
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The shared design contract, in Angular's type system.
|
|
7
|
+
*
|
|
8
|
+
* These unions are the same variant and size names the React, SwiftUI, Jetpack Compose and Flutter
|
|
9
|
+
* implementations use. That is what "parity" means in KinetixUI: the contract and the tokens are shared, the
|
|
10
|
+
* implementation is idiomatic per platform. Renaming one of these values is a cross-platform change, not an
|
|
11
|
+
* Angular one.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* KinetixUI button.
|
|
16
|
+
*
|
|
17
|
+
* <button kxButton>Save</button>
|
|
18
|
+
* <button kxButton variant="Outline" size="lg">Cancel</button>
|
|
19
|
+
* <a kxButton variant="Link" href="/docs">Docs</a>
|
|
20
|
+
*
|
|
21
|
+
* A directive on a real `<button>` or `<a>`, not a `<kx-button>` wrapper. That is the idiomatic Angular choice
|
|
22
|
+
* and the accessible one: the element keeps its own role, its `type`, `disabled`, form submission, focus order
|
|
23
|
+
* and the browser's own activation behaviour, so none of it has to be re-implemented with ARIA. §28 of the
|
|
24
|
+
* house rules — native semantics before ARIA — is the reason, not convenience.
|
|
25
|
+
*
|
|
26
|
+
* A disabled anchor is not a thing in HTML, so the anchor form is disabled with `aria-disabled` plus the
|
|
27
|
+
* stylesheet's `pointer-events: none`; callers should also drop `href`. The directive does not silently rewrite
|
|
28
|
+
* an anchor into a button.
|
|
29
|
+
*/
|
|
30
|
+
class KxButton {
|
|
31
|
+
variant = input('Primary', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
32
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
33
|
+
corners = input('default', ...(ngDevMode ? [{ debugName: "corners" }] : /* istanbul ignore next */ []));
|
|
34
|
+
classes = computed(() => {
|
|
35
|
+
const out = ['kx-btn', `kx-btn--${this.variant().toLowerCase()}`, `kx-btn--${this.size()}`];
|
|
36
|
+
// "default" corners means "whatever the control radius token says", so it adds no class
|
|
37
|
+
if (this.corners() !== 'default')
|
|
38
|
+
out.push(`kx-btn--${this.corners()}`);
|
|
39
|
+
return out.join(' ');
|
|
40
|
+
}, ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
41
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxButton, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
42
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.23", type: KxButton, isStandalone: true, selector: "button[kxButton], a[kxButton]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, corners: { classPropertyName: "corners", publicName: "corners", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.data-variant": "variant()", "attr.data-size": "size()" } }, ngImport: i0 });
|
|
43
|
+
}
|
|
44
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxButton, decorators: [{
|
|
45
|
+
type: Directive,
|
|
46
|
+
args: [{
|
|
47
|
+
selector: 'button[kxButton], a[kxButton]',
|
|
48
|
+
host: {
|
|
49
|
+
'[class]': 'classes()',
|
|
50
|
+
// exposed for application CSS and tests; mirrors the data-variant attribute the React button sets
|
|
51
|
+
'[attr.data-variant]': 'variant()',
|
|
52
|
+
'[attr.data-size]': 'size()',
|
|
53
|
+
},
|
|
54
|
+
}]
|
|
55
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], corners: [{ type: i0.Input, args: [{ isSignal: true, alias: "corners", required: false }] }] } });
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The presentational primitives: badge, card family, label, input, separator, progress and alert.
|
|
59
|
+
*
|
|
60
|
+
* Each is either a directive on the element HTML already has for the job (label, input) or a component that
|
|
61
|
+
* renders one (badge, card, alert). Nothing here invents a role the platform already provides.
|
|
62
|
+
*/
|
|
63
|
+
/* ── badge ──────────────────────────────────────────────────────────────── */
|
|
64
|
+
/**
|
|
65
|
+
* <kx-badge>New</kx-badge>
|
|
66
|
+
* <kx-badge variant="success">Synced</kx-badge>
|
|
67
|
+
*
|
|
68
|
+
* Presentational by default. A badge that conveys a status change the user must be told about should be given
|
|
69
|
+
* a live region by the caller — this does not guess, because a wrong `aria-live` is worse than none.
|
|
70
|
+
*/
|
|
71
|
+
class KxBadge {
|
|
72
|
+
variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
73
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxBadge, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
74
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxBadge, isStandalone: true, selector: "kx-badge", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "\"kx-badge kx-badge--\" + variant()" } }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
75
|
+
}
|
|
76
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxBadge, decorators: [{
|
|
77
|
+
type: Component,
|
|
78
|
+
args: [{
|
|
79
|
+
selector: 'kx-badge',
|
|
80
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
81
|
+
template: '<ng-content />',
|
|
82
|
+
host: { '[class]': '"kx-badge kx-badge--" + variant()' },
|
|
83
|
+
}]
|
|
84
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }] } });
|
|
85
|
+
/* ── card ───────────────────────────────────────────────────────────────── */
|
|
86
|
+
class KxCard {
|
|
87
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
88
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxCard, isStandalone: true, selector: "kx-card", host: { classAttribute: "kx-card" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
89
|
+
}
|
|
90
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCard, decorators: [{
|
|
91
|
+
type: Component,
|
|
92
|
+
args: [{
|
|
93
|
+
selector: 'kx-card',
|
|
94
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
95
|
+
template: '<ng-content />',
|
|
96
|
+
host: { class: 'kx-card' },
|
|
97
|
+
}]
|
|
98
|
+
}] });
|
|
99
|
+
class KxCardHeader {
|
|
100
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCardHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
101
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxCardHeader, isStandalone: true, selector: "kx-card-header", host: { classAttribute: "kx-card-header" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
102
|
+
}
|
|
103
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCardHeader, decorators: [{
|
|
104
|
+
type: Component,
|
|
105
|
+
args: [{
|
|
106
|
+
selector: 'kx-card-header',
|
|
107
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
108
|
+
template: '<ng-content />',
|
|
109
|
+
host: { class: 'kx-card-header' },
|
|
110
|
+
}]
|
|
111
|
+
}] });
|
|
112
|
+
/**
|
|
113
|
+
* A real heading, at a level the page chooses. `level` exists because a card's title is only an `<h3>` in the
|
|
114
|
+
* example — in a page that already has an `<h3>` the right level is whatever keeps the document outline
|
|
115
|
+
* correct, and a component that hard-codes one produces broken heading structure at scale.
|
|
116
|
+
*
|
|
117
|
+
* The level is carried by `role="heading"` + `aria-level` on the host rather than by switching between `<h1>`
|
|
118
|
+
* … `<h6>` tags. That is not a shortcut: `<ng-content>` projects its content exactly once, so a template with
|
|
119
|
+
* a heading tag per branch silently renders an empty heading for every branch after the first. Between a real
|
|
120
|
+
* tag that loses its text and an ARIA heading that keeps it, the accessible answer is the one with the text in
|
|
121
|
+
* it — and `role="heading"` with an explicit level is fully supported.
|
|
122
|
+
*/
|
|
123
|
+
class KxCardTitle {
|
|
124
|
+
level = input(3, ...(ngDevMode ? [{ debugName: "level" }] : /* istanbul ignore next */ []));
|
|
125
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCardTitle, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
126
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxCardTitle, isStandalone: true, selector: "kx-card-title", inputs: { level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "heading" }, properties: { "attr.aria-level": "level()" }, classAttribute: "kx-card-title" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
127
|
+
}
|
|
128
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCardTitle, decorators: [{
|
|
129
|
+
type: Component,
|
|
130
|
+
args: [{
|
|
131
|
+
selector: 'kx-card-title',
|
|
132
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
133
|
+
template: '<ng-content />',
|
|
134
|
+
host: {
|
|
135
|
+
class: 'kx-card-title',
|
|
136
|
+
role: 'heading',
|
|
137
|
+
'[attr.aria-level]': 'level()',
|
|
138
|
+
},
|
|
139
|
+
}]
|
|
140
|
+
}], propDecorators: { level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: false }] }] } });
|
|
141
|
+
class KxCardDescription {
|
|
142
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCardDescription, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
143
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxCardDescription, isStandalone: true, selector: "kx-card-description", ngImport: i0, template: '<p class="kx-card-description"><ng-content /></p>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
144
|
+
}
|
|
145
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCardDescription, decorators: [{
|
|
146
|
+
type: Component,
|
|
147
|
+
args: [{
|
|
148
|
+
selector: 'kx-card-description',
|
|
149
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
150
|
+
template: '<p class="kx-card-description"><ng-content /></p>',
|
|
151
|
+
}]
|
|
152
|
+
}] });
|
|
153
|
+
class KxCardContent {
|
|
154
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCardContent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
155
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxCardContent, isStandalone: true, selector: "kx-card-content", host: { classAttribute: "kx-card-content" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
156
|
+
}
|
|
157
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCardContent, decorators: [{
|
|
158
|
+
type: Component,
|
|
159
|
+
args: [{
|
|
160
|
+
selector: 'kx-card-content',
|
|
161
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
162
|
+
template: '<ng-content />',
|
|
163
|
+
host: { class: 'kx-card-content' },
|
|
164
|
+
}]
|
|
165
|
+
}] });
|
|
166
|
+
class KxCardFooter {
|
|
167
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCardFooter, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
168
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxCardFooter, isStandalone: true, selector: "kx-card-footer", host: { classAttribute: "kx-card-footer" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
169
|
+
}
|
|
170
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCardFooter, decorators: [{
|
|
171
|
+
type: Component,
|
|
172
|
+
args: [{
|
|
173
|
+
selector: 'kx-card-footer',
|
|
174
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
175
|
+
template: '<ng-content />',
|
|
176
|
+
host: { class: 'kx-card-footer' },
|
|
177
|
+
}]
|
|
178
|
+
}] });
|
|
179
|
+
/* ── label ──────────────────────────────────────────────────────────────── */
|
|
180
|
+
/**
|
|
181
|
+
* <label kxLabel for="email">Email</label>
|
|
182
|
+
*
|
|
183
|
+
* A directive on a real `<label>`, so `for`/`id` association and click-to-focus are the browser's, not ours.
|
|
184
|
+
*/
|
|
185
|
+
class KxLabel {
|
|
186
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
187
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.23", type: KxLabel, isStandalone: true, selector: "label[kxLabel]", host: { classAttribute: "kx-label" }, ngImport: i0 });
|
|
188
|
+
}
|
|
189
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxLabel, decorators: [{
|
|
190
|
+
type: Directive,
|
|
191
|
+
args: [{
|
|
192
|
+
selector: 'label[kxLabel]',
|
|
193
|
+
host: { class: 'kx-label' },
|
|
194
|
+
}]
|
|
195
|
+
}] });
|
|
196
|
+
/* ── input ──────────────────────────────────────────────────────────────── */
|
|
197
|
+
/**
|
|
198
|
+
* <input kxInput type="email" [(ngModel)]="email" />
|
|
199
|
+
*
|
|
200
|
+
* A directive, so `type`, `required`, `disabled`, autofill, the browser's own validation and every Angular
|
|
201
|
+
* forms directive keep working untouched. The error state is read from `aria-invalid`, which means the
|
|
202
|
+
* semantics drive the styling rather than a parallel `invalid` input that could disagree with them.
|
|
203
|
+
*
|
|
204
|
+
* Multi-line fields are `kxTextarea`, not this: a textarea has its own minimum height and resize affordance,
|
|
205
|
+
* and the React package splits them for the same reason.
|
|
206
|
+
*/
|
|
207
|
+
class KxInput {
|
|
208
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxInput, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
209
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.23", type: KxInput, isStandalone: true, selector: "input[kxInput]", host: { classAttribute: "kx-input" }, ngImport: i0 });
|
|
210
|
+
}
|
|
211
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxInput, decorators: [{
|
|
212
|
+
type: Directive,
|
|
213
|
+
args: [{
|
|
214
|
+
selector: 'input[kxInput]',
|
|
215
|
+
host: { class: 'kx-input' },
|
|
216
|
+
}]
|
|
217
|
+
}] });
|
|
218
|
+
/* ── separator ──────────────────────────────────────────────────────────── */
|
|
219
|
+
/**
|
|
220
|
+
* <kx-separator />
|
|
221
|
+
* <kx-separator orientation="vertical" />
|
|
222
|
+
*
|
|
223
|
+
* Decorative by default and therefore hidden from assistive technology, which is what a rule between two
|
|
224
|
+
* visually grouped things usually is. Set `[decorative]="false"` to expose it as a real `separator`.
|
|
225
|
+
*/
|
|
226
|
+
class KxSeparator {
|
|
227
|
+
orientation = input('horizontal', ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
228
|
+
decorative = input(true, ...(ngDevMode ? [{ debugName: "decorative" }] : /* istanbul ignore next */ []));
|
|
229
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSeparator, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
230
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxSeparator, isStandalone: true, selector: "kx-separator", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, decorative: { classPropertyName: "decorative", publicName: "decorative", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "\"kx-separator kx-separator--\" + orientation()", "attr.role": "decorative() ? \"none\" : \"separator\"", "attr.aria-hidden": "decorative() ? \"true\" : null", "attr.aria-orientation": "decorative() || orientation() === \"horizontal\" ? null : \"vertical\"" } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
231
|
+
}
|
|
232
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSeparator, decorators: [{
|
|
233
|
+
type: Component,
|
|
234
|
+
args: [{
|
|
235
|
+
selector: 'kx-separator',
|
|
236
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
237
|
+
template: '',
|
|
238
|
+
host: {
|
|
239
|
+
'[class]': '"kx-separator kx-separator--" + orientation()',
|
|
240
|
+
'[attr.role]': 'decorative() ? "none" : "separator"',
|
|
241
|
+
'[attr.aria-hidden]': 'decorative() ? "true" : null',
|
|
242
|
+
'[attr.aria-orientation]': 'decorative() || orientation() === "horizontal" ? null : "vertical"',
|
|
243
|
+
},
|
|
244
|
+
}]
|
|
245
|
+
}], propDecorators: { orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], decorative: [{ type: i0.Input, args: [{ isSignal: true, alias: "decorative", required: false }] }] } });
|
|
246
|
+
/* ── progress ───────────────────────────────────────────────────────────── */
|
|
247
|
+
/**
|
|
248
|
+
* <kx-progress [value]="62" />
|
|
249
|
+
* <kx-progress [value]="null" /> <!-- indeterminate -->
|
|
250
|
+
*
|
|
251
|
+
* A real `progressbar`. `value` is null for indeterminate, which drops `aria-valuenow` — the correct way to
|
|
252
|
+
* say "in progress, amount unknown", rather than reporting a fake 0.
|
|
253
|
+
*/
|
|
254
|
+
class KxProgress {
|
|
255
|
+
value = input(0, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
256
|
+
max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
257
|
+
/** Announced instead of the raw percentage when the number alone would not mean anything ("3 of 7 files"). */
|
|
258
|
+
valueText = input(null, ...(ngDevMode ? [{ debugName: "valueText" }] : /* istanbul ignore next */ []));
|
|
259
|
+
percent = computed(() => {
|
|
260
|
+
const v = this.value();
|
|
261
|
+
if (v === null)
|
|
262
|
+
return 100; // indeterminate: a full track, animated by CSS rather than a fake number
|
|
263
|
+
return Math.max(0, Math.min(100, (v / this.max()) * 100));
|
|
264
|
+
}, ...(ngDevMode ? [{ debugName: "percent" }] : /* istanbul ignore next */ []));
|
|
265
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxProgress, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
266
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxProgress, isStandalone: true, selector: "kx-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, valueText: { classPropertyName: "valueText", publicName: "valueText", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "progressbar" }, properties: { "attr.aria-valuenow": "value()", "attr.aria-valuemin": "0", "attr.aria-valuemax": "max()", "attr.aria-valuetext": "valueText()" }, classAttribute: "kx-progress" }, ngImport: i0, template: '<div class="kx-progress__bar" [style.inline-size.%]="percent()"></div>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
267
|
+
}
|
|
268
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxProgress, decorators: [{
|
|
269
|
+
type: Component,
|
|
270
|
+
args: [{
|
|
271
|
+
selector: 'kx-progress',
|
|
272
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
273
|
+
template: '<div class="kx-progress__bar" [style.inline-size.%]="percent()"></div>',
|
|
274
|
+
host: {
|
|
275
|
+
class: 'kx-progress',
|
|
276
|
+
role: 'progressbar',
|
|
277
|
+
'[attr.aria-valuenow]': 'value()',
|
|
278
|
+
'[attr.aria-valuemin]': '0',
|
|
279
|
+
'[attr.aria-valuemax]': 'max()',
|
|
280
|
+
'[attr.aria-valuetext]': 'valueText()',
|
|
281
|
+
},
|
|
282
|
+
}]
|
|
283
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], valueText: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueText", required: false }] }] } });
|
|
284
|
+
/* ── alert ──────────────────────────────────────────────────────────────── */
|
|
285
|
+
/**
|
|
286
|
+
* <kx-alert variant="destructive">
|
|
287
|
+
* <kx-alert-title>Payment failed</kx-alert-title>
|
|
288
|
+
* <kx-alert-description>Your card was declined.</kx-alert-description>
|
|
289
|
+
* </kx-alert>
|
|
290
|
+
*
|
|
291
|
+
* `role` defaults to `alert` only for the variants that carry urgency. A neutral informational panel announced
|
|
292
|
+
* as an assertive live region interrupts a screen-reader user for no reason, so `default` and `info` render as
|
|
293
|
+
* a plain region and the caller can opt in.
|
|
294
|
+
*/
|
|
295
|
+
class KxAlert {
|
|
296
|
+
variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
297
|
+
role = computed(() => (this.variant() === 'destructive' || this.variant() === 'warning' ? 'alert' : 'note'), ...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
|
|
298
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAlert, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
299
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxAlert, isStandalone: true, selector: "kx-alert", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "\"kx-alert kx-alert--\" + variant()", "attr.role": "role()" } }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
300
|
+
}
|
|
301
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAlert, decorators: [{
|
|
302
|
+
type: Component,
|
|
303
|
+
args: [{
|
|
304
|
+
selector: 'kx-alert',
|
|
305
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
306
|
+
template: '<ng-content />',
|
|
307
|
+
host: {
|
|
308
|
+
'[class]': '"kx-alert kx-alert--" + variant()',
|
|
309
|
+
'[attr.role]': 'role()',
|
|
310
|
+
},
|
|
311
|
+
}]
|
|
312
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }] } });
|
|
313
|
+
class KxAlertTitle {
|
|
314
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAlertTitle, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
315
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxAlertTitle, isStandalone: true, selector: "kx-alert-title", ngImport: i0, template: '<p class="kx-alert-title"><ng-content /></p>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
316
|
+
}
|
|
317
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAlertTitle, decorators: [{
|
|
318
|
+
type: Component,
|
|
319
|
+
args: [{
|
|
320
|
+
selector: 'kx-alert-title',
|
|
321
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
322
|
+
template: '<p class="kx-alert-title"><ng-content /></p>',
|
|
323
|
+
}]
|
|
324
|
+
}] });
|
|
325
|
+
class KxAlertDescription {
|
|
326
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAlertDescription, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
327
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxAlertDescription, isStandalone: true, selector: "kx-alert-description", ngImport: i0, template: '<p class="kx-alert-description"><ng-content /></p>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
328
|
+
}
|
|
329
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAlertDescription, decorators: [{
|
|
330
|
+
type: Component,
|
|
331
|
+
args: [{
|
|
332
|
+
selector: 'kx-alert-description',
|
|
333
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
334
|
+
template: '<p class="kx-alert-description"><ng-content /></p>',
|
|
335
|
+
}]
|
|
336
|
+
}] });
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Per-element ids for the `<input>`/`<label>` pairs below. A module counter rather than a static on each
|
|
340
|
+
* class: a static field cannot be read by an instance initialiser declared above it, and the id only has to
|
|
341
|
+
* be unique within the document.
|
|
342
|
+
*/
|
|
343
|
+
let seq$1 = 0;
|
|
344
|
+
const uid$1 = () => ++seq$1;
|
|
345
|
+
/**
|
|
346
|
+
* Checkbox and Switch — the two binary controls, and the package's forms architecture in miniature.
|
|
347
|
+
*
|
|
348
|
+
* Both implement `ControlValueAccessor`, so they work with `[(ngModel)]`, `formControlName` and
|
|
349
|
+
* `formControl` exactly like a native input, including `disabled` driven by the form model
|
|
350
|
+
* (`setDisabledState`) rather than only by an attribute. Both also expose a plain `[(checked)]` model for
|
|
351
|
+
* applications that are not using Angular forms; the two paths write through the same setter so they cannot
|
|
352
|
+
* disagree.
|
|
353
|
+
*
|
|
354
|
+
* Both render a real `<button>` carrying the ARIA role rather than a visually-hidden `<input>`. A button is
|
|
355
|
+
* already focusable, already activates on Space and Enter, and already reports `disabled` to assistive
|
|
356
|
+
* technology — so the role is the only thing ARIA has to add. The trade-off is deliberate and worth stating:
|
|
357
|
+
* a `<button role="checkbox">` submits no value in a native (non-Angular) form post. Angular forms carry the
|
|
358
|
+
* value in the form model, which is how these are meant to be used.
|
|
359
|
+
*/
|
|
360
|
+
/**
|
|
361
|
+
* Shared CVA plumbing: the parts that would otherwise be copied verbatim into both controls.
|
|
362
|
+
*
|
|
363
|
+
* `@Directive()` with no selector, not a plain class — `input()`, `model()` and `output()` are only legal on a
|
|
364
|
+
* class Angular's compiler knows about, so an undecorated base would fail the build with NG8110.
|
|
365
|
+
*/
|
|
366
|
+
class KxToggleBase {
|
|
367
|
+
checked = model(false, ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
|
|
368
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
369
|
+
/**
|
|
370
|
+
* Emits only on real user interaction. `checkedChange` (from the model) also fires when a form or a parent
|
|
371
|
+
* writes the value in, which is usually not what a caller reacting to "the user flipped this" wants.
|
|
372
|
+
*/
|
|
373
|
+
toggled = output();
|
|
374
|
+
/** `disabled` can come from the input OR from the form model — either one disables the control. */
|
|
375
|
+
formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : /* istanbul ignore next */ []));
|
|
376
|
+
isDisabled() {
|
|
377
|
+
return this.disabled() || this.formDisabled();
|
|
378
|
+
}
|
|
379
|
+
onChange = () => { };
|
|
380
|
+
onTouched = () => { };
|
|
381
|
+
toggle() {
|
|
382
|
+
if (this.isDisabled())
|
|
383
|
+
return;
|
|
384
|
+
const next = !this.checked();
|
|
385
|
+
this.checked.set(next);
|
|
386
|
+
this.onChange(next);
|
|
387
|
+
this.onTouched();
|
|
388
|
+
this.toggled.emit(next);
|
|
389
|
+
}
|
|
390
|
+
writeValue(value) {
|
|
391
|
+
this.checked.set(!!value);
|
|
392
|
+
}
|
|
393
|
+
registerOnChange(fn) {
|
|
394
|
+
this.onChange = fn;
|
|
395
|
+
}
|
|
396
|
+
registerOnTouched(fn) {
|
|
397
|
+
this.onTouched = fn;
|
|
398
|
+
}
|
|
399
|
+
setDisabledState(isDisabled) {
|
|
400
|
+
this.formDisabled.set(isDisabled);
|
|
401
|
+
}
|
|
402
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxToggleBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
403
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.23", type: KxToggleBase, isStandalone: true, inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", toggled: "toggled" }, ngImport: i0 });
|
|
404
|
+
}
|
|
405
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxToggleBase, decorators: [{
|
|
406
|
+
type: Directive
|
|
407
|
+
}], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], toggled: [{ type: i0.Output, args: ["toggled"] }] } });
|
|
408
|
+
/**
|
|
409
|
+
* <kx-checkbox [(ngModel)]="remember" />
|
|
410
|
+
* <kx-checkbox [(checked)]="remember" (toggled)="save($event)" />
|
|
411
|
+
*
|
|
412
|
+
* `indeterminate` renders `aria-checked="mixed"` — the tri-state a "select all" header needs. It is display
|
|
413
|
+
* only: activating an indeterminate checkbox resolves it to checked, as it does everywhere else.
|
|
414
|
+
*/
|
|
415
|
+
class KxCheckbox extends KxToggleBase {
|
|
416
|
+
indeterminate = input(false, { ...(ngDevMode ? { debugName: "indeterminate" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
417
|
+
/** One of these is required when no visible `<label for>` points at the control. */
|
|
418
|
+
ariaLabel = input(null, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
419
|
+
ariaLabelledby = input(null, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
420
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCheckbox, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
421
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: KxCheckbox, isStandalone: true, selector: "kx-checkbox", inputs: { indeterminate: { classPropertyName: "indeterminate", publicName: "indeterminate", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => KxCheckbox), multi: true }], usesInheritance: true, ngImport: i0, template: `
|
|
422
|
+
<button
|
|
423
|
+
type="button"
|
|
424
|
+
role="checkbox"
|
|
425
|
+
class="kx-checkbox"
|
|
426
|
+
[attr.aria-checked]="indeterminate() ? 'mixed' : checked()"
|
|
427
|
+
[attr.aria-label]="ariaLabel()"
|
|
428
|
+
[attr.aria-labelledby]="ariaLabelledby()"
|
|
429
|
+
[disabled]="isDisabled()"
|
|
430
|
+
(click)="toggle()"
|
|
431
|
+
>
|
|
432
|
+
@if (checked() || indeterminate()) {
|
|
433
|
+
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
434
|
+
@if (indeterminate()) {
|
|
435
|
+
<path d="M4 8h8" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
|
436
|
+
} @else {
|
|
437
|
+
<path d="M3.5 8.5l3 3 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
438
|
+
}
|
|
439
|
+
</svg>
|
|
440
|
+
}
|
|
441
|
+
</button>
|
|
442
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
443
|
+
}
|
|
444
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxCheckbox, decorators: [{
|
|
445
|
+
type: Component,
|
|
446
|
+
args: [{
|
|
447
|
+
selector: 'kx-checkbox',
|
|
448
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
449
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => KxCheckbox), multi: true }],
|
|
450
|
+
template: `
|
|
451
|
+
<button
|
|
452
|
+
type="button"
|
|
453
|
+
role="checkbox"
|
|
454
|
+
class="kx-checkbox"
|
|
455
|
+
[attr.aria-checked]="indeterminate() ? 'mixed' : checked()"
|
|
456
|
+
[attr.aria-label]="ariaLabel()"
|
|
457
|
+
[attr.aria-labelledby]="ariaLabelledby()"
|
|
458
|
+
[disabled]="isDisabled()"
|
|
459
|
+
(click)="toggle()"
|
|
460
|
+
>
|
|
461
|
+
@if (checked() || indeterminate()) {
|
|
462
|
+
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
463
|
+
@if (indeterminate()) {
|
|
464
|
+
<path d="M4 8h8" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
|
465
|
+
} @else {
|
|
466
|
+
<path d="M3.5 8.5l3 3 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
467
|
+
}
|
|
468
|
+
</svg>
|
|
469
|
+
}
|
|
470
|
+
</button>
|
|
471
|
+
`,
|
|
472
|
+
}]
|
|
473
|
+
}], propDecorators: { indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }] } });
|
|
474
|
+
/**
|
|
475
|
+
* <kx-switch [(ngModel)]="notifications" aria-label="Notifications" />
|
|
476
|
+
*
|
|
477
|
+
* `role="switch"` rather than a checkbox: it reports "on/off" instead of "checked", which is what an
|
|
478
|
+
* immediately-applied setting is.
|
|
479
|
+
*/
|
|
480
|
+
class KxSwitch extends KxToggleBase {
|
|
481
|
+
ariaLabel = input(null, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
482
|
+
ariaLabelledby = input(null, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
483
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSwitch, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
484
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxSwitch, isStandalone: true, selector: "kx-switch", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => KxSwitch), multi: true }], usesInheritance: true, ngImport: i0, template: `
|
|
485
|
+
<button
|
|
486
|
+
type="button"
|
|
487
|
+
role="switch"
|
|
488
|
+
class="kx-switch"
|
|
489
|
+
[attr.aria-checked]="checked()"
|
|
490
|
+
[attr.aria-label]="ariaLabel()"
|
|
491
|
+
[attr.aria-labelledby]="ariaLabelledby()"
|
|
492
|
+
[disabled]="isDisabled()"
|
|
493
|
+
(click)="toggle()"
|
|
494
|
+
>
|
|
495
|
+
<span class="kx-switch__thumb"></span>
|
|
496
|
+
</button>
|
|
497
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
498
|
+
}
|
|
499
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSwitch, decorators: [{
|
|
500
|
+
type: Component,
|
|
501
|
+
args: [{
|
|
502
|
+
selector: 'kx-switch',
|
|
503
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
504
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => KxSwitch), multi: true }],
|
|
505
|
+
template: `
|
|
506
|
+
<button
|
|
507
|
+
type="button"
|
|
508
|
+
role="switch"
|
|
509
|
+
class="kx-switch"
|
|
510
|
+
[attr.aria-checked]="checked()"
|
|
511
|
+
[attr.aria-label]="ariaLabel()"
|
|
512
|
+
[attr.aria-labelledby]="ariaLabelledby()"
|
|
513
|
+
[disabled]="isDisabled()"
|
|
514
|
+
(click)="toggle()"
|
|
515
|
+
>
|
|
516
|
+
<span class="kx-switch__thumb"></span>
|
|
517
|
+
</button>
|
|
518
|
+
`,
|
|
519
|
+
}]
|
|
520
|
+
}], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }] } });
|
|
521
|
+
/* ── toggle ─────────────────────────────────────────────────────────────── */
|
|
522
|
+
/**
|
|
523
|
+
* <button kxToggle [(pressed)]="bold" aria-label="Bold">B</button>
|
|
524
|
+
*
|
|
525
|
+
* A directive on a real `<button>` that carries `aria-pressed` — the ARIA pattern for a two-state button,
|
|
526
|
+
* and the one thing ARIA has to add to a button that already has focus, activation and disabled handling.
|
|
527
|
+
*/
|
|
528
|
+
class KxToggle {
|
|
529
|
+
pressed = model(false, ...(ngDevMode ? [{ debugName: "pressed" }] : /* istanbul ignore next */ []));
|
|
530
|
+
variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
531
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
532
|
+
toggled = output();
|
|
533
|
+
press() {
|
|
534
|
+
const next = !this.pressed();
|
|
535
|
+
this.pressed.set(next);
|
|
536
|
+
this.toggled.emit(next);
|
|
537
|
+
}
|
|
538
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxToggle, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
539
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.23", type: KxToggle, isStandalone: true, selector: "button[kxToggle]", inputs: { pressed: { classPropertyName: "pressed", publicName: "pressed", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pressed: "pressedChange", toggled: "toggled" }, host: { listeners: { "click": "press()" }, properties: { "class": "\"kx-toggle kx-toggle--\" + variant() + \" kx-toggle--\" + size()", "attr.aria-pressed": "pressed()" } }, ngImport: i0 });
|
|
540
|
+
}
|
|
541
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxToggle, decorators: [{
|
|
542
|
+
type: Directive,
|
|
543
|
+
args: [{
|
|
544
|
+
selector: 'button[kxToggle]',
|
|
545
|
+
host: {
|
|
546
|
+
'[class]': '"kx-toggle kx-toggle--" + variant() + " kx-toggle--" + size()',
|
|
547
|
+
'[attr.aria-pressed]': 'pressed()',
|
|
548
|
+
'(click)': 'press()',
|
|
549
|
+
},
|
|
550
|
+
}]
|
|
551
|
+
}], propDecorators: { pressed: [{ type: i0.Input, args: [{ isSignal: true, alias: "pressed", required: false }] }, { type: i0.Output, args: ["pressedChange"] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], toggled: [{ type: i0.Output, args: ["toggled"] }] } });
|
|
552
|
+
/* ── toggle group ───────────────────────────────────────────────────────── */
|
|
553
|
+
/**
|
|
554
|
+
* <kx-toggle-group [(value)]="marks" aria-label="Text style">
|
|
555
|
+
* <kx-toggle-group-item value="bold">B</kx-toggle-group-item>
|
|
556
|
+
* </kx-toggle-group>
|
|
557
|
+
*
|
|
558
|
+
* Multiple-select by default: each item is an independent `aria-pressed` button inside a `role="group"`, so
|
|
559
|
+
* every one is its own tab stop and its own announcement — which is what a formatting toolbar is.
|
|
560
|
+
*
|
|
561
|
+
* `type="single"` is a different control, and is rendered as one: a `radiogroup` of real `<input
|
|
562
|
+
* type="radio">` elements, so arrow-key selection and the single tab stop come from the browser. Sharing a
|
|
563
|
+
* template between the two would mean re-implementing radio behaviour with ARIA, which is exactly the
|
|
564
|
+
* trade this package refuses elsewhere.
|
|
565
|
+
*/
|
|
566
|
+
class KxToggleGroup {
|
|
567
|
+
type = input('multiple', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
568
|
+
/** A string for `single`, an array for `multiple`. */
|
|
569
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
570
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
571
|
+
name = input(`kx-toggle-group-${uid$1()}`, ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
572
|
+
isOn(value) {
|
|
573
|
+
const v = this.value();
|
|
574
|
+
return Array.isArray(v) ? v.includes(value) : v === value;
|
|
575
|
+
}
|
|
576
|
+
toggle(value) {
|
|
577
|
+
if (this.disabled())
|
|
578
|
+
return;
|
|
579
|
+
if (this.type() === 'single') {
|
|
580
|
+
this.value.set(this.value() === value ? null : value);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const current = Array.isArray(this.value()) ? this.value() : [];
|
|
584
|
+
this.value.set(current.includes(value) ? current.filter((v) => v !== value) : [...current, value]);
|
|
585
|
+
}
|
|
586
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxToggleGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
587
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxToggleGroup, isStandalone: true, selector: "kx-toggle-group", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "attr.role": "type() === 'single' ? 'radiogroup' : 'group'" }, classAttribute: "kx-toggle-group" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
588
|
+
}
|
|
589
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxToggleGroup, decorators: [{
|
|
590
|
+
type: Component,
|
|
591
|
+
args: [{
|
|
592
|
+
selector: 'kx-toggle-group',
|
|
593
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
594
|
+
template: '<ng-content />',
|
|
595
|
+
host: {
|
|
596
|
+
class: 'kx-toggle-group',
|
|
597
|
+
'[attr.role]': "type() === 'single' ? 'radiogroup' : 'group'",
|
|
598
|
+
},
|
|
599
|
+
}]
|
|
600
|
+
}], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }] } });
|
|
601
|
+
class KxToggleGroupItem {
|
|
602
|
+
group;
|
|
603
|
+
value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
604
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
605
|
+
id = input(`kx-toggle-group-item-${uid$1()}`, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
606
|
+
constructor(group) {
|
|
607
|
+
this.group = group;
|
|
608
|
+
}
|
|
609
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxToggleGroupItem, deps: [{ token: KxToggleGroup }], target: i0.ɵɵFactoryTarget.Component });
|
|
610
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: KxToggleGroupItem, isStandalone: true, selector: "kx-toggle-group-item", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "kx-toggle-group__item" }, ngImport: i0, template: `
|
|
611
|
+
@if (group.type() === 'single') {
|
|
612
|
+
<input
|
|
613
|
+
type="radio"
|
|
614
|
+
class="kx-toggle-group__radio"
|
|
615
|
+
[id]="id()"
|
|
616
|
+
[name]="group.name()"
|
|
617
|
+
[value]="value()"
|
|
618
|
+
[checked]="group.isOn(value())"
|
|
619
|
+
[disabled]="disabled() || group.disabled()"
|
|
620
|
+
(change)="group.toggle(value())"
|
|
621
|
+
/>
|
|
622
|
+
<label class="kx-toggle kx-toggle--group" [for]="id()"><ng-content /></label>
|
|
623
|
+
} @else {
|
|
624
|
+
<button
|
|
625
|
+
type="button"
|
|
626
|
+
class="kx-toggle kx-toggle--group"
|
|
627
|
+
[attr.aria-pressed]="group.isOn(value())"
|
|
628
|
+
[disabled]="disabled() || group.disabled()"
|
|
629
|
+
(click)="group.toggle(value())"
|
|
630
|
+
>
|
|
631
|
+
<ng-content />
|
|
632
|
+
</button>
|
|
633
|
+
}
|
|
634
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
635
|
+
}
|
|
636
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxToggleGroupItem, decorators: [{
|
|
637
|
+
type: Component,
|
|
638
|
+
args: [{
|
|
639
|
+
selector: 'kx-toggle-group-item',
|
|
640
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
641
|
+
template: `
|
|
642
|
+
@if (group.type() === 'single') {
|
|
643
|
+
<input
|
|
644
|
+
type="radio"
|
|
645
|
+
class="kx-toggle-group__radio"
|
|
646
|
+
[id]="id()"
|
|
647
|
+
[name]="group.name()"
|
|
648
|
+
[value]="value()"
|
|
649
|
+
[checked]="group.isOn(value())"
|
|
650
|
+
[disabled]="disabled() || group.disabled()"
|
|
651
|
+
(change)="group.toggle(value())"
|
|
652
|
+
/>
|
|
653
|
+
<label class="kx-toggle kx-toggle--group" [for]="id()"><ng-content /></label>
|
|
654
|
+
} @else {
|
|
655
|
+
<button
|
|
656
|
+
type="button"
|
|
657
|
+
class="kx-toggle kx-toggle--group"
|
|
658
|
+
[attr.aria-pressed]="group.isOn(value())"
|
|
659
|
+
[disabled]="disabled() || group.disabled()"
|
|
660
|
+
(click)="group.toggle(value())"
|
|
661
|
+
>
|
|
662
|
+
<ng-content />
|
|
663
|
+
</button>
|
|
664
|
+
}
|
|
665
|
+
`,
|
|
666
|
+
host: { class: 'kx-toggle-group__item' },
|
|
667
|
+
}]
|
|
668
|
+
}], ctorParameters: () => [{ type: KxToggleGroup }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
|
|
669
|
+
/* ── segmented control ──────────────────────────────────────────────────── */
|
|
670
|
+
/**
|
|
671
|
+
* <kx-segmented-control [(value)]="range" aria-label="Range">
|
|
672
|
+
* <kx-segment value="7d">7 days</kx-segment>
|
|
673
|
+
* <kx-segment value="30d">30 days</kx-segment>
|
|
674
|
+
* </kx-segmented-control>
|
|
675
|
+
*
|
|
676
|
+
* A single-choice control shown as one connected track — iOS's segmented control, on the web. It is a real
|
|
677
|
+
* `radiogroup` of real radio inputs for the same reason the radio group is: arrow keys, one tab stop and the
|
|
678
|
+
* "exactly one is chosen" semantics are the browser's.
|
|
679
|
+
*
|
|
680
|
+
* Unlike a toggle group, a segment cannot be turned off by clicking it again: a segmented control with
|
|
681
|
+
* nothing selected has no meaning.
|
|
682
|
+
*/
|
|
683
|
+
class KxSegmentedControl {
|
|
684
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
685
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
686
|
+
name = input(`kx-segmented-${uid$1()}`, ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
687
|
+
selected = output();
|
|
688
|
+
select(next) {
|
|
689
|
+
if (this.disabled())
|
|
690
|
+
return;
|
|
691
|
+
this.value.set(next);
|
|
692
|
+
this.selected.emit(next);
|
|
693
|
+
}
|
|
694
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSegmentedControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
695
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxSegmentedControl, isStandalone: true, selector: "kx-segmented-control", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", selected: "selected" }, host: { attributes: { "role": "radiogroup" }, classAttribute: "kx-segmented" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
696
|
+
}
|
|
697
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSegmentedControl, decorators: [{
|
|
698
|
+
type: Component,
|
|
699
|
+
args: [{
|
|
700
|
+
selector: 'kx-segmented-control',
|
|
701
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
702
|
+
template: '<ng-content />',
|
|
703
|
+
host: { class: 'kx-segmented', role: 'radiogroup' },
|
|
704
|
+
}]
|
|
705
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }] } });
|
|
706
|
+
class KxSegment {
|
|
707
|
+
control;
|
|
708
|
+
value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
709
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
710
|
+
id = input(`kx-segment-${uid$1()}`, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
711
|
+
constructor(control) {
|
|
712
|
+
this.control = control;
|
|
713
|
+
}
|
|
714
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSegment, deps: [{ token: KxSegmentedControl }], target: i0.ɵɵFactoryTarget.Component });
|
|
715
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxSegment, isStandalone: true, selector: "kx-segment", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "kx-segmented__item" }, ngImport: i0, template: `
|
|
716
|
+
<input
|
|
717
|
+
type="radio"
|
|
718
|
+
class="kx-segmented__radio"
|
|
719
|
+
[id]="id()"
|
|
720
|
+
[name]="control.name()"
|
|
721
|
+
[value]="value()"
|
|
722
|
+
[checked]="control.value() === value()"
|
|
723
|
+
[disabled]="disabled() || control.disabled()"
|
|
724
|
+
(change)="control.select(value())"
|
|
725
|
+
/>
|
|
726
|
+
<label class="kx-segmented__label" [for]="id()"><ng-content /></label>
|
|
727
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
728
|
+
}
|
|
729
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSegment, decorators: [{
|
|
730
|
+
type: Component,
|
|
731
|
+
args: [{
|
|
732
|
+
selector: 'kx-segment',
|
|
733
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
734
|
+
template: `
|
|
735
|
+
<input
|
|
736
|
+
type="radio"
|
|
737
|
+
class="kx-segmented__radio"
|
|
738
|
+
[id]="id()"
|
|
739
|
+
[name]="control.name()"
|
|
740
|
+
[value]="value()"
|
|
741
|
+
[checked]="control.value() === value()"
|
|
742
|
+
[disabled]="disabled() || control.disabled()"
|
|
743
|
+
(change)="control.select(value())"
|
|
744
|
+
/>
|
|
745
|
+
<label class="kx-segmented__label" [for]="id()"><ng-content /></label>
|
|
746
|
+
`,
|
|
747
|
+
host: { class: 'kx-segmented__item' },
|
|
748
|
+
}]
|
|
749
|
+
}], ctorParameters: () => [{ type: KxSegmentedControl }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* The display primitives: avatar, aspect ratio, keyboard glyphs, skeleton, spinner, tag, quote, metric and the
|
|
753
|
+
* empty-state family.
|
|
754
|
+
*
|
|
755
|
+
* Nothing here holds application state. Each is either a directive on the element HTML already has for the
|
|
756
|
+
* job, or a component that renders that element — the same rule the rest of the package follows.
|
|
757
|
+
*/
|
|
758
|
+
/* ── aspect ratio ───────────────────────────────────────────────────────── */
|
|
759
|
+
/**
|
|
760
|
+
* <kx-aspect-ratio [ratio]="16 / 9"><img src="cover.jpg" alt="" /></kx-aspect-ratio>
|
|
761
|
+
*
|
|
762
|
+
* The CSS `aspect-ratio` property, which every target browser has had for years. There is no padding-top
|
|
763
|
+
* percentage hack here and no ResizeObserver: the platform does this now, and a component that re-implemented
|
|
764
|
+
* it would be slower and would fight the container.
|
|
765
|
+
*/
|
|
766
|
+
class KxAspectRatio {
|
|
767
|
+
ratio = input(1, ...(ngDevMode ? [{ debugName: "ratio" }] : /* istanbul ignore next */ []));
|
|
768
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAspectRatio, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
769
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxAspectRatio, isStandalone: true, selector: "kx-aspect-ratio", inputs: { ratio: { classPropertyName: "ratio", publicName: "ratio", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--kx-aspect-ratio": "ratio()" }, classAttribute: "kx-aspect-ratio" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
770
|
+
}
|
|
771
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAspectRatio, decorators: [{
|
|
772
|
+
type: Component,
|
|
773
|
+
args: [{
|
|
774
|
+
selector: 'kx-aspect-ratio',
|
|
775
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
776
|
+
template: '<ng-content />',
|
|
777
|
+
host: {
|
|
778
|
+
class: 'kx-aspect-ratio',
|
|
779
|
+
// a custom property rather than `aspect-ratio` directly, so the ratio is an input to the stylesheet and
|
|
780
|
+
// the rule that consumes it stays in CSS with the rest of the component's layout
|
|
781
|
+
'[style.--kx-aspect-ratio]': 'ratio()',
|
|
782
|
+
},
|
|
783
|
+
}]
|
|
784
|
+
}], propDecorators: { ratio: [{ type: i0.Input, args: [{ isSignal: true, alias: "ratio", required: false }] }] } });
|
|
785
|
+
/* ── avatar ─────────────────────────────────────────────────────────────── */
|
|
786
|
+
/**
|
|
787
|
+
* <kx-avatar>
|
|
788
|
+
* <img kxAvatarImage src="/ada.jpg" alt="Ada Lovelace" />
|
|
789
|
+
* <kx-avatar-fallback>AL</kx-avatar-fallback>
|
|
790
|
+
* </kx-avatar>
|
|
791
|
+
*
|
|
792
|
+
* The fallback shows until the image reports `load`, and comes back if it reports `error`. That is the whole
|
|
793
|
+
* reason this is a component rather than a styled `<img>`: a broken avatar that leaves a blank circle is the
|
|
794
|
+
* failure users actually hit, and `alt` text alone does not fill the space.
|
|
795
|
+
*
|
|
796
|
+
* The image carries the accessible name. The fallback initials are decorative — announcing "AL" as well would
|
|
797
|
+
* read the person's name twice, so `aria-hidden` is set on it and the caller keeps `alt` meaningful.
|
|
798
|
+
*/
|
|
799
|
+
class KxAvatar {
|
|
800
|
+
/** Set by KxAvatarImage as the image loads or fails; the fallback reads it. */
|
|
801
|
+
imageLoaded = signal(false, ...(ngDevMode ? [{ debugName: "imageLoaded" }] : /* istanbul ignore next */ []));
|
|
802
|
+
/** Set by KxAvatarGroup on the avatars past its `max`. Not an input: the group owns it, not the caller. */
|
|
803
|
+
collapsed = signal(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
|
|
804
|
+
classes = computed(() => (this.collapsed() ? 'kx-avatar kx-avatar--collapsed' : 'kx-avatar'), ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
805
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAvatar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
806
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxAvatar, isStandalone: true, selector: "kx-avatar", host: { properties: { "class": "classes()" } }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
807
|
+
}
|
|
808
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAvatar, decorators: [{
|
|
809
|
+
type: Component,
|
|
810
|
+
args: [{
|
|
811
|
+
selector: 'kx-avatar',
|
|
812
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
813
|
+
template: '<ng-content />',
|
|
814
|
+
host: {
|
|
815
|
+
'[class]': 'classes()',
|
|
816
|
+
},
|
|
817
|
+
}]
|
|
818
|
+
}] });
|
|
819
|
+
/**
|
|
820
|
+
* A directive on a real `<img>`, so `src`, `srcset`, `loading="lazy"`, `alt` and the browser's own decoding
|
|
821
|
+
* are untouched. It only reports load state up to the avatar and hides itself until then.
|
|
822
|
+
*/
|
|
823
|
+
class KxAvatarImage {
|
|
824
|
+
avatar;
|
|
825
|
+
constructor(avatar) {
|
|
826
|
+
this.avatar = avatar;
|
|
827
|
+
}
|
|
828
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAvatarImage, deps: [{ token: KxAvatar }], target: i0.ɵɵFactoryTarget.Directive });
|
|
829
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.23", type: KxAvatarImage, isStandalone: true, selector: "img[kxAvatarImage]", host: { listeners: { "load": "avatar.imageLoaded.set(true)", "error": "avatar.imageLoaded.set(false)" }, properties: { "hidden": "!avatar.imageLoaded()" }, classAttribute: "kx-avatar__image" }, ngImport: i0 });
|
|
830
|
+
}
|
|
831
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAvatarImage, decorators: [{
|
|
832
|
+
type: Directive,
|
|
833
|
+
args: [{
|
|
834
|
+
selector: 'img[kxAvatarImage]',
|
|
835
|
+
host: {
|
|
836
|
+
class: 'kx-avatar__image',
|
|
837
|
+
'[hidden]': '!avatar.imageLoaded()',
|
|
838
|
+
'(load)': 'avatar.imageLoaded.set(true)',
|
|
839
|
+
'(error)': 'avatar.imageLoaded.set(false)',
|
|
840
|
+
},
|
|
841
|
+
}]
|
|
842
|
+
}], ctorParameters: () => [{ type: KxAvatar }] });
|
|
843
|
+
class KxAvatarFallback {
|
|
844
|
+
avatar;
|
|
845
|
+
constructor(avatar) {
|
|
846
|
+
this.avatar = avatar;
|
|
847
|
+
}
|
|
848
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAvatarFallback, deps: [{ token: KxAvatar }], target: i0.ɵɵFactoryTarget.Component });
|
|
849
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxAvatarFallback, isStandalone: true, selector: "kx-avatar-fallback", host: { attributes: { "aria-hidden": "true" }, properties: { "hidden": "avatar.imageLoaded()" }, classAttribute: "kx-avatar__fallback" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
850
|
+
}
|
|
851
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAvatarFallback, decorators: [{
|
|
852
|
+
type: Component,
|
|
853
|
+
args: [{
|
|
854
|
+
selector: 'kx-avatar-fallback',
|
|
855
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
856
|
+
template: '<ng-content />',
|
|
857
|
+
host: {
|
|
858
|
+
class: 'kx-avatar__fallback',
|
|
859
|
+
'aria-hidden': 'true',
|
|
860
|
+
'[hidden]': 'avatar.imageLoaded()',
|
|
861
|
+
},
|
|
862
|
+
}]
|
|
863
|
+
}], ctorParameters: () => [{ type: KxAvatar }] });
|
|
864
|
+
/**
|
|
865
|
+
* <kx-avatar-group [max]="3">
|
|
866
|
+
* <kx-avatar>…</kx-avatar>
|
|
867
|
+
* …
|
|
868
|
+
* </kx-avatar-group>
|
|
869
|
+
*
|
|
870
|
+
* Stacked, overlapping avatars with a "+N" marker for the rest. The group counts its projected avatars with a
|
|
871
|
+
* content query and tells the ones past `max` to collapse; it does not re-wrap or re-render them, because a
|
|
872
|
+
* projected node can only be rendered where it was projected.
|
|
873
|
+
*
|
|
874
|
+
* The overflow marker is announced ("and 4 more") rather than left as the bare glyph, which reads as "plus
|
|
875
|
+
* four" out of context.
|
|
876
|
+
*/
|
|
877
|
+
class KxAvatarGroup {
|
|
878
|
+
/** Visible avatars before the rest fold into the marker. Null (the default) shows every one. */
|
|
879
|
+
max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
880
|
+
avatars = contentChildren(KxAvatar, ...(ngDevMode ? [{ debugName: "avatars" }] : /* istanbul ignore next */ []));
|
|
881
|
+
overflow = computed(() => {
|
|
882
|
+
const max = this.max();
|
|
883
|
+
return max === null ? 0 : Math.max(0, this.avatars().length - max);
|
|
884
|
+
}, ...(ngDevMode ? [{ debugName: "overflow" }] : /* istanbul ignore next */ []));
|
|
885
|
+
constructor() {
|
|
886
|
+
effect(() => {
|
|
887
|
+
const max = this.max();
|
|
888
|
+
this.avatars().forEach((avatar, i) => avatar.collapsed.set(max !== null && i >= max));
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAvatarGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
892
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: KxAvatarGroup, isStandalone: true, selector: "kx-avatar-group", inputs: { max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "kx-avatar-group" }, queries: [{ propertyName: "avatars", predicate: KxAvatar, isSignal: true }], ngImport: i0, template: `
|
|
893
|
+
<ng-content />
|
|
894
|
+
@if (overflow() > 0) {
|
|
895
|
+
<span class="kx-avatar kx-avatar-group__overflow">
|
|
896
|
+
<span class="kx-avatar__fallback" aria-hidden="true">+{{ overflow() }}</span>
|
|
897
|
+
<span class="kx-sr-only">and {{ overflow() }} more</span>
|
|
898
|
+
</span>
|
|
899
|
+
}
|
|
900
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
901
|
+
}
|
|
902
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxAvatarGroup, decorators: [{
|
|
903
|
+
type: Component,
|
|
904
|
+
args: [{
|
|
905
|
+
selector: 'kx-avatar-group',
|
|
906
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
907
|
+
template: `
|
|
908
|
+
<ng-content />
|
|
909
|
+
@if (overflow() > 0) {
|
|
910
|
+
<span class="kx-avatar kx-avatar-group__overflow">
|
|
911
|
+
<span class="kx-avatar__fallback" aria-hidden="true">+{{ overflow() }}</span>
|
|
912
|
+
<span class="kx-sr-only">and {{ overflow() }} more</span>
|
|
913
|
+
</span>
|
|
914
|
+
}
|
|
915
|
+
`,
|
|
916
|
+
host: { class: 'kx-avatar-group' },
|
|
917
|
+
}]
|
|
918
|
+
}], ctorParameters: () => [], propDecorators: { max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], avatars: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => KxAvatar), { isSignal: true }] }] } });
|
|
919
|
+
/* ── keyboard keys ──────────────────────────────────────────────────────── */
|
|
920
|
+
/**
|
|
921
|
+
* <kbd kxKbdGroup><kbd kxKbd>Ctrl</kbd><kbd kxKbd>K</kbd></kbd>
|
|
922
|
+
*
|
|
923
|
+
* Directives on real `<kbd>` elements. `<kbd>` already means "keyboard input" to assistive technology and to
|
|
924
|
+
* every reader of the markup, so there is nothing for a wrapper component to add.
|
|
925
|
+
*/
|
|
926
|
+
class KxKbd {
|
|
927
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxKbd, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
928
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.23", type: KxKbd, isStandalone: true, selector: "kbd[kxKbd]", host: { classAttribute: "kx-kbd" }, ngImport: i0 });
|
|
929
|
+
}
|
|
930
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxKbd, decorators: [{
|
|
931
|
+
type: Directive,
|
|
932
|
+
args: [{ selector: 'kbd[kxKbd]', host: { class: 'kx-kbd' } }]
|
|
933
|
+
}] });
|
|
934
|
+
class KxKbdGroup {
|
|
935
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxKbdGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
936
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.23", type: KxKbdGroup, isStandalone: true, selector: "kbd[kxKbdGroup]", host: { classAttribute: "kx-kbd-group" }, ngImport: i0 });
|
|
937
|
+
}
|
|
938
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxKbdGroup, decorators: [{
|
|
939
|
+
type: Directive,
|
|
940
|
+
args: [{ selector: 'kbd[kxKbdGroup]', host: { class: 'kx-kbd-group' } }]
|
|
941
|
+
}] });
|
|
942
|
+
/* ── skeleton ───────────────────────────────────────────────────────────── */
|
|
943
|
+
/**
|
|
944
|
+
* <kx-skeleton style="block-size: 1rem; inline-size: 60%" />
|
|
945
|
+
*
|
|
946
|
+
* A loading placeholder, sized by the caller because only the caller knows the shape of the content it stands
|
|
947
|
+
* in for. Hidden from assistive technology: the meaningful announcement is the "loading" state on the region
|
|
948
|
+
* that owns it, not a stack of grey rectangles.
|
|
949
|
+
*
|
|
950
|
+
* The shimmer is a CSS animation wrapped in `prefers-reduced-motion`, so it stops for users who ask it to.
|
|
951
|
+
*/
|
|
952
|
+
class KxSkeleton {
|
|
953
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
954
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxSkeleton, isStandalone: true, selector: "kx-skeleton", host: { attributes: { "aria-hidden": "true" }, classAttribute: "kx-skeleton" }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
955
|
+
}
|
|
956
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSkeleton, decorators: [{
|
|
957
|
+
type: Component,
|
|
958
|
+
args: [{
|
|
959
|
+
selector: 'kx-skeleton',
|
|
960
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
961
|
+
template: '',
|
|
962
|
+
host: { class: 'kx-skeleton', 'aria-hidden': 'true' },
|
|
963
|
+
}]
|
|
964
|
+
}] });
|
|
965
|
+
/* ── spinner ────────────────────────────────────────────────────────────── */
|
|
966
|
+
/**
|
|
967
|
+
* <kx-spinner />
|
|
968
|
+
* <kx-spinner size="lg" variant="muted" label="Saving…" />
|
|
969
|
+
*
|
|
970
|
+
* A `role="status"` live region carrying its own label, so a screen reader is told that something is in
|
|
971
|
+
* progress instead of being shown a spinning border it cannot perceive. The ring is a CSS border animation —
|
|
972
|
+
* no icon dependency, and it degrades to a static ring under reduced motion rather than disappearing.
|
|
973
|
+
*/
|
|
974
|
+
class KxSpinner {
|
|
975
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
976
|
+
variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
977
|
+
label = input('Loading…', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
978
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSpinner, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
979
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxSpinner, isStandalone: true, selector: "kx-spinner", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "status" }, properties: { "class": "\"kx-spinner kx-spinner--\" + size() + \" kx-spinner--\" + variant()" } }, ngImport: i0, template: '<span class="kx-sr-only">{{ label() }}</span>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
980
|
+
}
|
|
981
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSpinner, decorators: [{
|
|
982
|
+
type: Component,
|
|
983
|
+
args: [{
|
|
984
|
+
selector: 'kx-spinner',
|
|
985
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
986
|
+
template: '<span class="kx-sr-only">{{ label() }}</span>',
|
|
987
|
+
host: {
|
|
988
|
+
'[class]': '"kx-spinner kx-spinner--" + size() + " kx-spinner--" + variant()',
|
|
989
|
+
role: 'status',
|
|
990
|
+
},
|
|
991
|
+
}]
|
|
992
|
+
}], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
993
|
+
/* ── tag ────────────────────────────────────────────────────────────────── */
|
|
994
|
+
/**
|
|
995
|
+
* <kx-tag>Design</kx-tag>
|
|
996
|
+
* <kx-tag variant="outline" removable (removed)="drop('design')">Design</kx-tag>
|
|
997
|
+
*
|
|
998
|
+
* A container-tinted chip, distinct from Badge (a solid pill). The dismiss button is opt-in and carries an
|
|
999
|
+
* accessible name built from the tag's own text, so a list of them does not announce "Remove, Remove,
|
|
1000
|
+
* Remove" — `removeLabel` overrides it where the text is not the right name.
|
|
1001
|
+
*/
|
|
1002
|
+
class KxTag {
|
|
1003
|
+
variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
1004
|
+
removable = input(false, { ...(ngDevMode ? { debugName: "removable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1005
|
+
removeLabel = input(null, ...(ngDevMode ? [{ debugName: "removeLabel" }] : /* istanbul ignore next */ []));
|
|
1006
|
+
removed = output();
|
|
1007
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxTag, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1008
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: KxTag, isStandalone: true, selector: "kx-tag", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, removable: { classPropertyName: "removable", publicName: "removable", isSignal: true, isRequired: false, transformFunction: null }, removeLabel: { classPropertyName: "removeLabel", publicName: "removeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removed: "removed" }, host: { properties: { "class": "\"kx-tag kx-tag--\" + variant()" } }, ngImport: i0, template: `
|
|
1009
|
+
<span class="kx-tag__text" #text><ng-content /></span>
|
|
1010
|
+
@if (removable()) {
|
|
1011
|
+
<button
|
|
1012
|
+
type="button"
|
|
1013
|
+
class="kx-tag__remove"
|
|
1014
|
+
[attr.aria-label]="removeLabel() ?? 'Remove ' + text.textContent?.trim()"
|
|
1015
|
+
(click)="removed.emit()"
|
|
1016
|
+
>
|
|
1017
|
+
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
1018
|
+
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
|
1019
|
+
</svg>
|
|
1020
|
+
</button>
|
|
1021
|
+
}
|
|
1022
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1023
|
+
}
|
|
1024
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxTag, decorators: [{
|
|
1025
|
+
type: Component,
|
|
1026
|
+
args: [{
|
|
1027
|
+
selector: 'kx-tag',
|
|
1028
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1029
|
+
template: `
|
|
1030
|
+
<span class="kx-tag__text" #text><ng-content /></span>
|
|
1031
|
+
@if (removable()) {
|
|
1032
|
+
<button
|
|
1033
|
+
type="button"
|
|
1034
|
+
class="kx-tag__remove"
|
|
1035
|
+
[attr.aria-label]="removeLabel() ?? 'Remove ' + text.textContent?.trim()"
|
|
1036
|
+
(click)="removed.emit()"
|
|
1037
|
+
>
|
|
1038
|
+
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
1039
|
+
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
|
1040
|
+
</svg>
|
|
1041
|
+
</button>
|
|
1042
|
+
}
|
|
1043
|
+
`,
|
|
1044
|
+
host: { '[class]': '"kx-tag kx-tag--" + variant()' },
|
|
1045
|
+
}]
|
|
1046
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], removable: [{ type: i0.Input, args: [{ isSignal: true, alias: "removable", required: false }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], removed: [{ type: i0.Output, args: ["removed"] }] } });
|
|
1047
|
+
/* ── quote ──────────────────────────────────────────────────────────────── */
|
|
1048
|
+
/**
|
|
1049
|
+
* <kx-quote author="Ada Lovelace" authorTitle="Mathematician">
|
|
1050
|
+
* The Analytical Engine weaves algebraic patterns.
|
|
1051
|
+
* </kx-quote>
|
|
1052
|
+
*
|
|
1053
|
+
* A real `<figure>` / `<blockquote>` / `<figcaption>`, which is the markup a quotation with an attribution
|
|
1054
|
+
* already has in HTML. The curly quotation marks are decorative punctuation and are hidden from assistive
|
|
1055
|
+
* technology — a screen reader announces a blockquote as one already.
|
|
1056
|
+
*/
|
|
1057
|
+
class KxQuote {
|
|
1058
|
+
author = input(null, ...(ngDevMode ? [{ debugName: "author" }] : /* istanbul ignore next */ []));
|
|
1059
|
+
authorTitle = input(null, ...(ngDevMode ? [{ debugName: "authorTitle" }] : /* istanbul ignore next */ []));
|
|
1060
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxQuote, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1061
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: KxQuote, isStandalone: true, selector: "kx-quote", inputs: { author: { classPropertyName: "author", publicName: "author", isSignal: true, isRequired: false, transformFunction: null }, authorTitle: { classPropertyName: "authorTitle", publicName: "authorTitle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
1062
|
+
<figure class="kx-quote">
|
|
1063
|
+
<blockquote class="kx-quote__body">
|
|
1064
|
+
<span aria-hidden="true">“</span><ng-content /><span aria-hidden="true">”</span>
|
|
1065
|
+
</blockquote>
|
|
1066
|
+
@if (author() || authorTitle()) {
|
|
1067
|
+
<figcaption class="kx-quote__caption">
|
|
1068
|
+
<ng-content select="[kxQuoteAvatar]" />
|
|
1069
|
+
<span>
|
|
1070
|
+
@if (author()) {
|
|
1071
|
+
<span class="kx-quote__author">{{ author() }}</span>
|
|
1072
|
+
}
|
|
1073
|
+
@if (authorTitle()) {
|
|
1074
|
+
<span class="kx-quote__author-title">{{ authorTitle() }}</span>
|
|
1075
|
+
}
|
|
1076
|
+
</span>
|
|
1077
|
+
</figcaption>
|
|
1078
|
+
}
|
|
1079
|
+
</figure>
|
|
1080
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1081
|
+
}
|
|
1082
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxQuote, decorators: [{
|
|
1083
|
+
type: Component,
|
|
1084
|
+
args: [{
|
|
1085
|
+
selector: 'kx-quote',
|
|
1086
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1087
|
+
template: `
|
|
1088
|
+
<figure class="kx-quote">
|
|
1089
|
+
<blockquote class="kx-quote__body">
|
|
1090
|
+
<span aria-hidden="true">“</span><ng-content /><span aria-hidden="true">”</span>
|
|
1091
|
+
</blockquote>
|
|
1092
|
+
@if (author() || authorTitle()) {
|
|
1093
|
+
<figcaption class="kx-quote__caption">
|
|
1094
|
+
<ng-content select="[kxQuoteAvatar]" />
|
|
1095
|
+
<span>
|
|
1096
|
+
@if (author()) {
|
|
1097
|
+
<span class="kx-quote__author">{{ author() }}</span>
|
|
1098
|
+
}
|
|
1099
|
+
@if (authorTitle()) {
|
|
1100
|
+
<span class="kx-quote__author-title">{{ authorTitle() }}</span>
|
|
1101
|
+
}
|
|
1102
|
+
</span>
|
|
1103
|
+
</figcaption>
|
|
1104
|
+
}
|
|
1105
|
+
</figure>
|
|
1106
|
+
`,
|
|
1107
|
+
}]
|
|
1108
|
+
}], propDecorators: { author: [{ type: i0.Input, args: [{ isSignal: true, alias: "author", required: false }] }], authorTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "authorTitle", required: false }] }] } });
|
|
1109
|
+
/* ── metric ─────────────────────────────────────────────────────────────── */
|
|
1110
|
+
/**
|
|
1111
|
+
* <kx-metric label="Revenue" value="$48,120" trend="up" change="+12.4%" />
|
|
1112
|
+
*
|
|
1113
|
+
* A stat tile. The trend arrow is decorative — the direction is already in the `change` text, and an arrow
|
|
1114
|
+
* announced as "up" next to "+12.4%" is noise. Colour is not the only carrier of meaning here for the same
|
|
1115
|
+
* reason: the sign is in the text.
|
|
1116
|
+
*/
|
|
1117
|
+
class KxMetric {
|
|
1118
|
+
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1119
|
+
value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1120
|
+
trend = input(null, ...(ngDevMode ? [{ debugName: "trend" }] : /* istanbul ignore next */ []));
|
|
1121
|
+
change = input(null, ...(ngDevMode ? [{ debugName: "change" }] : /* istanbul ignore next */ []));
|
|
1122
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxMetric, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1123
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: KxMetric, isStandalone: true, selector: "kx-metric", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, trend: { classPropertyName: "trend", publicName: "trend", isSignal: true, isRequired: false, transformFunction: null }, change: { classPropertyName: "change", publicName: "change", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "kx-metric" }, ngImport: i0, template: `
|
|
1124
|
+
<div class="kx-metric__head">
|
|
1125
|
+
<p class="kx-metric__label">{{ label() }}</p>
|
|
1126
|
+
<ng-content select="[kxMetricIcon]" />
|
|
1127
|
+
</div>
|
|
1128
|
+
<div class="kx-metric__body">
|
|
1129
|
+
<p class="kx-metric__value">{{ value() }}</p>
|
|
1130
|
+
@if (trend()) {
|
|
1131
|
+
<span class="kx-metric__trend" [attr.data-trend]="trend()">
|
|
1132
|
+
<span aria-hidden="true">{{ trend() === 'up' ? '▲' : trend() === 'down' ? '▼' : '—' }}</span>
|
|
1133
|
+
{{ change() }}
|
|
1134
|
+
</span>
|
|
1135
|
+
}
|
|
1136
|
+
</div>
|
|
1137
|
+
<ng-content select="[kxMetricChart]" />
|
|
1138
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1139
|
+
}
|
|
1140
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxMetric, decorators: [{
|
|
1141
|
+
type: Component,
|
|
1142
|
+
args: [{
|
|
1143
|
+
selector: 'kx-metric',
|
|
1144
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1145
|
+
template: `
|
|
1146
|
+
<div class="kx-metric__head">
|
|
1147
|
+
<p class="kx-metric__label">{{ label() }}</p>
|
|
1148
|
+
<ng-content select="[kxMetricIcon]" />
|
|
1149
|
+
</div>
|
|
1150
|
+
<div class="kx-metric__body">
|
|
1151
|
+
<p class="kx-metric__value">{{ value() }}</p>
|
|
1152
|
+
@if (trend()) {
|
|
1153
|
+
<span class="kx-metric__trend" [attr.data-trend]="trend()">
|
|
1154
|
+
<span aria-hidden="true">{{ trend() === 'up' ? '▲' : trend() === 'down' ? '▼' : '—' }}</span>
|
|
1155
|
+
{{ change() }}
|
|
1156
|
+
</span>
|
|
1157
|
+
}
|
|
1158
|
+
</div>
|
|
1159
|
+
<ng-content select="[kxMetricChart]" />
|
|
1160
|
+
`,
|
|
1161
|
+
host: { class: 'kx-metric' },
|
|
1162
|
+
}]
|
|
1163
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], trend: [{ type: i0.Input, args: [{ isSignal: true, alias: "trend", required: false }] }], change: [{ type: i0.Input, args: [{ isSignal: true, alias: "change", required: false }] }] } });
|
|
1164
|
+
/* ── empty state ────────────────────────────────────────────────────────── */
|
|
1165
|
+
/**
|
|
1166
|
+
* <kx-empty>
|
|
1167
|
+
* <kx-empty-header>
|
|
1168
|
+
* <kx-empty-media variant="icon">…</kx-empty-media>
|
|
1169
|
+
* <kx-empty-title>No projects yet</kx-empty-title>
|
|
1170
|
+
* <kx-empty-description>Create one to get started.</kx-empty-description>
|
|
1171
|
+
* </kx-empty-header>
|
|
1172
|
+
* <kx-empty-content><button kxButton>New project</button></kx-empty-content>
|
|
1173
|
+
* </kx-empty>
|
|
1174
|
+
*
|
|
1175
|
+
* Composed rather than prop-driven, like Card, so it drops into whatever already has a border — a table cell,
|
|
1176
|
+
* a card, a bare section — without forcing its own.
|
|
1177
|
+
*/
|
|
1178
|
+
class KxEmpty {
|
|
1179
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxEmpty, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1180
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxEmpty, isStandalone: true, selector: "kx-empty", host: { classAttribute: "kx-empty" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1181
|
+
}
|
|
1182
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxEmpty, decorators: [{
|
|
1183
|
+
type: Component,
|
|
1184
|
+
args: [{
|
|
1185
|
+
selector: 'kx-empty',
|
|
1186
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1187
|
+
template: '<ng-content />',
|
|
1188
|
+
host: { class: 'kx-empty' },
|
|
1189
|
+
}]
|
|
1190
|
+
}] });
|
|
1191
|
+
class KxEmptyHeader {
|
|
1192
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxEmptyHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1193
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxEmptyHeader, isStandalone: true, selector: "kx-empty-header", host: { classAttribute: "kx-empty__header" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1194
|
+
}
|
|
1195
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxEmptyHeader, decorators: [{
|
|
1196
|
+
type: Component,
|
|
1197
|
+
args: [{
|
|
1198
|
+
selector: 'kx-empty-header',
|
|
1199
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1200
|
+
template: '<ng-content />',
|
|
1201
|
+
host: { class: 'kx-empty__header' },
|
|
1202
|
+
}]
|
|
1203
|
+
}] });
|
|
1204
|
+
class KxEmptyMedia {
|
|
1205
|
+
variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
1206
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxEmptyMedia, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1207
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxEmptyMedia, isStandalone: true, selector: "kx-empty-media", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, properties: { "class": "\"kx-empty__media kx-empty__media--\" + variant()" } }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1208
|
+
}
|
|
1209
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxEmptyMedia, decorators: [{
|
|
1210
|
+
type: Component,
|
|
1211
|
+
args: [{
|
|
1212
|
+
selector: 'kx-empty-media',
|
|
1213
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1214
|
+
template: '<ng-content />',
|
|
1215
|
+
host: { '[class]': '"kx-empty__media kx-empty__media--" + variant()', 'aria-hidden': 'true' },
|
|
1216
|
+
}]
|
|
1217
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }] } });
|
|
1218
|
+
class KxEmptyTitle {
|
|
1219
|
+
level = input(3, ...(ngDevMode ? [{ debugName: "level" }] : /* istanbul ignore next */ []));
|
|
1220
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxEmptyTitle, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1221
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxEmptyTitle, isStandalone: true, selector: "kx-empty-title", inputs: { level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "heading" }, properties: { "attr.aria-level": "level()" }, classAttribute: "kx-empty__title" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1222
|
+
}
|
|
1223
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxEmptyTitle, decorators: [{
|
|
1224
|
+
type: Component,
|
|
1225
|
+
args: [{
|
|
1226
|
+
selector: 'kx-empty-title',
|
|
1227
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1228
|
+
template: '<ng-content />',
|
|
1229
|
+
host: { class: 'kx-empty__title', role: 'heading', '[attr.aria-level]': 'level()' },
|
|
1230
|
+
}]
|
|
1231
|
+
}], propDecorators: { level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: false }] }] } });
|
|
1232
|
+
class KxEmptyDescription {
|
|
1233
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxEmptyDescription, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1234
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxEmptyDescription, isStandalone: true, selector: "kx-empty-description", host: { classAttribute: "kx-empty__description" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1235
|
+
}
|
|
1236
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxEmptyDescription, decorators: [{
|
|
1237
|
+
type: Component,
|
|
1238
|
+
args: [{
|
|
1239
|
+
selector: 'kx-empty-description',
|
|
1240
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1241
|
+
template: '<ng-content />',
|
|
1242
|
+
host: { class: 'kx-empty__description' },
|
|
1243
|
+
}]
|
|
1244
|
+
}] });
|
|
1245
|
+
class KxEmptyContent {
|
|
1246
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxEmptyContent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1247
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxEmptyContent, isStandalone: true, selector: "kx-empty-content", host: { classAttribute: "kx-empty__content" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1248
|
+
}
|
|
1249
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxEmptyContent, decorators: [{
|
|
1250
|
+
type: Component,
|
|
1251
|
+
args: [{
|
|
1252
|
+
selector: 'kx-empty-content',
|
|
1253
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1254
|
+
template: '<ng-content />',
|
|
1255
|
+
host: { class: 'kx-empty__content' },
|
|
1256
|
+
}]
|
|
1257
|
+
}] });
|
|
1258
|
+
|
|
1259
|
+
/**
|
|
1260
|
+
* Per-element ids for the `<input>`/`<label>` pairs below. A module counter rather than a static on each
|
|
1261
|
+
* class: a static field cannot be read by an instance initialiser declared above it, and the id only has to
|
|
1262
|
+
* be unique within the document.
|
|
1263
|
+
*/
|
|
1264
|
+
let seq = 0;
|
|
1265
|
+
const uid = () => ++seq;
|
|
1266
|
+
/**
|
|
1267
|
+
* The form controls beyond checkbox and switch: textarea, native select, radio group, slider, number input,
|
|
1268
|
+
* password input, and the field wrapper that labels and describes any of them.
|
|
1269
|
+
*
|
|
1270
|
+
* The rule this file follows, and the reason it is short: where HTML already has the control, KinetixUI is a
|
|
1271
|
+
* directive on it. A `<input type="range">` already has arrow-key stepping, Home/End, `aria-valuenow`, RTL
|
|
1272
|
+
* awareness and a form value. A `role="slider"` div has none of that, and every re-implementation of it in
|
|
1273
|
+
* this industry has shipped with at least one of those missing. The same argument decides radio (a real
|
|
1274
|
+
* `<input type="radio">` group gives roving focus and arrow-key selection for free) and number input.
|
|
1275
|
+
*
|
|
1276
|
+
* Every control that carries a value implements `ControlValueAccessor`, so `[(ngModel)]`, `formControl` and
|
|
1277
|
+
* `formControlName` all work, including `setDisabledState` from the form model.
|
|
1278
|
+
*/
|
|
1279
|
+
/* ── shared CVA plumbing ────────────────────────────────────────────────── */
|
|
1280
|
+
/**
|
|
1281
|
+
* `@Directive()` with no selector rather than a plain class: `input()`, `model()` and `output()` are only
|
|
1282
|
+
* legal on a class the Angular compiler knows about, and an undecorated base fails the build with NG8110.
|
|
1283
|
+
*/
|
|
1284
|
+
class KxValueBase {
|
|
1285
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1286
|
+
formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : /* istanbul ignore next */ []));
|
|
1287
|
+
isDisabled() {
|
|
1288
|
+
return this.disabled() || this.formDisabled();
|
|
1289
|
+
}
|
|
1290
|
+
onChange = () => { };
|
|
1291
|
+
onTouched = () => { };
|
|
1292
|
+
registerOnChange(fn) {
|
|
1293
|
+
this.onChange = fn;
|
|
1294
|
+
}
|
|
1295
|
+
registerOnTouched(fn) {
|
|
1296
|
+
this.onTouched = fn;
|
|
1297
|
+
}
|
|
1298
|
+
setDisabledState(isDisabled) {
|
|
1299
|
+
this.formDisabled.set(isDisabled);
|
|
1300
|
+
}
|
|
1301
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxValueBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1302
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.23", type: KxValueBase, isStandalone: true, inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
1303
|
+
}
|
|
1304
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxValueBase, decorators: [{
|
|
1305
|
+
type: Directive
|
|
1306
|
+
}], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
1307
|
+
/* ── textarea ───────────────────────────────────────────────────────────── */
|
|
1308
|
+
/**
|
|
1309
|
+
* <textarea kxTextarea rows="4" [(ngModel)]="notes"></textarea>
|
|
1310
|
+
*
|
|
1311
|
+
* A directive on a real `<textarea>`: `rows`, `maxlength`, `required`, the browser's own validation and every
|
|
1312
|
+
* Angular forms directive keep working untouched. Separate from `kxInput` because the two have different
|
|
1313
|
+
* metrics (a minimum height and a resize affordance), matching the React package's Input/Textarea split.
|
|
1314
|
+
*
|
|
1315
|
+
* The error state is read from `aria-invalid`, so the semantics drive the styling instead of a parallel
|
|
1316
|
+
* `invalid` input that could disagree with them.
|
|
1317
|
+
*/
|
|
1318
|
+
class KxTextarea {
|
|
1319
|
+
/** `vertical` (the default) or `none`. Horizontal resize breaks the column it sits in, so it is not offered. */
|
|
1320
|
+
resize = input('vertical', ...(ngDevMode ? [{ debugName: "resize" }] : /* istanbul ignore next */ []));
|
|
1321
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxTextarea, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1322
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.23", type: KxTextarea, isStandalone: true, selector: "textarea[kxTextarea]", inputs: { resize: { classPropertyName: "resize", publicName: "resize", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.resize": "resize() === 'none' ? 'none' : null" }, classAttribute: "kx-textarea" }, ngImport: i0 });
|
|
1323
|
+
}
|
|
1324
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxTextarea, decorators: [{
|
|
1325
|
+
type: Directive,
|
|
1326
|
+
args: [{
|
|
1327
|
+
selector: 'textarea[kxTextarea]',
|
|
1328
|
+
host: {
|
|
1329
|
+
class: 'kx-textarea',
|
|
1330
|
+
'[style.resize]': "resize() === 'none' ? 'none' : null",
|
|
1331
|
+
},
|
|
1332
|
+
}]
|
|
1333
|
+
}], propDecorators: { resize: [{ type: i0.Input, args: [{ isSignal: true, alias: "resize", required: false }] }] } });
|
|
1334
|
+
/* ── native select ──────────────────────────────────────────────────────── */
|
|
1335
|
+
/**
|
|
1336
|
+
* <select kxNativeSelect [(ngModel)]="country">
|
|
1337
|
+
* <option value="pt">Portugal</option>
|
|
1338
|
+
* </select>
|
|
1339
|
+
*
|
|
1340
|
+
* A directive on the browser's own `<select>`, which on a phone opens the platform picker and on a desktop
|
|
1341
|
+
* opens the OS listbox. That is the whole point of it — where a designed listbox is wanted, that is a
|
|
1342
|
+
* different component. Nothing here re-implements the popup, so nothing here can get it wrong.
|
|
1343
|
+
*/
|
|
1344
|
+
class KxNativeSelect {
|
|
1345
|
+
corners = input('default', ...(ngDevMode ? [{ debugName: "corners" }] : /* istanbul ignore next */ []));
|
|
1346
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxNativeSelect, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1347
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.23", type: KxNativeSelect, isStandalone: true, selector: "select[kxNativeSelect]", inputs: { corners: { classPropertyName: "corners", publicName: "corners", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "\"kx-native-select kx-native-select--\" + corners()" } }, ngImport: i0 });
|
|
1348
|
+
}
|
|
1349
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxNativeSelect, decorators: [{
|
|
1350
|
+
type: Directive,
|
|
1351
|
+
args: [{
|
|
1352
|
+
selector: 'select[kxNativeSelect]',
|
|
1353
|
+
host: {
|
|
1354
|
+
'[class]': '"kx-native-select kx-native-select--" + corners()',
|
|
1355
|
+
},
|
|
1356
|
+
}]
|
|
1357
|
+
}], propDecorators: { corners: [{ type: i0.Input, args: [{ isSignal: true, alias: "corners", required: false }] }] } });
|
|
1358
|
+
/* ── radio group ────────────────────────────────────────────────────────── */
|
|
1359
|
+
/**
|
|
1360
|
+
* <kx-radio-group name="plan" [(ngModel)]="plan" aria-label="Plan">
|
|
1361
|
+
* <kx-radio value="free">Free</kx-radio>
|
|
1362
|
+
* <kx-radio value="pro">Pro</kx-radio>
|
|
1363
|
+
* </kx-radio-group>
|
|
1364
|
+
*
|
|
1365
|
+
* The radios are real `<input type="radio">` elements sharing a `name`, so arrow-key selection, roving focus,
|
|
1366
|
+
* the "one tab stop per group" behaviour and the group's own form semantics are the browser's. An ARIA
|
|
1367
|
+
* re-implementation of a radio group has to rebuild all four, and usually rebuilds three.
|
|
1368
|
+
*
|
|
1369
|
+
* The group is the `ControlValueAccessor`; the radios read and write through it.
|
|
1370
|
+
*/
|
|
1371
|
+
class KxRadioGroup extends KxValueBase {
|
|
1372
|
+
/** Shared `name` for the underlying inputs. Defaults to a per-instance name so two groups never merge. */
|
|
1373
|
+
name = input(`kx-radio-${uid()}`, ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
1374
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1375
|
+
orientation = input('vertical', ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
1376
|
+
/** Emits only on user selection — `valueChange` also fires when a form writes the value in. */
|
|
1377
|
+
selected = output();
|
|
1378
|
+
/** Called by a child radio. Public because the radio is a separate component, not because callers should. */
|
|
1379
|
+
select(next) {
|
|
1380
|
+
if (this.isDisabled())
|
|
1381
|
+
return;
|
|
1382
|
+
this.value.set(next);
|
|
1383
|
+
this.onChange(next);
|
|
1384
|
+
this.onTouched();
|
|
1385
|
+
this.selected.emit(next);
|
|
1386
|
+
}
|
|
1387
|
+
groupDisabled() {
|
|
1388
|
+
return this.isDisabled();
|
|
1389
|
+
}
|
|
1390
|
+
/** A radio lost focus — mark the group touched so `ng-touched` and validation messages behave. */
|
|
1391
|
+
touch() {
|
|
1392
|
+
this.onTouched();
|
|
1393
|
+
}
|
|
1394
|
+
writeValue(value) {
|
|
1395
|
+
this.value.set(value ?? null);
|
|
1396
|
+
}
|
|
1397
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxRadioGroup, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1398
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxRadioGroup, isStandalone: true, selector: "kx-radio-group", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", selected: "selected" }, host: { attributes: { "role": "radiogroup" }, properties: { "attr.aria-orientation": "orientation() === 'horizontal' ? 'horizontal' : null" }, classAttribute: "kx-radio-group" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => KxRadioGroup), multi: true }], usesInheritance: true, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1399
|
+
}
|
|
1400
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxRadioGroup, decorators: [{
|
|
1401
|
+
type: Component,
|
|
1402
|
+
args: [{
|
|
1403
|
+
selector: 'kx-radio-group',
|
|
1404
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1405
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => KxRadioGroup), multi: true }],
|
|
1406
|
+
template: '<ng-content />',
|
|
1407
|
+
host: {
|
|
1408
|
+
class: 'kx-radio-group',
|
|
1409
|
+
role: 'radiogroup',
|
|
1410
|
+
'[attr.aria-orientation]': "orientation() === 'horizontal' ? 'horizontal' : null",
|
|
1411
|
+
},
|
|
1412
|
+
}]
|
|
1413
|
+
}], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }] } });
|
|
1414
|
+
/**
|
|
1415
|
+
* <kx-radio value="pro">Pro</kx-radio>
|
|
1416
|
+
*
|
|
1417
|
+
* Renders `<input type="radio">` + `<label>`, associated by id, so the whole row is a click target and the
|
|
1418
|
+
* label is the accessible name without any `aria-label` guessing.
|
|
1419
|
+
*/
|
|
1420
|
+
class KxRadio {
|
|
1421
|
+
group;
|
|
1422
|
+
value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1423
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1424
|
+
id = input(`kx-radio-item-${uid()}`, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
1425
|
+
constructor(group) {
|
|
1426
|
+
this.group = group;
|
|
1427
|
+
}
|
|
1428
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxRadio, deps: [{ token: KxRadioGroup }], target: i0.ɵɵFactoryTarget.Component });
|
|
1429
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxRadio, isStandalone: true, selector: "kx-radio", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "kx-radio" }, ngImport: i0, template: `
|
|
1430
|
+
<input
|
|
1431
|
+
type="radio"
|
|
1432
|
+
class="kx-radio__input"
|
|
1433
|
+
[id]="id()"
|
|
1434
|
+
[name]="group.name()"
|
|
1435
|
+
[value]="value()"
|
|
1436
|
+
[checked]="group.value() === value()"
|
|
1437
|
+
[disabled]="disabled() || group.groupDisabled()"
|
|
1438
|
+
(change)="group.select(value())"
|
|
1439
|
+
(blur)="group.touch()"
|
|
1440
|
+
/>
|
|
1441
|
+
<label class="kx-radio__label" [for]="id()"><ng-content /></label>
|
|
1442
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1443
|
+
}
|
|
1444
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxRadio, decorators: [{
|
|
1445
|
+
type: Component,
|
|
1446
|
+
args: [{
|
|
1447
|
+
selector: 'kx-radio',
|
|
1448
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1449
|
+
template: `
|
|
1450
|
+
<input
|
|
1451
|
+
type="radio"
|
|
1452
|
+
class="kx-radio__input"
|
|
1453
|
+
[id]="id()"
|
|
1454
|
+
[name]="group.name()"
|
|
1455
|
+
[value]="value()"
|
|
1456
|
+
[checked]="group.value() === value()"
|
|
1457
|
+
[disabled]="disabled() || group.groupDisabled()"
|
|
1458
|
+
(change)="group.select(value())"
|
|
1459
|
+
(blur)="group.touch()"
|
|
1460
|
+
/>
|
|
1461
|
+
<label class="kx-radio__label" [for]="id()"><ng-content /></label>
|
|
1462
|
+
`,
|
|
1463
|
+
host: { class: 'kx-radio' },
|
|
1464
|
+
}]
|
|
1465
|
+
}], ctorParameters: () => [{ type: KxRadioGroup }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
|
|
1466
|
+
/* ── slider ─────────────────────────────────────────────────────────────── */
|
|
1467
|
+
/**
|
|
1468
|
+
* <kx-slider [(ngModel)]="volume" [min]="0" [max]="100" [step]="5" aria-label="Volume" />
|
|
1469
|
+
*
|
|
1470
|
+
* A real `<input type="range">`. Arrow keys, Page Up/Down, Home/End, `aria-valuenow`/`valuemin`/`valuemax`,
|
|
1471
|
+
* the RTL direction flip and touch dragging are all the browser's, and all of them are things hand-rolled
|
|
1472
|
+
* sliders routinely lose. The filled track is drawn with a CSS custom property set from the value, so the
|
|
1473
|
+
* visual follows the real control rather than the other way round.
|
|
1474
|
+
*/
|
|
1475
|
+
class KxSlider extends KxValueBase {
|
|
1476
|
+
value = model(0, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1477
|
+
min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
1478
|
+
max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
1479
|
+
step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
1480
|
+
/** Announced instead of the raw number where the number alone means nothing ("Medium", "3 of 7"). */
|
|
1481
|
+
valueText = input(null, ...(ngDevMode ? [{ debugName: "valueText" }] : /* istanbul ignore next */ []));
|
|
1482
|
+
ariaLabel = input(null, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
1483
|
+
ariaLabelledby = input(null, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
1484
|
+
changed = output();
|
|
1485
|
+
fill = computed(() => {
|
|
1486
|
+
const span = this.max() - this.min();
|
|
1487
|
+
return span <= 0 ? 0 : ((this.value() - this.min()) / span) * 100;
|
|
1488
|
+
}, ...(ngDevMode ? [{ debugName: "fill" }] : /* istanbul ignore next */ []));
|
|
1489
|
+
commit(next) {
|
|
1490
|
+
this.value.set(next);
|
|
1491
|
+
this.onChange(next);
|
|
1492
|
+
this.changed.emit(next);
|
|
1493
|
+
}
|
|
1494
|
+
writeValue(value) {
|
|
1495
|
+
this.value.set(Number.isFinite(value) ? value : this.min());
|
|
1496
|
+
}
|
|
1497
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSlider, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1498
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxSlider, isStandalone: true, selector: "kx-slider", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, valueText: { classPropertyName: "valueText", publicName: "valueText", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", changed: "changed" }, host: { properties: { "style.--kx-slider-fill.%": "fill()" }, classAttribute: "kx-slider" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => KxSlider), multi: true }], usesInheritance: true, ngImport: i0, template: `
|
|
1499
|
+
<input
|
|
1500
|
+
type="range"
|
|
1501
|
+
class="kx-slider__input"
|
|
1502
|
+
[min]="min()"
|
|
1503
|
+
[max]="max()"
|
|
1504
|
+
[step]="step()"
|
|
1505
|
+
[value]="value()"
|
|
1506
|
+
[disabled]="isDisabled()"
|
|
1507
|
+
[attr.aria-label]="ariaLabel()"
|
|
1508
|
+
[attr.aria-labelledby]="ariaLabelledby()"
|
|
1509
|
+
[attr.aria-valuetext]="valueText()"
|
|
1510
|
+
(input)="commit($any($event.target).valueAsNumber)"
|
|
1511
|
+
(blur)="onTouched()"
|
|
1512
|
+
/>
|
|
1513
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1514
|
+
}
|
|
1515
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxSlider, decorators: [{
|
|
1516
|
+
type: Component,
|
|
1517
|
+
args: [{
|
|
1518
|
+
selector: 'kx-slider',
|
|
1519
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1520
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => KxSlider), multi: true }],
|
|
1521
|
+
template: `
|
|
1522
|
+
<input
|
|
1523
|
+
type="range"
|
|
1524
|
+
class="kx-slider__input"
|
|
1525
|
+
[min]="min()"
|
|
1526
|
+
[max]="max()"
|
|
1527
|
+
[step]="step()"
|
|
1528
|
+
[value]="value()"
|
|
1529
|
+
[disabled]="isDisabled()"
|
|
1530
|
+
[attr.aria-label]="ariaLabel()"
|
|
1531
|
+
[attr.aria-labelledby]="ariaLabelledby()"
|
|
1532
|
+
[attr.aria-valuetext]="valueText()"
|
|
1533
|
+
(input)="commit($any($event.target).valueAsNumber)"
|
|
1534
|
+
(blur)="onTouched()"
|
|
1535
|
+
/>
|
|
1536
|
+
`,
|
|
1537
|
+
host: {
|
|
1538
|
+
class: 'kx-slider',
|
|
1539
|
+
'[style.--kx-slider-fill.%]': 'fill()',
|
|
1540
|
+
},
|
|
1541
|
+
}]
|
|
1542
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], valueText: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueText", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
|
|
1543
|
+
/* ── number input ───────────────────────────────────────────────────────── */
|
|
1544
|
+
/**
|
|
1545
|
+
* <kx-number-input [(ngModel)]="quantity" [min]="1" [max]="99" aria-label="Quantity" />
|
|
1546
|
+
*
|
|
1547
|
+
* A real `<input type="number">` with explicit step buttons beside it. The input keeps the numeric keypad on
|
|
1548
|
+
* touch, the browser's own arrow-key stepping and `valueAsNumber`; the buttons exist because the native
|
|
1549
|
+
* spinners are tiny, inconsistent between browsers, and in several of them not reachable by keyboard at all.
|
|
1550
|
+
*
|
|
1551
|
+
* The buttons are `aria-hidden` and `tabindex="-1"`: they duplicate keyboard behaviour the input already has,
|
|
1552
|
+
* so exposing them adds two stops to the tab order that do nothing new.
|
|
1553
|
+
*/
|
|
1554
|
+
class KxNumberInput extends KxValueBase {
|
|
1555
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1556
|
+
min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
1557
|
+
max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
1558
|
+
step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
1559
|
+
ariaLabel = input(null, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
1560
|
+
ariaLabelledby = input(null, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
1561
|
+
changed = output();
|
|
1562
|
+
nudge(direction) {
|
|
1563
|
+
this.commit((this.value() ?? 0) + direction * this.step());
|
|
1564
|
+
}
|
|
1565
|
+
commit(next) {
|
|
1566
|
+
// an emptied field is null, not 0 — "no answer" and "zero" are different answers
|
|
1567
|
+
const value = Number.isFinite(next) ? this.clamp(next) : null;
|
|
1568
|
+
this.value.set(value);
|
|
1569
|
+
this.onChange(value);
|
|
1570
|
+
this.changed.emit(value);
|
|
1571
|
+
}
|
|
1572
|
+
clamp(n) {
|
|
1573
|
+
const min = this.min();
|
|
1574
|
+
const max = this.max();
|
|
1575
|
+
if (min !== null && n < min)
|
|
1576
|
+
return min;
|
|
1577
|
+
if (max !== null && n > max)
|
|
1578
|
+
return max;
|
|
1579
|
+
return n;
|
|
1580
|
+
}
|
|
1581
|
+
writeValue(value) {
|
|
1582
|
+
this.value.set(value ?? null);
|
|
1583
|
+
}
|
|
1584
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxNumberInput, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1585
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxNumberInput, isStandalone: true, selector: "kx-number-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", changed: "changed" }, host: { classAttribute: "kx-number-input" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => KxNumberInput), multi: true }], usesInheritance: true, ngImport: i0, template: `
|
|
1586
|
+
<button type="button" class="kx-number-input__step" tabindex="-1" aria-hidden="true" [disabled]="isDisabled()" (click)="nudge(-1)">−</button>
|
|
1587
|
+
<input
|
|
1588
|
+
type="number"
|
|
1589
|
+
class="kx-number-input__input"
|
|
1590
|
+
[min]="min()"
|
|
1591
|
+
[max]="max()"
|
|
1592
|
+
[step]="step()"
|
|
1593
|
+
[value]="value()"
|
|
1594
|
+
[disabled]="isDisabled()"
|
|
1595
|
+
[attr.aria-label]="ariaLabel()"
|
|
1596
|
+
[attr.aria-labelledby]="ariaLabelledby()"
|
|
1597
|
+
(input)="commit($any($event.target).valueAsNumber)"
|
|
1598
|
+
(blur)="onTouched()"
|
|
1599
|
+
/>
|
|
1600
|
+
<button type="button" class="kx-number-input__step" tabindex="-1" aria-hidden="true" [disabled]="isDisabled()" (click)="nudge(1)">+</button>
|
|
1601
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1602
|
+
}
|
|
1603
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxNumberInput, decorators: [{
|
|
1604
|
+
type: Component,
|
|
1605
|
+
args: [{
|
|
1606
|
+
selector: 'kx-number-input',
|
|
1607
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1608
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => KxNumberInput), multi: true }],
|
|
1609
|
+
template: `
|
|
1610
|
+
<button type="button" class="kx-number-input__step" tabindex="-1" aria-hidden="true" [disabled]="isDisabled()" (click)="nudge(-1)">−</button>
|
|
1611
|
+
<input
|
|
1612
|
+
type="number"
|
|
1613
|
+
class="kx-number-input__input"
|
|
1614
|
+
[min]="min()"
|
|
1615
|
+
[max]="max()"
|
|
1616
|
+
[step]="step()"
|
|
1617
|
+
[value]="value()"
|
|
1618
|
+
[disabled]="isDisabled()"
|
|
1619
|
+
[attr.aria-label]="ariaLabel()"
|
|
1620
|
+
[attr.aria-labelledby]="ariaLabelledby()"
|
|
1621
|
+
(input)="commit($any($event.target).valueAsNumber)"
|
|
1622
|
+
(blur)="onTouched()"
|
|
1623
|
+
/>
|
|
1624
|
+
<button type="button" class="kx-number-input__step" tabindex="-1" aria-hidden="true" [disabled]="isDisabled()" (click)="nudge(1)">+</button>
|
|
1625
|
+
`,
|
|
1626
|
+
host: { class: 'kx-number-input' },
|
|
1627
|
+
}]
|
|
1628
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
|
|
1629
|
+
/* ── password input ─────────────────────────────────────────────────────── */
|
|
1630
|
+
/**
|
|
1631
|
+
* <kx-password-input [(ngModel)]="password" aria-label="Password" />
|
|
1632
|
+
*
|
|
1633
|
+
* A real password field with a reveal toggle. The toggle is a real `<button>` carrying `aria-pressed`, so its
|
|
1634
|
+
* state is announced; the field keeps `autocomplete="current-password"` by default so password managers still
|
|
1635
|
+
* recognise it, and `[autocomplete]` switches it to `new-password` on a sign-up form.
|
|
1636
|
+
*/
|
|
1637
|
+
class KxPasswordInput extends KxValueBase {
|
|
1638
|
+
value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1639
|
+
autocomplete = input('current-password', ...(ngDevMode ? [{ debugName: "autocomplete" }] : /* istanbul ignore next */ []));
|
|
1640
|
+
ariaLabel = input(null, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
1641
|
+
ariaLabelledby = input(null, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
1642
|
+
revealed = signal(false, ...(ngDevMode ? [{ debugName: "revealed" }] : /* istanbul ignore next */ []));
|
|
1643
|
+
commit(next) {
|
|
1644
|
+
this.value.set(next);
|
|
1645
|
+
this.onChange(next);
|
|
1646
|
+
}
|
|
1647
|
+
writeValue(value) {
|
|
1648
|
+
this.value.set(value ?? '');
|
|
1649
|
+
}
|
|
1650
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxPasswordInput, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1651
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxPasswordInput, isStandalone: true, selector: "kx-password-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { classAttribute: "kx-password-input" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => KxPasswordInput), multi: true }], usesInheritance: true, ngImport: i0, template: `
|
|
1652
|
+
<input
|
|
1653
|
+
class="kx-password-input__input"
|
|
1654
|
+
[type]="revealed() ? 'text' : 'password'"
|
|
1655
|
+
[value]="value()"
|
|
1656
|
+
[disabled]="isDisabled()"
|
|
1657
|
+
[attr.autocomplete]="autocomplete()"
|
|
1658
|
+
[attr.aria-label]="ariaLabel()"
|
|
1659
|
+
[attr.aria-labelledby]="ariaLabelledby()"
|
|
1660
|
+
(input)="commit($any($event.target).value)"
|
|
1661
|
+
(blur)="onTouched()"
|
|
1662
|
+
/>
|
|
1663
|
+
<button
|
|
1664
|
+
type="button"
|
|
1665
|
+
class="kx-password-input__reveal"
|
|
1666
|
+
[attr.aria-pressed]="revealed()"
|
|
1667
|
+
[attr.aria-label]="revealed() ? 'Hide password' : 'Show password'"
|
|
1668
|
+
[disabled]="isDisabled()"
|
|
1669
|
+
(click)="revealed.set(!revealed())"
|
|
1670
|
+
>
|
|
1671
|
+
{{ revealed() ? 'Hide' : 'Show' }}
|
|
1672
|
+
</button>
|
|
1673
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1674
|
+
}
|
|
1675
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxPasswordInput, decorators: [{
|
|
1676
|
+
type: Component,
|
|
1677
|
+
args: [{
|
|
1678
|
+
selector: 'kx-password-input',
|
|
1679
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1680
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => KxPasswordInput), multi: true }],
|
|
1681
|
+
template: `
|
|
1682
|
+
<input
|
|
1683
|
+
class="kx-password-input__input"
|
|
1684
|
+
[type]="revealed() ? 'text' : 'password'"
|
|
1685
|
+
[value]="value()"
|
|
1686
|
+
[disabled]="isDisabled()"
|
|
1687
|
+
[attr.autocomplete]="autocomplete()"
|
|
1688
|
+
[attr.aria-label]="ariaLabel()"
|
|
1689
|
+
[attr.aria-labelledby]="ariaLabelledby()"
|
|
1690
|
+
(input)="commit($any($event.target).value)"
|
|
1691
|
+
(blur)="onTouched()"
|
|
1692
|
+
/>
|
|
1693
|
+
<button
|
|
1694
|
+
type="button"
|
|
1695
|
+
class="kx-password-input__reveal"
|
|
1696
|
+
[attr.aria-pressed]="revealed()"
|
|
1697
|
+
[attr.aria-label]="revealed() ? 'Hide password' : 'Show password'"
|
|
1698
|
+
[disabled]="isDisabled()"
|
|
1699
|
+
(click)="revealed.set(!revealed())"
|
|
1700
|
+
>
|
|
1701
|
+
{{ revealed() ? 'Hide' : 'Show' }}
|
|
1702
|
+
</button>
|
|
1703
|
+
`,
|
|
1704
|
+
host: { class: 'kx-password-input' },
|
|
1705
|
+
}]
|
|
1706
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }] } });
|
|
1707
|
+
/* ── field ──────────────────────────────────────────────────────────────── */
|
|
1708
|
+
/**
|
|
1709
|
+
* <kx-field>
|
|
1710
|
+
* <label kxFieldLabel for="email">Email</label>
|
|
1711
|
+
* <input kxInput id="email" aria-describedby="email-hint" />
|
|
1712
|
+
* <p kxFieldDescription id="email-hint">We only use this to sign you in.</p>
|
|
1713
|
+
* <kx-field-message variant="error">Enter a valid address.</kx-field-message>
|
|
1714
|
+
* </kx-field>
|
|
1715
|
+
*
|
|
1716
|
+
* Layout and type for a labelled control, and nothing else. It deliberately does not generate ids or wire
|
|
1717
|
+
* `aria-describedby` for you: a wrapper that guesses those has to guess which of several descriptions is the
|
|
1718
|
+
* error, and gets it wrong on the form where it matters. The ids are the caller's, and they are three
|
|
1719
|
+
* characters each.
|
|
1720
|
+
*
|
|
1721
|
+
* An error message is a live region, so a validation message that appears after submit is announced rather
|
|
1722
|
+
* than silently rendered below the field.
|
|
1723
|
+
*/
|
|
1724
|
+
class KxField {
|
|
1725
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxField, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1726
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxField, isStandalone: true, selector: "kx-field", host: { classAttribute: "kx-field" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1727
|
+
}
|
|
1728
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxField, decorators: [{
|
|
1729
|
+
type: Component,
|
|
1730
|
+
args: [{
|
|
1731
|
+
selector: 'kx-field',
|
|
1732
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1733
|
+
template: '<ng-content />',
|
|
1734
|
+
host: { class: 'kx-field' },
|
|
1735
|
+
}]
|
|
1736
|
+
}] });
|
|
1737
|
+
class KxFieldLabel {
|
|
1738
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxFieldLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1739
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.23", type: KxFieldLabel, isStandalone: true, selector: "label[kxFieldLabel]", host: { classAttribute: "kx-field__label kx-label" }, ngImport: i0 });
|
|
1740
|
+
}
|
|
1741
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxFieldLabel, decorators: [{
|
|
1742
|
+
type: Directive,
|
|
1743
|
+
args: [{ selector: 'label[kxFieldLabel]', host: { class: 'kx-field__label kx-label' } }]
|
|
1744
|
+
}] });
|
|
1745
|
+
class KxFieldDescription {
|
|
1746
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxFieldDescription, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1747
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.23", type: KxFieldDescription, isStandalone: true, selector: "[kxFieldDescription]", host: { classAttribute: "kx-field__description" }, ngImport: i0 });
|
|
1748
|
+
}
|
|
1749
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxFieldDescription, decorators: [{
|
|
1750
|
+
type: Directive,
|
|
1751
|
+
args: [{ selector: '[kxFieldDescription]', host: { class: 'kx-field__description' } }]
|
|
1752
|
+
}] });
|
|
1753
|
+
class KxFieldMessage {
|
|
1754
|
+
variant = input('error', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
1755
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxFieldMessage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1756
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: KxFieldMessage, isStandalone: true, selector: "kx-field-message", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "\"kx-field__message kx-field__message--\" + variant()", "attr.role": "variant() === 'error' ? 'alert' : null" } }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1757
|
+
}
|
|
1758
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxFieldMessage, decorators: [{
|
|
1759
|
+
type: Component,
|
|
1760
|
+
args: [{
|
|
1761
|
+
selector: 'kx-field-message',
|
|
1762
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1763
|
+
template: '<ng-content />',
|
|
1764
|
+
host: {
|
|
1765
|
+
'[class]': '"kx-field__message kx-field__message--" + variant()',
|
|
1766
|
+
'[attr.role]': "variant() === 'error' ? 'alert' : null",
|
|
1767
|
+
},
|
|
1768
|
+
}]
|
|
1769
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }] } });
|
|
1770
|
+
|
|
1771
|
+
/**
|
|
1772
|
+
* Tabs — the package's first composite, and where the keyboard pattern is established.
|
|
1773
|
+
*
|
|
1774
|
+
* <kx-tabs [(value)]="tab">
|
|
1775
|
+
* <kx-tab-list aria-label="Account">
|
|
1776
|
+
* <button kxTab value="profile">Profile</button>
|
|
1777
|
+
* <button kxTab value="billing">Billing</button>
|
|
1778
|
+
* </kx-tab-list>
|
|
1779
|
+
* <kx-tab-panel value="profile">…</kx-tab-panel>
|
|
1780
|
+
* <kx-tab-panel value="billing">…</kx-tab-panel>
|
|
1781
|
+
* </kx-tabs>
|
|
1782
|
+
*
|
|
1783
|
+
* Follows the APG tabs pattern: one stop in the page tab order (roving tabindex), arrows move between tabs,
|
|
1784
|
+
* Home/End jump to the ends, and selection follows focus (automatic activation) because the panels here are
|
|
1785
|
+
* cheap to render. Arrow direction is resolved against the computed writing direction, so in an RTL document
|
|
1786
|
+
* ArrowLeft moves to the *next* tab — mirroring what the Compose port already tested for.
|
|
1787
|
+
*
|
|
1788
|
+
* Ids are generated once per tab so `aria-controls`/`aria-labelledby` can point at each other without the
|
|
1789
|
+
* caller having to invent and thread two ids per tab.
|
|
1790
|
+
*/
|
|
1791
|
+
let nextId = 0;
|
|
1792
|
+
class KxTab {
|
|
1793
|
+
tabs = inject(KxTabs);
|
|
1794
|
+
el = inject(ElementRef);
|
|
1795
|
+
value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1796
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
1797
|
+
id = `kx-tab-${nextId++}`;
|
|
1798
|
+
panelId = `${this.id}-panel`;
|
|
1799
|
+
selected() {
|
|
1800
|
+
return this.tabs.value() === this.value();
|
|
1801
|
+
}
|
|
1802
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxTab, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1803
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.23", type: KxTab, isStandalone: true, selector: "button[kxTab]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button", "role": "tab" }, listeners: { "click": "tabs.select(value())", "keydown": "tabs.onKeydown($event)" }, properties: { "id": "id", "attr.aria-selected": "selected()", "attr.aria-controls": "panelId", "attr.tabindex": "selected() ? 0 : -1", "disabled": "disabled()" }, classAttribute: "kx-tab" }, ngImport: i0 });
|
|
1804
|
+
}
|
|
1805
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxTab, decorators: [{
|
|
1806
|
+
type: Directive,
|
|
1807
|
+
args: [{
|
|
1808
|
+
selector: 'button[kxTab]',
|
|
1809
|
+
host: {
|
|
1810
|
+
class: 'kx-tab',
|
|
1811
|
+
type: 'button',
|
|
1812
|
+
role: 'tab',
|
|
1813
|
+
'[id]': 'id',
|
|
1814
|
+
'[attr.aria-selected]': 'selected()',
|
|
1815
|
+
'[attr.aria-controls]': 'panelId',
|
|
1816
|
+
'[attr.tabindex]': 'selected() ? 0 : -1',
|
|
1817
|
+
'[disabled]': 'disabled()',
|
|
1818
|
+
'(click)': 'tabs.select(value())',
|
|
1819
|
+
'(keydown)': 'tabs.onKeydown($event)',
|
|
1820
|
+
},
|
|
1821
|
+
}]
|
|
1822
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
1823
|
+
class KxTabList {
|
|
1824
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxTabList, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1825
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.23", type: KxTabList, isStandalone: true, selector: "kx-tab-list", host: { attributes: { "role": "tablist" }, classAttribute: "kx-tab-list" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1826
|
+
}
|
|
1827
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxTabList, decorators: [{
|
|
1828
|
+
type: Component,
|
|
1829
|
+
args: [{
|
|
1830
|
+
selector: 'kx-tab-list',
|
|
1831
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1832
|
+
template: '<ng-content />',
|
|
1833
|
+
host: { class: 'kx-tab-list', role: 'tablist' },
|
|
1834
|
+
}]
|
|
1835
|
+
}] });
|
|
1836
|
+
/**
|
|
1837
|
+
* The panel is focusable (`tabindex="0"`) because after Tab moves out of the tab list the next stop must be
|
|
1838
|
+
* the panel itself — otherwise a keyboard user lands past the content the tab just revealed. Hidden panels are
|
|
1839
|
+
* removed from the DOM rather than merely hidden, so nothing inside them is reachable or announced.
|
|
1840
|
+
*/
|
|
1841
|
+
class KxTabPanel {
|
|
1842
|
+
tabs = inject(KxTabs);
|
|
1843
|
+
value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1844
|
+
active() {
|
|
1845
|
+
return this.tabs.value() === this.value();
|
|
1846
|
+
}
|
|
1847
|
+
labelledBy() {
|
|
1848
|
+
return this.tabs.tabFor(this.value())?.id ?? null;
|
|
1849
|
+
}
|
|
1850
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxTabPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1851
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: KxTabPanel, isStandalone: true, selector: "kx-tab-panel", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "tabpanel" }, properties: { "attr.tabindex": "active() ? 0 : null", "attr.aria-labelledby": "labelledBy()", "hidden": "!active()" }, classAttribute: "kx-tab-panel" }, ngImport: i0, template: '@if (active()) { <ng-content /> }', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1852
|
+
}
|
|
1853
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxTabPanel, decorators: [{
|
|
1854
|
+
type: Component,
|
|
1855
|
+
args: [{
|
|
1856
|
+
selector: 'kx-tab-panel',
|
|
1857
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1858
|
+
template: '@if (active()) { <ng-content /> }',
|
|
1859
|
+
host: {
|
|
1860
|
+
class: 'kx-tab-panel',
|
|
1861
|
+
role: 'tabpanel',
|
|
1862
|
+
'[attr.tabindex]': 'active() ? 0 : null',
|
|
1863
|
+
'[attr.aria-labelledby]': 'labelledBy()',
|
|
1864
|
+
'[hidden]': '!active()',
|
|
1865
|
+
},
|
|
1866
|
+
}]
|
|
1867
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }] } });
|
|
1868
|
+
class KxTabs {
|
|
1869
|
+
/** Two-way: `[(value)]` for a controlled tab strip, or leave it and let the first tab win. */
|
|
1870
|
+
value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1871
|
+
tabs = contentChildren(KxTab, { ...(ngDevMode ? { debugName: "tabs" } : /* istanbul ignore next */ {}), descendants: true });
|
|
1872
|
+
initialised = signal(false, ...(ngDevMode ? [{ debugName: "initialised" }] : /* istanbul ignore next */ []));
|
|
1873
|
+
tabFor(value) {
|
|
1874
|
+
return this.tabs().find((t) => t.value() === value);
|
|
1875
|
+
}
|
|
1876
|
+
select(value) {
|
|
1877
|
+
this.value.set(value);
|
|
1878
|
+
}
|
|
1879
|
+
/** Falls back to the first enabled tab when nothing is selected, so an uncontrolled strip still works. */
|
|
1880
|
+
constructor() {
|
|
1881
|
+
queueMicrotask(() => {
|
|
1882
|
+
if (this.initialised())
|
|
1883
|
+
return;
|
|
1884
|
+
this.initialised.set(true);
|
|
1885
|
+
if (!this.value()) {
|
|
1886
|
+
const first = this.tabs().find((t) => !t.disabled());
|
|
1887
|
+
if (first)
|
|
1888
|
+
this.value.set(first.value());
|
|
1889
|
+
}
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
onKeydown(event) {
|
|
1893
|
+
const enabled = this.tabs().filter((t) => !t.disabled());
|
|
1894
|
+
if (enabled.length === 0)
|
|
1895
|
+
return;
|
|
1896
|
+
const current = enabled.findIndex((t) => t.value() === this.value());
|
|
1897
|
+
const rtl = this.isRtl();
|
|
1898
|
+
const forward = rtl ? 'ArrowLeft' : 'ArrowRight';
|
|
1899
|
+
const back = rtl ? 'ArrowRight' : 'ArrowLeft';
|
|
1900
|
+
let next = null;
|
|
1901
|
+
if (event.key === forward)
|
|
1902
|
+
next = (current + 1) % enabled.length;
|
|
1903
|
+
else if (event.key === back)
|
|
1904
|
+
next = (current - 1 + enabled.length) % enabled.length;
|
|
1905
|
+
else if (event.key === 'Home')
|
|
1906
|
+
next = 0;
|
|
1907
|
+
else if (event.key === 'End')
|
|
1908
|
+
next = enabled.length - 1;
|
|
1909
|
+
if (next === null)
|
|
1910
|
+
return;
|
|
1911
|
+
event.preventDefault();
|
|
1912
|
+
const target = enabled[next];
|
|
1913
|
+
this.select(target.value());
|
|
1914
|
+
target.el.nativeElement.focus();
|
|
1915
|
+
}
|
|
1916
|
+
el = inject(ElementRef);
|
|
1917
|
+
/**
|
|
1918
|
+
* The document's own direction, read at the moment of the keypress rather than taken as an input — an
|
|
1919
|
+
* application that flips `dir` at runtime keeps working with no extra wiring.
|
|
1920
|
+
*
|
|
1921
|
+
* The nearest `[dir]` ancestor is checked before the computed style. Computed direction is the more correct
|
|
1922
|
+
* answer when direction comes from CSS, but it is also the one a DOM implementation is free not to inherit,
|
|
1923
|
+
* so relying on it alone makes the behaviour depend on the environment rather than on the markup.
|
|
1924
|
+
*/
|
|
1925
|
+
isRtl() {
|
|
1926
|
+
const node = this.el.nativeElement;
|
|
1927
|
+
const explicit = node.closest('[dir]')?.getAttribute('dir')?.toLowerCase();
|
|
1928
|
+
if (explicit === 'rtl' || explicit === 'ltr')
|
|
1929
|
+
return explicit === 'rtl';
|
|
1930
|
+
return node.ownerDocument.defaultView?.getComputedStyle(node).direction === 'rtl';
|
|
1931
|
+
}
|
|
1932
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1933
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.23", type: KxTabs, isStandalone: true, selector: "kx-tabs", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { classAttribute: "kx-tabs" }, queries: [{ propertyName: "tabs", predicate: KxTab, descendants: true, isSignal: true }], ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1934
|
+
}
|
|
1935
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: KxTabs, decorators: [{
|
|
1936
|
+
type: Component,
|
|
1937
|
+
args: [{
|
|
1938
|
+
selector: 'kx-tabs',
|
|
1939
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1940
|
+
template: '<ng-content />',
|
|
1941
|
+
host: { class: 'kx-tabs' },
|
|
1942
|
+
}]
|
|
1943
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => KxTab), { ...{ descendants: true }, isSignal: true }] }] } });
|
|
1944
|
+
|
|
1945
|
+
/**
|
|
1946
|
+
* @kinetixui/angular — public API.
|
|
1947
|
+
*
|
|
1948
|
+
* This file is the package's contract, and `scripts/check-platform-source.mjs` reads it: a component may only
|
|
1949
|
+
* be declared as an Angular implementation in `components.manifest.json` if its symbol is exported here. That
|
|
1950
|
+
* is what stops the manifest, the website and the documentation from advertising an Angular component that
|
|
1951
|
+
* does not exist — the failure mode this package was created to end.
|
|
1952
|
+
*
|
|
1953
|
+
* Every export is a standalone directive or component: import the ones a template uses, nothing more. There is
|
|
1954
|
+
* no NgModule and no barrel module, so nothing is pulled in by association.
|
|
1955
|
+
*/
|
|
1956
|
+
|
|
1957
|
+
/**
|
|
1958
|
+
* Generated bundle index. Do not edit.
|
|
1959
|
+
*/
|
|
1960
|
+
|
|
1961
|
+
export { KxAlert, KxAlertDescription, KxAlertTitle, KxAspectRatio, KxAvatar, KxAvatarFallback, KxAvatarGroup, KxAvatarImage, KxBadge, KxButton, KxCard, KxCardContent, KxCardDescription, KxCardFooter, KxCardHeader, KxCardTitle, KxCheckbox, KxEmpty, KxEmptyContent, KxEmptyDescription, KxEmptyHeader, KxEmptyMedia, KxEmptyTitle, KxField, KxFieldDescription, KxFieldLabel, KxFieldMessage, KxInput, KxKbd, KxKbdGroup, KxLabel, KxMetric, KxNativeSelect, KxNumberInput, KxPasswordInput, KxProgress, KxQuote, KxRadio, KxRadioGroup, KxSegment, KxSegmentedControl, KxSeparator, KxSkeleton, KxSlider, KxSpinner, KxSwitch, KxTab, KxTabList, KxTabPanel, KxTabs, KxTag, KxTextarea, KxToggle, KxToggleGroup, KxToggleGroupItem };
|
|
1962
|
+
//# sourceMappingURL=kinetixui-angular.mjs.map
|