@kraft.stitch/angular 0.0.3 → 0.0.4-beta.1
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 +0 -4
- package/ng-package.json +10 -0
- package/package.json +28 -19
- 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,111 @@
|
|
|
1
|
+
// Side-effect import: registers the `ks-split-button` tag (and its `ks-icon`
|
|
2
|
+
// dependency) — see `button/button.ts` for the full explanation of this
|
|
3
|
+
// pattern (Stencil owns rendering, this class only translates
|
|
4
|
+
// Angular-idiomatic Input/Output into DOM property writes + event listeners).
|
|
5
|
+
import '@kraft.stitch/core/dist/components/ks-split-button.js';
|
|
6
|
+
|
|
7
|
+
import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, effect, inject, input, output } from '@angular/core';
|
|
8
|
+
import type { KsBaseSize, KsVariant } from '@kraft.stitch/core';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mirrors `KsSplitButtonItem` exported by
|
|
12
|
+
* `core/src/components/Buttons/split-button/split-button.tsx`. Duplicated
|
|
13
|
+
* locally — see the note in `button-group/button-group.ts` for why.
|
|
14
|
+
*/
|
|
15
|
+
export interface KsSplitButtonItem {
|
|
16
|
+
key: string;
|
|
17
|
+
label?: string;
|
|
18
|
+
/** Icon class string (e.g. ks-icon-pencil) */
|
|
19
|
+
icon?: string;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
/** Renders the item in the danger colour */
|
|
22
|
+
destructive?: boolean;
|
|
23
|
+
/** Renders a divider line instead of an item */
|
|
24
|
+
separator?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** DOM shape of the real `<ks-split-button>` custom element, typed for the wrapper. */
|
|
28
|
+
type KsSplitButtonElement = HTMLElement & {
|
|
29
|
+
label: string;
|
|
30
|
+
icon: string;
|
|
31
|
+
variant: KsVariant;
|
|
32
|
+
size: KsBaseSize;
|
|
33
|
+
disabled: boolean;
|
|
34
|
+
items: KsSplitButtonItem[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Angular wrapper around the `<ks-split-button>` Stencil custom element.
|
|
39
|
+
*
|
|
40
|
+
* Outputs are prefixed with `ks` (`ksClick`, `ksItemSelected`) instead of
|
|
41
|
+
* reusing the native event names `clicked` / `itemSelected` — see
|
|
42
|
+
* `button/button.ts` for why a same-named output would fire twice.
|
|
43
|
+
* `template: ''` — the wrapped Stencil `render()` has no `<slot>`.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```html
|
|
47
|
+
* <ks-split-button
|
|
48
|
+
* label="Save"
|
|
49
|
+
* [items]="items"
|
|
50
|
+
* (ksClick)="save()"
|
|
51
|
+
* (ksItemSelected)="onSelect($event)"
|
|
52
|
+
* ></ks-split-button>
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
@Component({
|
|
56
|
+
selector: 'ks-split-button',
|
|
57
|
+
standalone: true,
|
|
58
|
+
template: '',
|
|
59
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
60
|
+
})
|
|
61
|
+
export class KsSplitButtonComponent {
|
|
62
|
+
private readonly el: KsSplitButtonElement;
|
|
63
|
+
|
|
64
|
+
/** Main button label */
|
|
65
|
+
readonly label = input('');
|
|
66
|
+
|
|
67
|
+
/** Main button icon class */
|
|
68
|
+
readonly icon = input('');
|
|
69
|
+
|
|
70
|
+
/** Visual style variant */
|
|
71
|
+
readonly variant = input<KsVariant>('brand');
|
|
72
|
+
|
|
73
|
+
/** Size */
|
|
74
|
+
readonly size = input<KsBaseSize>('md');
|
|
75
|
+
|
|
76
|
+
/** Disables both buttons */
|
|
77
|
+
readonly disabled = input(false);
|
|
78
|
+
|
|
79
|
+
/** Menu entries */
|
|
80
|
+
readonly items = input<KsSplitButtonItem[]>([]);
|
|
81
|
+
|
|
82
|
+
/** Emits the `MouseEvent` when the main button is clicked. From the native `clicked` event. */
|
|
83
|
+
readonly ksClick = output<MouseEvent>();
|
|
84
|
+
|
|
85
|
+
/** Emits the selected item's `key`. From the native `itemSelected` event. */
|
|
86
|
+
readonly ksItemSelected = output<string>();
|
|
87
|
+
|
|
88
|
+
constructor(ref: ElementRef<KsSplitButtonElement>) {
|
|
89
|
+
this.el = ref.nativeElement;
|
|
90
|
+
|
|
91
|
+
effect(() => (this.el.label = this.label()));
|
|
92
|
+
effect(() => (this.el.icon = this.icon()));
|
|
93
|
+
effect(() => (this.el.variant = this.variant()));
|
|
94
|
+
effect(() => (this.el.size = this.size()));
|
|
95
|
+
effect(() => (this.el.disabled = this.disabled()));
|
|
96
|
+
effect(() => (this.el.items = this.items()));
|
|
97
|
+
|
|
98
|
+
const onNativeClicked = (event: Event): void => {
|
|
99
|
+
this.ksClick.emit((event as CustomEvent<MouseEvent>).detail);
|
|
100
|
+
};
|
|
101
|
+
const onNativeItemSelected = (event: Event): void => {
|
|
102
|
+
this.ksItemSelected.emit((event as CustomEvent<string>).detail);
|
|
103
|
+
};
|
|
104
|
+
this.el.addEventListener('clicked', onNativeClicked);
|
|
105
|
+
this.el.addEventListener('itemSelected', onNativeItemSelected);
|
|
106
|
+
inject(DestroyRef).onDestroy(() => {
|
|
107
|
+
this.el.removeEventListener('clicked', onNativeClicked);
|
|
108
|
+
this.el.removeEventListener('itemSelected', onNativeItemSelected);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Side-effect import: registers the `ks-datepicker` tag — see
|
|
2
|
+
// `button/button.ts` for the full explanation of this pattern.
|
|
3
|
+
import '@kraft.stitch/core/dist/components/ks-datepicker.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-datepicker>` custom element, typed for the wrapper. */
|
|
9
|
+
type KsDatepickerElement = HTMLElement & {
|
|
10
|
+
label: string;
|
|
11
|
+
placeholder: string;
|
|
12
|
+
minDate: Date | null;
|
|
13
|
+
maxDate: Date | null;
|
|
14
|
+
disabled: boolean;
|
|
15
|
+
selectedDate: Date | null;
|
|
16
|
+
writeValue(val: Date | null): Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Angular wrapper around the `<ks-datepicker>` Stencil custom element.
|
|
21
|
+
*
|
|
22
|
+
* Two-way binding is via `ControlValueAccessor` — use `[(ngModel)]` or
|
|
23
|
+
* `formControlName` and it works through DI (`writeValue` /
|
|
24
|
+
* `registerOnChange`), with no template event-name collision to worry
|
|
25
|
+
* about. See `chips/chips.ts` for the full rationale (same pattern, same
|
|
26
|
+
* reason the native `selectedDateChange` event can't be reused verbatim as
|
|
27
|
+
* an Angular output name).
|
|
28
|
+
*
|
|
29
|
+
* For plain (non-forms) two-way use, bind explicitly instead:
|
|
30
|
+
* `[selectedDate]="d" (ksSelectedDateChange)="d = $event"`.
|
|
31
|
+
*
|
|
32
|
+
* `template: ''` — the projected panel content in the Stencil render is
|
|
33
|
+
* internal (calendar grid projected into the shared `<ks-overlay-field>`
|
|
34
|
+
* default slot); there is no slot exposed on `<ks-datepicker>` itself for
|
|
35
|
+
* a consumer to project into.
|
|
36
|
+
*/
|
|
37
|
+
@Component({
|
|
38
|
+
selector: 'ks-datepicker',
|
|
39
|
+
standalone: true,
|
|
40
|
+
template: '',
|
|
41
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
42
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: KsDatepickerComponent, multi: true }],
|
|
43
|
+
})
|
|
44
|
+
export class KsDatepickerComponent implements ControlValueAccessor {
|
|
45
|
+
private readonly el: KsDatepickerElement;
|
|
46
|
+
private onChange: (value: Date | null) => void = () => {};
|
|
47
|
+
private onTouched: () => void = () => {};
|
|
48
|
+
|
|
49
|
+
/** Field label */
|
|
50
|
+
readonly label = input('');
|
|
51
|
+
|
|
52
|
+
/** Placeholder text */
|
|
53
|
+
readonly placeholder = input('Select date');
|
|
54
|
+
|
|
55
|
+
/** Min selectable date */
|
|
56
|
+
readonly minDate = input<Date | null>(null);
|
|
57
|
+
|
|
58
|
+
/** Max selectable date */
|
|
59
|
+
readonly maxDate = input<Date | null>(null);
|
|
60
|
+
|
|
61
|
+
/** Disabled state */
|
|
62
|
+
readonly disabled = input(false);
|
|
63
|
+
|
|
64
|
+
/** Initial/controlled value for plain (non-forms) usage. Prefer `[(ngModel)]` / `formControlName` when inside a form. */
|
|
65
|
+
readonly selectedDate = input<Date | null>(null);
|
|
66
|
+
|
|
67
|
+
/** Emits the newly-picked date. From the native `selectedDateChange` event. */
|
|
68
|
+
readonly ksSelectedDateChange = output<Date | null>();
|
|
69
|
+
|
|
70
|
+
constructor(ref: ElementRef<KsDatepickerElement>) {
|
|
71
|
+
this.el = ref.nativeElement;
|
|
72
|
+
|
|
73
|
+
effect(() => (this.el.label = this.label()));
|
|
74
|
+
effect(() => (this.el.placeholder = this.placeholder()));
|
|
75
|
+
effect(() => (this.el.minDate = this.minDate()));
|
|
76
|
+
effect(() => (this.el.maxDate = this.maxDate()));
|
|
77
|
+
effect(() => (this.el.disabled = this.disabled()));
|
|
78
|
+
effect(() => void this.el.writeValue(this.selectedDate()));
|
|
79
|
+
|
|
80
|
+
const onNativeSelectedDateChange = (event: Event): void => {
|
|
81
|
+
const detail = (event as CustomEvent<Date | null>).detail;
|
|
82
|
+
this.ksSelectedDateChange.emit(detail);
|
|
83
|
+
this.onChange(detail);
|
|
84
|
+
this.onTouched();
|
|
85
|
+
};
|
|
86
|
+
this.el.addEventListener('selectedDateChange', onNativeSelectedDateChange);
|
|
87
|
+
inject(DestroyRef).onDestroy(() => this.el.removeEventListener('selectedDateChange', onNativeSelectedDateChange));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
writeValue(value: Date | null): void {
|
|
91
|
+
void this.el.writeValue(value ?? null);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
registerOnChange(fn: (value: Date | null) => void): void {
|
|
95
|
+
this.onChange = fn;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
registerOnTouched(fn: () => void): void {
|
|
99
|
+
this.onTouched = fn;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setDisabledState(isDisabled: boolean): void {
|
|
103
|
+
this.el.disabled = isDisabled;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Side-effect import: registers the `ks-daterange` tag — see
|
|
2
|
+
// `button/button.ts` for the full explanation of this pattern.
|
|
3
|
+
import '@kraft.stitch/core/dist/components/ks-daterange.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 `KsDateRange` / `KsPresetRange` exported by
|
|
10
|
+
* `core/src/components/Calendars/daterange/daterange.types.ts`. Duplicated
|
|
11
|
+
* locally — see the note in `button-group/button-group.ts` for why.
|
|
12
|
+
*/
|
|
13
|
+
export interface KsDateRange {
|
|
14
|
+
start: Date | null;
|
|
15
|
+
end: Date | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface KsPresetRange {
|
|
19
|
+
label: string;
|
|
20
|
+
range: () => KsDateRange;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** DOM shape of the real `<ks-daterange>` custom element, typed for the wrapper. */
|
|
24
|
+
type KsDaterangeElement = HTMLElement & {
|
|
25
|
+
label: string;
|
|
26
|
+
placeholder: string;
|
|
27
|
+
minDate: Date | null;
|
|
28
|
+
maxDate: Date | null;
|
|
29
|
+
presets: KsPresetRange[];
|
|
30
|
+
showPresets: boolean;
|
|
31
|
+
disabled: boolean;
|
|
32
|
+
range: KsDateRange;
|
|
33
|
+
writeValue(val: KsDateRange | null): Promise<void>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Angular wrapper around the `<ks-daterange>` Stencil custom element.
|
|
38
|
+
*
|
|
39
|
+
* Two-way binding is via `ControlValueAccessor` — use `[(ngModel)]` or
|
|
40
|
+
* `formControlName`. See `chips/chips.ts` for the full rationale (the
|
|
41
|
+
* native `rangeChange` event can't be reused verbatim as an Angular output
|
|
42
|
+
* name without a double-fire risk).
|
|
43
|
+
*
|
|
44
|
+
* For plain (non-forms) two-way use, bind explicitly instead:
|
|
45
|
+
* `[range]="r" (ksRangeChange)="r = $event"`.
|
|
46
|
+
*
|
|
47
|
+
* `template: ''` — no slot is exposed on `<ks-daterange>` itself.
|
|
48
|
+
*/
|
|
49
|
+
@Component({
|
|
50
|
+
selector: 'ks-daterange',
|
|
51
|
+
standalone: true,
|
|
52
|
+
template: '',
|
|
53
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
54
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: KsDaterangeComponent, multi: true }],
|
|
55
|
+
})
|
|
56
|
+
export class KsDaterangeComponent implements ControlValueAccessor {
|
|
57
|
+
private readonly el: KsDaterangeElement;
|
|
58
|
+
private onChange: (value: KsDateRange) => void = () => {};
|
|
59
|
+
private onTouched: () => void = () => {};
|
|
60
|
+
|
|
61
|
+
readonly label = input('');
|
|
62
|
+
readonly placeholder = input('Select date range…');
|
|
63
|
+
readonly minDate = input<Date | null>(null);
|
|
64
|
+
readonly maxDate = input<Date | null>(null);
|
|
65
|
+
readonly presets = input<KsPresetRange[]>([]);
|
|
66
|
+
readonly showPresets = input(true);
|
|
67
|
+
readonly disabled = input(false);
|
|
68
|
+
|
|
69
|
+
/** Initial/controlled value for plain (non-forms) usage. Prefer `[(ngModel)]` / `formControlName` when inside a form. */
|
|
70
|
+
readonly range = input<KsDateRange>({ start: null, end: null });
|
|
71
|
+
|
|
72
|
+
/** Emits the newly-picked range. From the native `rangeChange` event. */
|
|
73
|
+
readonly ksRangeChange = output<KsDateRange>();
|
|
74
|
+
|
|
75
|
+
constructor(ref: ElementRef<KsDaterangeElement>) {
|
|
76
|
+
this.el = ref.nativeElement;
|
|
77
|
+
|
|
78
|
+
effect(() => (this.el.label = this.label()));
|
|
79
|
+
effect(() => (this.el.placeholder = this.placeholder()));
|
|
80
|
+
effect(() => (this.el.minDate = this.minDate()));
|
|
81
|
+
effect(() => (this.el.maxDate = this.maxDate()));
|
|
82
|
+
effect(() => (this.el.presets = this.presets()));
|
|
83
|
+
effect(() => (this.el.showPresets = this.showPresets()));
|
|
84
|
+
effect(() => (this.el.disabled = this.disabled()));
|
|
85
|
+
effect(() => void this.el.writeValue(this.range()));
|
|
86
|
+
|
|
87
|
+
const onNativeRangeChange = (event: Event): void => {
|
|
88
|
+
const detail = (event as CustomEvent<KsDateRange>).detail;
|
|
89
|
+
this.ksRangeChange.emit(detail);
|
|
90
|
+
this.onChange(detail);
|
|
91
|
+
this.onTouched();
|
|
92
|
+
};
|
|
93
|
+
this.el.addEventListener('rangeChange', onNativeRangeChange);
|
|
94
|
+
inject(DestroyRef).onDestroy(() => this.el.removeEventListener('rangeChange', onNativeRangeChange));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
writeValue(value: KsDateRange | null): void {
|
|
98
|
+
void this.el.writeValue(value ?? { start: null, end: null });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
registerOnChange(fn: (value: KsDateRange) => void): void {
|
|
102
|
+
this.onChange = fn;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
registerOnTouched(fn: () => void): void {
|
|
106
|
+
this.onTouched = fn;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
setDisabledState(isDisabled: boolean): void {
|
|
110
|
+
this.el.disabled = isDisabled;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Side-effect import: registers the `ks-timepicker` tag — see
|
|
2
|
+
// `button/button.ts` for the full explanation of this pattern.
|
|
3
|
+
import '@kraft.stitch/core/dist/components/ks-timepicker.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
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Mirrors `KsTimeValue` exported by
|
|
10
|
+
* `core/src/components/Calendars/timepicker/timepicker.tsx`. Duplicated
|
|
11
|
+
* locally — see the note in `button-group/button-group.ts` for why.
|
|
12
|
+
*/
|
|
13
|
+
export interface KsTimeValue {
|
|
14
|
+
hours: number;
|
|
15
|
+
minutes: number;
|
|
16
|
+
seconds?: number;
|
|
17
|
+
period?: 'AM' | 'PM';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** DOM shape of the real `<ks-timepicker>` custom element, typed for the wrapper. */
|
|
21
|
+
type KsTimepickerElement = HTMLElement & {
|
|
22
|
+
label: string;
|
|
23
|
+
placeholder: string;
|
|
24
|
+
use12Hour: boolean;
|
|
25
|
+
showSeconds: boolean;
|
|
26
|
+
disabled: boolean;
|
|
27
|
+
error: string;
|
|
28
|
+
value: KsTimeValue | null;
|
|
29
|
+
writeValue(val: KsTimeValue | null): Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Angular wrapper around the `<ks-timepicker>` Stencil custom element.
|
|
34
|
+
*
|
|
35
|
+
* Two-way binding is via `ControlValueAccessor` — use `[(ngModel)]` or
|
|
36
|
+
* `formControlName`. See `chips/chips.ts` for the full rationale (the
|
|
37
|
+
* native `valueChange` event can't be reused verbatim as an Angular output
|
|
38
|
+
* name without a double-fire risk).
|
|
39
|
+
*
|
|
40
|
+
* For plain (non-forms) two-way use, bind explicitly instead:
|
|
41
|
+
* `[value]="t" (ksValueChange)="t = $event"`.
|
|
42
|
+
*
|
|
43
|
+
* `template: ''` — no slot is exposed on `<ks-timepicker>` itself.
|
|
44
|
+
*/
|
|
45
|
+
@Component({
|
|
46
|
+
selector: 'ks-timepicker',
|
|
47
|
+
standalone: true,
|
|
48
|
+
template: '',
|
|
49
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
50
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: KsTimepickerComponent, multi: true }],
|
|
51
|
+
})
|
|
52
|
+
export class KsTimepickerComponent implements ControlValueAccessor {
|
|
53
|
+
private readonly el: KsTimepickerElement;
|
|
54
|
+
private onChange: (value: KsTimeValue) => void = () => {};
|
|
55
|
+
private onTouched: () => void = () => {};
|
|
56
|
+
|
|
57
|
+
/** Field label */
|
|
58
|
+
readonly label = input('');
|
|
59
|
+
|
|
60
|
+
/** Placeholder */
|
|
61
|
+
readonly placeholder = input('Select time…');
|
|
62
|
+
|
|
63
|
+
/** Use 12-hour clock (AM/PM) */
|
|
64
|
+
readonly use12Hour = input(false);
|
|
65
|
+
|
|
66
|
+
/** Show seconds */
|
|
67
|
+
readonly showSeconds = input(false);
|
|
68
|
+
|
|
69
|
+
/** Disabled */
|
|
70
|
+
readonly disabled = input(false);
|
|
71
|
+
|
|
72
|
+
/** Error message */
|
|
73
|
+
readonly error = input('');
|
|
74
|
+
|
|
75
|
+
/** Initial/controlled value for plain (non-forms) usage. Prefer `[(ngModel)]` / `formControlName` when inside a form. */
|
|
76
|
+
readonly value = model<KsTimeValue | null>(null);
|
|
77
|
+
|
|
78
|
+
/** Emits the confirmed time. From the native `valueChange` event. */
|
|
79
|
+
readonly ksValueChange = output<KsTimeValue>();
|
|
80
|
+
|
|
81
|
+
constructor(ref: ElementRef<KsTimepickerElement>) {
|
|
82
|
+
this.el = ref.nativeElement;
|
|
83
|
+
|
|
84
|
+
effect(() => (this.el.label = this.label()));
|
|
85
|
+
effect(() => (this.el.placeholder = this.placeholder()));
|
|
86
|
+
effect(() => (this.el.use12Hour = this.use12Hour()));
|
|
87
|
+
effect(() => (this.el.showSeconds = this.showSeconds()));
|
|
88
|
+
effect(() => (this.el.disabled = this.disabled()));
|
|
89
|
+
effect(() => (this.el.error = this.error()));
|
|
90
|
+
effect(() => void this.el.writeValue(this.value()));
|
|
91
|
+
|
|
92
|
+
const onNativeValueChange = (event: Event): void => {
|
|
93
|
+
const detail = (event as CustomEvent<KsTimeValue>).detail;
|
|
94
|
+
this.ksValueChange.emit(detail);
|
|
95
|
+
this.onChange(detail);
|
|
96
|
+
this.onTouched();
|
|
97
|
+
};
|
|
98
|
+
this.el.addEventListener('valueChange', onNativeValueChange);
|
|
99
|
+
inject(DestroyRef).onDestroy(() => this.el.removeEventListener('valueChange', onNativeValueChange));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
writeValue(value: KsTimeValue | null): void {
|
|
103
|
+
void this.el.writeValue(value ?? null);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
registerOnChange(fn: (value: KsTimeValue) => void): void {
|
|
107
|
+
this.onChange = fn;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
registerOnTouched(fn: () => void): void {
|
|
111
|
+
this.onTouched = fn;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setDisabledState(isDisabled: boolean): void {
|
|
115
|
+
this.el.disabled = isDisabled;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Side-effect import: registers the `ks-dropdown` tag — see
|
|
2
|
+
// `button/button.ts` for the full explanation of this pattern.
|
|
3
|
+
import '@kraft.stitch/core/dist/components/ks-dropdown.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 `KsDropdownOption` exported by
|
|
10
|
+
* `core/src/components/Dropdowns/dropdown/dropdown.tsx`. Duplicated
|
|
11
|
+
* locally — see the note in `button-group/button-group.ts` for why.
|
|
12
|
+
*/
|
|
13
|
+
export interface KsDropdownOption {
|
|
14
|
+
value: string | number;
|
|
15
|
+
label: string;
|
|
16
|
+
icon?: string;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type KsDropdownValue = (string | number)[] | string | number;
|
|
21
|
+
|
|
22
|
+
/** DOM shape of the real `<ks-dropdown>` custom element, typed for the wrapper. */
|
|
23
|
+
type KsDropdownElement = HTMLElement & {
|
|
24
|
+
options: KsDropdownOption[];
|
|
25
|
+
placeholder: string;
|
|
26
|
+
multi: boolean;
|
|
27
|
+
searchable: boolean;
|
|
28
|
+
label: string;
|
|
29
|
+
disabled: boolean;
|
|
30
|
+
selectedValues: (string | number)[];
|
|
31
|
+
writeValue(val: (string | number) | (string | number)[] | null): Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Angular wrapper around the `<ks-dropdown>` Stencil custom element.
|
|
36
|
+
*
|
|
37
|
+
* Two-way binding is via `ControlValueAccessor` — use `[(ngModel)]` or
|
|
38
|
+
* `formControlName`. See `chips/chips.ts` for the full rationale (the
|
|
39
|
+
* native `selectedValuesChange` event can't be reused verbatim as an
|
|
40
|
+
* Angular output name without a double-fire risk).
|
|
41
|
+
*
|
|
42
|
+
* For plain (non-forms) two-way use, bind explicitly instead:
|
|
43
|
+
* `[selectedValues]="v" (ksSelectedValuesChange)="v = $event"`.
|
|
44
|
+
*
|
|
45
|
+
* `template: ''` — the option list / search box in the Stencil render are
|
|
46
|
+
* projected into the shared, internal `<ks-overlay-field>` slot; there is
|
|
47
|
+
* no slot exposed on `<ks-dropdown>` itself for a consumer to project into.
|
|
48
|
+
*/
|
|
49
|
+
@Component({
|
|
50
|
+
selector: 'ks-dropdown',
|
|
51
|
+
standalone: true,
|
|
52
|
+
template: '',
|
|
53
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
54
|
+
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: KsDropdownComponent, multi: true }],
|
|
55
|
+
})
|
|
56
|
+
export class KsDropdownComponent implements ControlValueAccessor {
|
|
57
|
+
private readonly el: KsDropdownElement;
|
|
58
|
+
private onChange: (value: KsDropdownValue) => void = () => {};
|
|
59
|
+
private onTouched: () => void = () => {};
|
|
60
|
+
|
|
61
|
+
/** Full list of options */
|
|
62
|
+
readonly options = input<KsDropdownOption[]>([]);
|
|
63
|
+
|
|
64
|
+
/** Placeholder text shown when nothing is selected */
|
|
65
|
+
readonly placeholder = input('Select…');
|
|
66
|
+
|
|
67
|
+
/** Allow selecting multiple options */
|
|
68
|
+
readonly multi = input(false);
|
|
69
|
+
|
|
70
|
+
/** Show a search box inside the dropdown */
|
|
71
|
+
readonly searchable = input(false);
|
|
72
|
+
|
|
73
|
+
/** Field label */
|
|
74
|
+
readonly label = input('');
|
|
75
|
+
|
|
76
|
+
/** Disabled state */
|
|
77
|
+
readonly disabled = input(false);
|
|
78
|
+
|
|
79
|
+
/** Initial/controlled selected value(s) for plain (non-forms) usage. Prefer `[(ngModel)]` / `formControlName` when inside a form. */
|
|
80
|
+
readonly selectedValues = input<(string | number)[]>([]);
|
|
81
|
+
|
|
82
|
+
/** Emits the new selection — an array when `multi`, otherwise a single value. From the native `selectedValuesChange` event. */
|
|
83
|
+
readonly ksSelectedValuesChange = output<KsDropdownValue>();
|
|
84
|
+
|
|
85
|
+
constructor(ref: ElementRef<KsDropdownElement>) {
|
|
86
|
+
this.el = ref.nativeElement;
|
|
87
|
+
|
|
88
|
+
effect(() => (this.el.options = this.options()));
|
|
89
|
+
effect(() => (this.el.placeholder = this.placeholder()));
|
|
90
|
+
effect(() => (this.el.multi = this.multi()));
|
|
91
|
+
effect(() => (this.el.searchable = this.searchable()));
|
|
92
|
+
effect(() => (this.el.label = this.label()));
|
|
93
|
+
effect(() => (this.el.disabled = this.disabled()));
|
|
94
|
+
effect(() => void this.el.writeValue(this.selectedValues()));
|
|
95
|
+
|
|
96
|
+
const onNativeSelectedValuesChange = (event: Event): void => {
|
|
97
|
+
const detail = (event as CustomEvent<KsDropdownValue>).detail;
|
|
98
|
+
this.ksSelectedValuesChange.emit(detail);
|
|
99
|
+
this.onChange(detail);
|
|
100
|
+
this.onTouched();
|
|
101
|
+
};
|
|
102
|
+
this.el.addEventListener('selectedValuesChange', onNativeSelectedValuesChange);
|
|
103
|
+
inject(DestroyRef).onDestroy(() => this.el.removeEventListener('selectedValuesChange', onNativeSelectedValuesChange));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
writeValue(value: KsDropdownValue | null): void {
|
|
107
|
+
void this.el.writeValue(value ?? null);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
registerOnChange(fn: (value: KsDropdownValue) => void): void {
|
|
111
|
+
this.onChange = fn;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
registerOnTouched(fn: () => void): void {
|
|
115
|
+
this.onTouched = fn;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setDisabledState(isDisabled: boolean): void {
|
|
119
|
+
this.el.disabled = isDisabled;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -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
|
+
}
|