@kraft.stitch/angular 0.0.2 → 0.0.4
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/.github/workflows/npm-publish.yml +41 -0
- package/README.md +97 -2
- package/ng-package.json +10 -0
- package/package.json +28 -17
- package/src/lib/Buttons/button/button.ts +152 -0
- package/src/lib/Buttons/button-group/button-group.ts +129 -0
- package/src/lib/Buttons/chips/chips.ts +131 -0
- package/src/lib/Buttons/fab/fab.ts +96 -0
- package/src/lib/Buttons/split-button/split-button.ts +111 -0
- package/src/lib/Calendars/datepicker/datepicker.ts +105 -0
- package/src/lib/Calendars/daterange/daterange.ts +112 -0
- package/src/lib/Calendars/timepicker/timepicker.ts +117 -0
- package/src/lib/Dropdowns/dropdown/dropdown.ts +121 -0
- package/src/lib/Dropdowns/listbox/listbox.ts +116 -0
- package/src/lib/Inputs/checkbox/checkbox.ts +126 -0
- package/src/lib/Inputs/colour-picker/colour-picker.ts +101 -0
- package/src/lib/Inputs/input/input.ts +139 -0
- package/src/lib/Inputs/otp-input/otp-input.ts +113 -0
- package/src/lib/Inputs/radio/radio.ts +115 -0
- package/src/lib/Inputs/rating/rating.ts +104 -0
- package/src/lib/Inputs/rolling-number/rolling-number.ts +136 -0
- package/src/lib/Inputs/slider/slider.ts +121 -0
- package/src/lib/Inputs/switch/switch.ts +100 -0
- package/src/lib/Inputs/textarea/textarea.ts +129 -0
- package/src/lib/Inputs/theme-switch/theme-switch.ts +61 -0
- package/src/lib/Inputs/uploader/uploader.ts +97 -0
- package/src/lib/Layout/avatar/avatar.ts +66 -0
- package/src/lib/Layout/card/card.ts +88 -0
- package/src/lib/Layout/code-view/code-view.ts +77 -0
- package/src/lib/Layout/dialog/dialog.ts +80 -0
- package/src/lib/Layout/not-found/not-found.ts +73 -0
- package/src/lib/Layout/sidebar/sidebar.ts +97 -0
- package/src/lib/Layout/table/table.ts +81 -0
- package/src/lib/Layout/tooltip/tooltip.ts +48 -0
- package/src/lib/Navigation/accordion/accordion.ts +80 -0
- package/src/lib/Navigation/breadcrumb/breadcrumb.ts +58 -0
- package/src/lib/Navigation/carousel/carousel.ts +70 -0
- package/src/lib/Navigation/context-menu/context-menu.ts +79 -0
- package/src/lib/Navigation/nav-bar/nav-bar.ts +72 -0
- package/src/lib/Navigation/pagination/pagination.ts +82 -0
- package/src/lib/Navigation/stepper/stepper.ts +77 -0
- package/src/lib/Navigation/tab/tab.ts +71 -0
- package/src/lib/Navigation/toolbar/toolbar.ts +60 -0
- package/src/lib/Navigation/top-nav-bar/top-nav-bar.ts +71 -0
- package/src/lib/Navigation/tree-view/tree-view.ts +75 -0
- package/src/lib/Notification/badge/badge.ts +55 -0
- package/src/lib/Notification/loader/loader.ts +41 -0
- package/src/lib/Notification/message/message.ts +60 -0
- package/src/lib/Notification/progress/progress.ts +65 -0
- package/src/lib/Notification/spinner/spinner.ts +50 -0
- package/src/lib/Notification/toast/toast.ts +56 -0
- package/src/public-api.ts +68 -0
- package/src/utils/theme-service.ts +11 -0
- package/tsconfig.json +23 -0
- package/fesm2022/kraft.stitch-angular.mjs +0 -2873
- package/fesm2022/kraft.stitch-angular.mjs.map +0 -1
- package/types/kraft.stitch-angular.d.ts +0 -2247
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Side-effect import: registers the `ks-listbox` tag — see
|
|
2
|
+
// `button/button.ts` for the full explanation of this pattern.
|
|
3
|
+
import '@kraft.stitch/core/dist/components/ks-listbox.js';
|
|
4
|
+
|
|
5
|
+
import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, effect, inject, input, output } from '@angular/core';
|
|
6
|
+
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Mirrors `KsListBoxOption` exported by
|
|
10
|
+
* `core/src/components/Dropdowns/listbox/listbox.tsx`. Duplicated locally
|
|
11
|
+
* — see the note in `button-group/button-group.ts` for why.
|
|
12
|
+
*/
|
|
13
|
+
export interface KsListBoxOption {
|
|
14
|
+
value: string | number;
|
|
15
|
+
label: string;
|
|
16
|
+
icon?: string;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
/** Optional group key — used when `groupBy` is enabled */
|
|
19
|
+
group?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type KsListBoxValue = (string | number)[] | string | number;
|
|
23
|
+
|
|
24
|
+
/** DOM shape of the real `<ks-listbox>` custom element, typed for the wrapper. */
|
|
25
|
+
type KsListBoxElement = HTMLElement & {
|
|
26
|
+
options: KsListBoxOption[];
|
|
27
|
+
multi: boolean;
|
|
28
|
+
searchable: boolean;
|
|
29
|
+
disabled: boolean;
|
|
30
|
+
selected: (string | number)[];
|
|
31
|
+
groupBy: boolean;
|
|
32
|
+
writeValue(val: (string | number) | (string | number)[] | null): Promise<void>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Angular wrapper around the `<ks-listbox>` Stencil custom element.
|
|
37
|
+
*
|
|
38
|
+
* Two-way binding is via `ControlValueAccessor` — use `[(ngModel)]` or
|
|
39
|
+
* `formControlName`. See `chips/chips.ts` for the full rationale (the
|
|
40
|
+
* native `selectedChange` event can't be reused verbatim as an Angular
|
|
41
|
+
* output name without a double-fire risk).
|
|
42
|
+
*
|
|
43
|
+
* For plain (non-forms) two-way use, bind explicitly instead:
|
|
44
|
+
* `[selected]="v" (ksSelectedChange)="v = $event"`.
|
|
45
|
+
*
|
|
46
|
+
* `template: ''` — no slot is exposed on `<ks-listbox>` itself.
|
|
47
|
+
*/
|
|
48
|
+
@Component({
|
|
49
|
+
selector: 'ks-listbox',
|
|
50
|
+
standalone: true,
|
|
51
|
+
template: '',
|
|
52
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
53
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: KsListBoxComponent, multi: true }],
|
|
54
|
+
})
|
|
55
|
+
export class KsListBoxComponent implements ControlValueAccessor {
|
|
56
|
+
private readonly el: KsListBoxElement;
|
|
57
|
+
private onChange: (value: KsListBoxValue) => void = () => {};
|
|
58
|
+
private onTouched: () => void = () => {};
|
|
59
|
+
|
|
60
|
+
/** Options list */
|
|
61
|
+
readonly options = input<KsListBoxOption[]>([]);
|
|
62
|
+
|
|
63
|
+
/** Allow multi-select */
|
|
64
|
+
readonly multi = input(false);
|
|
65
|
+
|
|
66
|
+
/** Show search box */
|
|
67
|
+
readonly searchable = input(false);
|
|
68
|
+
|
|
69
|
+
/** Disabled */
|
|
70
|
+
readonly disabled = input(false);
|
|
71
|
+
|
|
72
|
+
/** Enable group headers using the `group` field on each option */
|
|
73
|
+
readonly groupBy = input(false);
|
|
74
|
+
|
|
75
|
+
/** Initial/controlled selected value(s) for plain (non-forms) usage. Prefer `[(ngModel)]` / `formControlName` when inside a form. */
|
|
76
|
+
readonly selected = input<(string | number)[]>([]);
|
|
77
|
+
|
|
78
|
+
/** Emits the new selection — an array when `multi`, otherwise a single value. From the native `selectedChange` event. */
|
|
79
|
+
readonly ksSelectedChange = output<KsListBoxValue>();
|
|
80
|
+
|
|
81
|
+
constructor(ref: ElementRef<KsListBoxElement>) {
|
|
82
|
+
this.el = ref.nativeElement;
|
|
83
|
+
|
|
84
|
+
effect(() => (this.el.options = this.options()));
|
|
85
|
+
effect(() => (this.el.multi = this.multi()));
|
|
86
|
+
effect(() => (this.el.searchable = this.searchable()));
|
|
87
|
+
effect(() => (this.el.disabled = this.disabled()));
|
|
88
|
+
effect(() => (this.el.groupBy = this.groupBy()));
|
|
89
|
+
effect(() => void this.el.writeValue(this.selected()));
|
|
90
|
+
|
|
91
|
+
const onNativeSelectedChange = (event: Event): void => {
|
|
92
|
+
const detail = (event as CustomEvent<KsListBoxValue>).detail;
|
|
93
|
+
this.ksSelectedChange.emit(detail);
|
|
94
|
+
this.onChange(detail);
|
|
95
|
+
this.onTouched();
|
|
96
|
+
};
|
|
97
|
+
this.el.addEventListener('selectedChange', onNativeSelectedChange);
|
|
98
|
+
inject(DestroyRef).onDestroy(() => this.el.removeEventListener('selectedChange', onNativeSelectedChange));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
writeValue(value: KsListBoxValue | null): void {
|
|
102
|
+
void this.el.writeValue(value ?? null);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
registerOnChange(fn: (value: KsListBoxValue) => void): void {
|
|
106
|
+
this.onChange = fn;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
registerOnTouched(fn: () => void): void {
|
|
110
|
+
this.onTouched = fn;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setDisabledState(isDisabled: boolean): void {
|
|
114
|
+
this.el.disabled = isDisabled;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Side-effect import: registers the `ks-checkbox` tag — see
|
|
2
|
+
// `Buttons/button/button.ts` for the full explanation of this pattern.
|
|
3
|
+
import '@kraft.stitch/core/dist/components/ks-checkbox.js';
|
|
4
|
+
|
|
5
|
+
import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, effect, inject, input, output } from '@angular/core';
|
|
6
|
+
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Mirrors `KsCheckboxOption` exported by
|
|
10
|
+
* `core/src/components/Inputs/checkbox/checkbox.tsx`. Duplicated locally
|
|
11
|
+
* — see the note in `Buttons/button-group/button-group.ts` for why.
|
|
12
|
+
*/
|
|
13
|
+
export interface KsCheckboxOption {
|
|
14
|
+
value: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
checked?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** DOM shape of the real `<ks-checkbox>` custom element, typed for the wrapper. */
|
|
21
|
+
type KsCheckboxElement = HTMLElement & {
|
|
22
|
+
label: string;
|
|
23
|
+
options: KsCheckboxOption[];
|
|
24
|
+
disabled: boolean;
|
|
25
|
+
checked: boolean;
|
|
26
|
+
selected: string[];
|
|
27
|
+
writeValue(val: unknown): Promise<void>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Angular wrapper around the `<ks-checkbox>` Stencil custom element.
|
|
32
|
+
*
|
|
33
|
+
* Supports two modes, mirroring the Stencil source:
|
|
34
|
+
* - **Single** mode (no `options`): two-way bind `checked` (boolean).
|
|
35
|
+
* - **Group** mode (`options` set): two-way bind `selected` (string[]).
|
|
36
|
+
*
|
|
37
|
+
* Two-way binding for either mode is via `ControlValueAccessor` — use
|
|
38
|
+
* `[(ngModel)]`/`formControlName` and it works for whichever mode is
|
|
39
|
+
* active (see `Buttons/chips/chips.ts` for the full rationale on why CVA
|
|
40
|
+
* is used instead of reusing a native event name as an Angular output).
|
|
41
|
+
*
|
|
42
|
+
* For plain (non-forms) two-way use, bind explicitly instead:
|
|
43
|
+
* `[checked]="c" (ksCheckedChange)="c = $event"` or
|
|
44
|
+
* `[selected]="s" (ksSelectedChange)="s = $event"`.
|
|
45
|
+
*
|
|
46
|
+
* `template: ''` — no `<slot>` in the wrapped Stencil render.
|
|
47
|
+
*/
|
|
48
|
+
@Component({
|
|
49
|
+
selector: 'ks-checkbox',
|
|
50
|
+
standalone: true,
|
|
51
|
+
template: '',
|
|
52
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
53
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: KsCheckboxComponent, multi: true }],
|
|
54
|
+
})
|
|
55
|
+
export class KsCheckboxComponent implements ControlValueAccessor {
|
|
56
|
+
private readonly el: KsCheckboxElement;
|
|
57
|
+
private onChange: (value: unknown) => void = () => {};
|
|
58
|
+
private onTouched: () => void = () => {};
|
|
59
|
+
|
|
60
|
+
/** Label shown next to the control (single mode) */
|
|
61
|
+
readonly label = input('');
|
|
62
|
+
|
|
63
|
+
/** Optional set of multiple options (multi-select / group mode) */
|
|
64
|
+
readonly options = input<KsCheckboxOption[]>([]);
|
|
65
|
+
|
|
66
|
+
/** Disabled state */
|
|
67
|
+
readonly disabled = input(false);
|
|
68
|
+
|
|
69
|
+
/** Single mode: initial/controlled checked value. Prefer `[(ngModel)]` / `formControlName`. */
|
|
70
|
+
readonly checked = input(false);
|
|
71
|
+
|
|
72
|
+
/** Group mode: initial/controlled selected values. Prefer `[(ngModel)]` / `formControlName`. */
|
|
73
|
+
readonly selected = input<string[]>([]);
|
|
74
|
+
|
|
75
|
+
/** Emits whenever `checked` changes (single mode). From the native `checkedChange` event. */
|
|
76
|
+
readonly ksCheckedChange = output<boolean>();
|
|
77
|
+
|
|
78
|
+
/** Emits whenever `selected` changes (group mode). From the native `selectedChange` event. */
|
|
79
|
+
readonly ksSelectedChange = output<string[]>();
|
|
80
|
+
|
|
81
|
+
constructor(ref: ElementRef<KsCheckboxElement>) {
|
|
82
|
+
this.el = ref.nativeElement;
|
|
83
|
+
|
|
84
|
+
effect(() => (this.el.label = this.label()));
|
|
85
|
+
effect(() => (this.el.options = this.options()));
|
|
86
|
+
effect(() => (this.el.disabled = this.disabled()));
|
|
87
|
+
effect(() => (this.el.checked = this.checked()));
|
|
88
|
+
effect(() => (this.el.selected = this.selected()));
|
|
89
|
+
|
|
90
|
+
const onNativeCheckedChange = (event: Event): void => {
|
|
91
|
+
const detail = (event as CustomEvent<boolean>).detail;
|
|
92
|
+
this.ksCheckedChange.emit(detail);
|
|
93
|
+
this.onChange(detail);
|
|
94
|
+
this.onTouched();
|
|
95
|
+
};
|
|
96
|
+
const onNativeSelectedChange = (event: Event): void => {
|
|
97
|
+
const detail = (event as CustomEvent<string[]>).detail;
|
|
98
|
+
this.ksSelectedChange.emit(detail);
|
|
99
|
+
this.onChange(detail);
|
|
100
|
+
this.onTouched();
|
|
101
|
+
};
|
|
102
|
+
this.el.addEventListener('checkedChange', onNativeCheckedChange);
|
|
103
|
+
this.el.addEventListener('selectedChange', onNativeSelectedChange);
|
|
104
|
+
inject(DestroyRef).onDestroy(() => {
|
|
105
|
+
this.el.removeEventListener('checkedChange', onNativeCheckedChange);
|
|
106
|
+
this.el.removeEventListener('selectedChange', onNativeSelectedChange);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** `ControlValueAccessor` — called by `ngModel`/`formControl` to push a value in (either mode). */
|
|
111
|
+
writeValue(value: unknown): void {
|
|
112
|
+
void this.el.writeValue(value);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
registerOnChange(fn: (value: unknown) => void): void {
|
|
116
|
+
this.onChange = fn;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
registerOnTouched(fn: () => void): void {
|
|
120
|
+
this.onTouched = fn;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
setDisabledState(isDisabled: boolean): void {
|
|
124
|
+
this.el.disabled = isDisabled;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Side-effect import: registers the `ks-colour-picker` tag — see
|
|
2
|
+
// `Buttons/button/button.ts` for the full explanation of this pattern.
|
|
3
|
+
import '@kraft.stitch/core/dist/components/ks-colour-picker.js';
|
|
4
|
+
|
|
5
|
+
import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, effect, inject, input, output } from '@angular/core';
|
|
6
|
+
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
7
|
+
|
|
8
|
+
/** Mirrors the inline union used by `core/src/components/Inputs/colour-picker/colour-picker.tsx`. */
|
|
9
|
+
export type KsColourPickerVariant = 'input' | 'swatch';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_SWATCHES = ['#ef4444', '#f97316', '#eab308', '#22c55e', '#3b82f6', '#8b5cf6', '#ec4899', '#64748b', '#000000', '#ffffff'];
|
|
12
|
+
|
|
13
|
+
/** DOM shape of the real `<ks-colour-picker>` custom element, typed for the wrapper. */
|
|
14
|
+
type KsColourPickerElement = HTMLElement & {
|
|
15
|
+
label: string;
|
|
16
|
+
variant: KsColourPickerVariant;
|
|
17
|
+
swatches: string[];
|
|
18
|
+
disabled: boolean;
|
|
19
|
+
value: string;
|
|
20
|
+
writeValue(val: string): Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Angular wrapper around the `<ks-colour-picker>` Stencil custom element.
|
|
25
|
+
*
|
|
26
|
+
* Two-way binding is via `ControlValueAccessor` — use `[(ngModel)]` or
|
|
27
|
+
* `formControlName`. See `Buttons/chips/chips.ts` for the full rationale
|
|
28
|
+
* (the native `valueChange` event can't be reused verbatim as an Angular
|
|
29
|
+
* output name without a double-fire risk).
|
|
30
|
+
*
|
|
31
|
+
* For plain (non-forms) two-way use, bind explicitly instead:
|
|
32
|
+
* `[value]="hex" (ksValueChange)="hex = $event"`.
|
|
33
|
+
*
|
|
34
|
+
* `template: ''` — the panel is rendered into the internal
|
|
35
|
+
* `<ks-overlay-field>`; there is no slot on `<ks-colour-picker>` itself.
|
|
36
|
+
*/
|
|
37
|
+
@Component({
|
|
38
|
+
selector: 'ks-colour-picker',
|
|
39
|
+
standalone: true,
|
|
40
|
+
template: '',
|
|
41
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
42
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: KsColourPickerComponent, multi: true }],
|
|
43
|
+
})
|
|
44
|
+
export class KsColourPickerComponent implements ControlValueAccessor {
|
|
45
|
+
private readonly el: KsColourPickerElement;
|
|
46
|
+
private onChange: (value: string) => void = () => {};
|
|
47
|
+
private onTouched: () => void = () => {};
|
|
48
|
+
|
|
49
|
+
/** Field label */
|
|
50
|
+
readonly label = input('');
|
|
51
|
+
|
|
52
|
+
/** Picker UI variant */
|
|
53
|
+
readonly variant = input<KsColourPickerVariant>('input');
|
|
54
|
+
|
|
55
|
+
/** Preset swatch colours */
|
|
56
|
+
readonly swatches = input<string[]>(DEFAULT_SWATCHES);
|
|
57
|
+
|
|
58
|
+
/** Disabled */
|
|
59
|
+
readonly disabled = input(false);
|
|
60
|
+
|
|
61
|
+
/** Initial/controlled hex value. Prefer `[(ngModel)]` / `formControlName`. */
|
|
62
|
+
readonly value = input('#3b82f6');
|
|
63
|
+
|
|
64
|
+
/** Emits the newly-picked hex colour. From the native `valueChange` event. */
|
|
65
|
+
readonly ksValueChange = output<string>();
|
|
66
|
+
|
|
67
|
+
constructor(ref: ElementRef<KsColourPickerElement>) {
|
|
68
|
+
this.el = ref.nativeElement;
|
|
69
|
+
|
|
70
|
+
effect(() => (this.el.label = this.label()));
|
|
71
|
+
effect(() => (this.el.variant = this.variant()));
|
|
72
|
+
effect(() => (this.el.swatches = this.swatches()));
|
|
73
|
+
effect(() => (this.el.disabled = this.disabled()));
|
|
74
|
+
effect(() => void this.el.writeValue(this.value()));
|
|
75
|
+
|
|
76
|
+
const onNativeValueChange = (event: Event): void => {
|
|
77
|
+
const detail = (event as CustomEvent<string>).detail;
|
|
78
|
+
this.ksValueChange.emit(detail);
|
|
79
|
+
this.onChange(detail);
|
|
80
|
+
this.onTouched();
|
|
81
|
+
};
|
|
82
|
+
this.el.addEventListener('valueChange', onNativeValueChange);
|
|
83
|
+
inject(DestroyRef).onDestroy(() => this.el.removeEventListener('valueChange', onNativeValueChange));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
writeValue(value: string): void {
|
|
87
|
+
void this.el.writeValue(value ?? '#3b82f6');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
registerOnChange(fn: (value: string) => void): void {
|
|
91
|
+
this.onChange = fn;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
registerOnTouched(fn: () => void): void {
|
|
95
|
+
this.onTouched = fn;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
setDisabledState(isDisabled: boolean): void {
|
|
99
|
+
this.el.disabled = isDisabled;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Side-effect import: registers the `ks-input` tag — see
|
|
2
|
+
// `Buttons/button/button.ts` for the full explanation of this pattern.
|
|
3
|
+
import '@kraft.stitch/core/dist/components/ks-input.js';
|
|
4
|
+
|
|
5
|
+
import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, effect, inject, input, model, output } from '@angular/core';
|
|
6
|
+
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
7
|
+
import type { KsSize } from '@kraft.stitch/core';
|
|
8
|
+
|
|
9
|
+
/** Mirrors `KsInputType` exported by `core/src/components/Inputs/input/input.tsx`. */
|
|
10
|
+
export type KsInputType = 'text' | 'password' | 'email' | 'number' | 'search' | 'tel' | 'url';
|
|
11
|
+
|
|
12
|
+
/** DOM shape of the real `<ks-input>` custom element, typed for the wrapper. */
|
|
13
|
+
type KsInputElement = HTMLElement & {
|
|
14
|
+
type: KsInputType;
|
|
15
|
+
placeholder: string;
|
|
16
|
+
label: string;
|
|
17
|
+
hint: string;
|
|
18
|
+
error: string;
|
|
19
|
+
prefixIcon: string;
|
|
20
|
+
suffixIcon: string;
|
|
21
|
+
size: KsSize;
|
|
22
|
+
value: string;
|
|
23
|
+
disabled: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Angular wrapper around the `<ks-input>` Stencil custom element.
|
|
28
|
+
*
|
|
29
|
+
* Two-way binding is via `ControlValueAccessor` — use `[(ngModel)]` or
|
|
30
|
+
* `formControlName`. `<ks-input>` has no `@Method() writeValue()` of its
|
|
31
|
+
* own (unlike `chips`/`dropdown`/…), so this wrapper's `writeValue` sets
|
|
32
|
+
* `el.value` directly instead of delegating to a Stencil method.
|
|
33
|
+
*
|
|
34
|
+
* The native `ksBlur` event is already prefixed `ks` by the Stencil
|
|
35
|
+
* author, so re-using that exact name here as the Angular output would
|
|
36
|
+
* still hit the double-fire collision described in `Buttons/button.ts`
|
|
37
|
+
* (Ivy's `tNode.outputs` match is purely by literal name, `ks`-prefix or
|
|
38
|
+
* not) — hence `ksBlurred` (distinct spelling) instead.
|
|
39
|
+
*
|
|
40
|
+
* For plain (non-forms) two-way use, bind explicitly instead:
|
|
41
|
+
* `[value]="v" (ksValueChange)="v = $event"`.
|
|
42
|
+
*
|
|
43
|
+
* `template: ''` — no `<slot>` in the wrapped Stencil render.
|
|
44
|
+
*/
|
|
45
|
+
@Component({
|
|
46
|
+
selector: 'ks-input',
|
|
47
|
+
standalone: true,
|
|
48
|
+
template: '',
|
|
49
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
50
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: KsInputComponent, multi: true }],
|
|
51
|
+
})
|
|
52
|
+
export class KsInputComponent implements ControlValueAccessor {
|
|
53
|
+
private readonly el: KsInputElement;
|
|
54
|
+
private onChange: (value: string) => void = () => {};
|
|
55
|
+
private onTouched: () => void = () => {};
|
|
56
|
+
|
|
57
|
+
/** HTML input type */
|
|
58
|
+
readonly type = input<KsInputType>('text');
|
|
59
|
+
|
|
60
|
+
/** Placeholder text */
|
|
61
|
+
readonly placeholder = input('');
|
|
62
|
+
|
|
63
|
+
/** Field label */
|
|
64
|
+
readonly label = input('');
|
|
65
|
+
|
|
66
|
+
/** Helper / hint text shown below the input */
|
|
67
|
+
readonly hint = input('');
|
|
68
|
+
|
|
69
|
+
/** Error message — also triggers the error state */
|
|
70
|
+
readonly error = input('');
|
|
71
|
+
|
|
72
|
+
/** Leading icon class (e.g. Material Symbol name) */
|
|
73
|
+
readonly prefixIcon = input('');
|
|
74
|
+
|
|
75
|
+
/** Trailing icon class */
|
|
76
|
+
readonly suffixIcon = input('');
|
|
77
|
+
|
|
78
|
+
/** Size variant */
|
|
79
|
+
readonly size = input<KsSize>('md');
|
|
80
|
+
|
|
81
|
+
/** Initial/controlled value. Prefer `[(ngModel)]` / `formControlName`. */
|
|
82
|
+
readonly value = model('');
|
|
83
|
+
|
|
84
|
+
/** Disabled state */
|
|
85
|
+
readonly disabled = input(false);
|
|
86
|
+
|
|
87
|
+
/** Emits the newly-typed value. From the native `valueChange` event. */
|
|
88
|
+
readonly ksValueChange = output<string>();
|
|
89
|
+
|
|
90
|
+
/** Emitted on native blur (mirrors Angular's onTouched callback). From the native `ksBlur` event. */
|
|
91
|
+
readonly ksBlurred = output<void>();
|
|
92
|
+
|
|
93
|
+
constructor(ref: ElementRef<KsInputElement>) {
|
|
94
|
+
this.el = ref.nativeElement;
|
|
95
|
+
|
|
96
|
+
effect(() => (this.el.type = this.type()));
|
|
97
|
+
effect(() => (this.el.placeholder = this.placeholder()));
|
|
98
|
+
effect(() => (this.el.label = this.label()));
|
|
99
|
+
effect(() => (this.el.hint = this.hint()));
|
|
100
|
+
effect(() => (this.el.error = this.error()));
|
|
101
|
+
effect(() => (this.el.prefixIcon = this.prefixIcon()));
|
|
102
|
+
effect(() => (this.el.suffixIcon = this.suffixIcon()));
|
|
103
|
+
effect(() => (this.el.size = this.size()));
|
|
104
|
+
effect(() => (this.el.disabled = this.disabled()));
|
|
105
|
+
effect(() => (this.el.value = this.value()));
|
|
106
|
+
|
|
107
|
+
const onNativeValueChange = (event: Event): void => {
|
|
108
|
+
const detail = (event as CustomEvent<string>).detail;
|
|
109
|
+
this.ksValueChange.emit(detail);
|
|
110
|
+
this.onChange(detail);
|
|
111
|
+
};
|
|
112
|
+
const onNativeBlur = (): void => {
|
|
113
|
+
this.ksBlurred.emit();
|
|
114
|
+
this.onTouched();
|
|
115
|
+
};
|
|
116
|
+
this.el.addEventListener('valueChange', onNativeValueChange);
|
|
117
|
+
this.el.addEventListener('ksBlur', onNativeBlur);
|
|
118
|
+
inject(DestroyRef).onDestroy(() => {
|
|
119
|
+
this.el.removeEventListener('valueChange', onNativeValueChange);
|
|
120
|
+
this.el.removeEventListener('ksBlur', onNativeBlur);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
writeValue(value: string): void {
|
|
125
|
+
this.el.value = value ?? '';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
registerOnChange(fn: (value: string) => void): void {
|
|
129
|
+
this.onChange = fn;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
registerOnTouched(fn: () => void): void {
|
|
133
|
+
this.onTouched = fn;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setDisabledState(isDisabled: boolean): void {
|
|
137
|
+
this.el.disabled = isDisabled;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Side-effect import: registers the `ks-otp-input` tag — see
|
|
2
|
+
// `Buttons/button/button.ts` for the full explanation of this pattern.
|
|
3
|
+
import '@kraft.stitch/core/dist/components/ks-otp-input.js';
|
|
4
|
+
|
|
5
|
+
import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, effect, inject, input, output } from '@angular/core';
|
|
6
|
+
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
7
|
+
|
|
8
|
+
/** DOM shape of the real `<ks-otp-input>` custom element, typed for the wrapper. */
|
|
9
|
+
type KsOtpInputElement = HTMLElement & {
|
|
10
|
+
length: number;
|
|
11
|
+
inputType: 'number' | 'text';
|
|
12
|
+
label: string;
|
|
13
|
+
error: string;
|
|
14
|
+
masked: boolean;
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
value: string;
|
|
17
|
+
writeValue(val: string): Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Angular wrapper around the `<ks-otp-input>` Stencil custom element.
|
|
22
|
+
*
|
|
23
|
+
* Two-way binding is via `ControlValueAccessor` — use `[(ngModel)]` or
|
|
24
|
+
* `formControlName`. See `Buttons/chips/chips.ts` for the full rationale.
|
|
25
|
+
*
|
|
26
|
+
* For plain (non-forms) two-way use, bind explicitly instead:
|
|
27
|
+
* `[value]="v" (ksValueChange)="v = $event"`.
|
|
28
|
+
*
|
|
29
|
+
* `template: ''` — no `<slot>` in the wrapped Stencil render.
|
|
30
|
+
*/
|
|
31
|
+
@Component({
|
|
32
|
+
selector: 'ks-otp-input',
|
|
33
|
+
standalone: true,
|
|
34
|
+
template: '',
|
|
35
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
36
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: KsOtpInputComponent, multi: true }],
|
|
37
|
+
})
|
|
38
|
+
export class KsOtpInputComponent implements ControlValueAccessor {
|
|
39
|
+
private readonly el: KsOtpInputElement;
|
|
40
|
+
private onChange: (value: string) => void = () => {};
|
|
41
|
+
private onTouched: () => void = () => {};
|
|
42
|
+
|
|
43
|
+
/** Number of digits */
|
|
44
|
+
readonly length = input(6);
|
|
45
|
+
|
|
46
|
+
/** Input type: 'number' | 'text' */
|
|
47
|
+
readonly inputType = input<'number' | 'text'>('number');
|
|
48
|
+
|
|
49
|
+
/** Field label */
|
|
50
|
+
readonly label = input('');
|
|
51
|
+
|
|
52
|
+
/** Error message */
|
|
53
|
+
readonly error = input('');
|
|
54
|
+
|
|
55
|
+
/** Mask the input (like a password) */
|
|
56
|
+
readonly masked = input(false);
|
|
57
|
+
|
|
58
|
+
/** Disabled */
|
|
59
|
+
readonly disabled = input(false);
|
|
60
|
+
|
|
61
|
+
/** Initial/controlled value. Prefer `[(ngModel)]` / `formControlName`. */
|
|
62
|
+
readonly value = input('');
|
|
63
|
+
|
|
64
|
+
/** Emits whenever `value` changes. From the native `valueChange` event. */
|
|
65
|
+
readonly ksValueChange = output<string>();
|
|
66
|
+
|
|
67
|
+
/** Emits the full OTP string when all digits are filled. From the native `completed` event. */
|
|
68
|
+
readonly ksCompleted = output<string>();
|
|
69
|
+
|
|
70
|
+
constructor(ref: ElementRef<KsOtpInputElement>) {
|
|
71
|
+
this.el = ref.nativeElement;
|
|
72
|
+
|
|
73
|
+
effect(() => (this.el.length = this.length()));
|
|
74
|
+
effect(() => (this.el.inputType = this.inputType()));
|
|
75
|
+
effect(() => (this.el.label = this.label()));
|
|
76
|
+
effect(() => (this.el.error = this.error()));
|
|
77
|
+
effect(() => (this.el.masked = this.masked()));
|
|
78
|
+
effect(() => (this.el.disabled = this.disabled()));
|
|
79
|
+
effect(() => void this.el.writeValue(this.value()));
|
|
80
|
+
|
|
81
|
+
const onNativeValueChange = (event: Event): void => {
|
|
82
|
+
const detail = (event as CustomEvent<string>).detail;
|
|
83
|
+
this.ksValueChange.emit(detail);
|
|
84
|
+
this.onChange(detail);
|
|
85
|
+
this.onTouched();
|
|
86
|
+
};
|
|
87
|
+
const onNativeCompleted = (event: Event): void => {
|
|
88
|
+
this.ksCompleted.emit((event as CustomEvent<string>).detail);
|
|
89
|
+
};
|
|
90
|
+
this.el.addEventListener('valueChange', onNativeValueChange);
|
|
91
|
+
this.el.addEventListener('completed', onNativeCompleted);
|
|
92
|
+
inject(DestroyRef).onDestroy(() => {
|
|
93
|
+
this.el.removeEventListener('valueChange', onNativeValueChange);
|
|
94
|
+
this.el.removeEventListener('completed', onNativeCompleted);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
writeValue(value: string): void {
|
|
99
|
+
void this.el.writeValue(value ?? '');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
registerOnChange(fn: (value: string) => void): void {
|
|
103
|
+
this.onChange = fn;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
registerOnTouched(fn: () => void): void {
|
|
107
|
+
this.onTouched = fn;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
setDisabledState(isDisabled: boolean): void {
|
|
111
|
+
this.el.disabled = isDisabled;
|
|
112
|
+
}
|
|
113
|
+
}
|