@egose/shadcn-theme-ng 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -1
- package/autocomplete/fesm2022/autocomplete.mjs +14 -4
- package/autocomplete/types/autocomplete.d.ts +4 -1
- package/checkbox/README.md +6 -2
- package/checkbox/fesm2022/checkbox.mjs +11 -7
- package/checkbox/types/checkbox.d.ts +6 -4
- package/combobox/fesm2022/combobox.mjs +13 -7
- package/combobox/types/combobox.d.ts +8 -2
- package/data-table/README.md +11 -1
- package/data-table/fesm2022/data-table.mjs +64 -18
- package/data-table/types/data-table.d.ts +25 -6
- package/date-picker/README.md +41 -9
- package/date-picker/fesm2022/date-picker.mjs +265 -59
- package/date-picker/types/date-picker.d.ts +58 -27
- package/form-autocomplete/README.md +4 -2
- package/form-autocomplete/fesm2022/form-autocomplete.mjs +19 -6
- package/form-autocomplete/types/form-autocomplete.d.ts +9 -3
- package/form-combobox/README.md +4 -2
- package/form-combobox/fesm2022/form-combobox.mjs +23 -6
- package/form-combobox/types/form-combobox.d.ts +10 -3
- package/form-input-otp/README.md +7 -5
- package/form-input-otp/fesm2022/form-input-otp.mjs +20 -8
- package/form-input-otp/types/form-input-otp.d.ts +9 -3
- package/form-searchable-multiselect/README.md +15 -1
- package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +15 -3
- package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +9 -1
- package/form-slider/README.md +3 -1
- package/form-slider/fesm2022/form-slider.mjs +19 -6
- package/form-slider/types/form-slider.d.ts +9 -3
- package/form-toggle/README.md +22 -18
- package/form-toggle/fesm2022/form-toggle.mjs +22 -9
- package/form-toggle/types/form-toggle.d.ts +6 -2
- package/input-otp/README.md +33 -9
- package/input-otp/fesm2022/input-otp.mjs +127 -11
- package/input-otp/types/input-otp.d.ts +24 -4
- package/package.json +1 -1
- package/pagination/README.md +21 -12
- package/pagination/fesm2022/pagination.mjs +73 -65
- package/pagination/types/pagination.d.ts +21 -8
- package/phone-input/README.md +16 -0
- package/phone-input/fesm2022/phone-input.mjs +41 -3
- package/phone-input/types/phone-input.d.ts +5 -2
- package/searchable-multiselect/README.md +62 -40
- package/searchable-multiselect/fesm2022/searchable-multiselect.mjs +59 -32
- package/searchable-multiselect/types/searchable-multiselect.d.ts +23 -6
- package/slider/fesm2022/slider.mjs +9 -5
- package/slider/types/slider.d.ts +6 -2
- package/stepper/README.md +39 -19
- package/stepper/fesm2022/stepper.mjs +64 -12
- package/stepper/types/stepper.d.ts +12 -1
package/form-input-otp/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Form Input OTP (`@egose/shadcn-theme-ng/form-input-otp`)
|
|
2
2
|
|
|
3
|
-
A ready-made reactive-form one-time-code field: label + `
|
|
3
|
+
A ready-made reactive-form one-time-code field: label + `hlm-input-otp` slots + validation error/hint display in one tag. The model is a plain `string`.
|
|
4
4
|
|
|
5
|
-
The Angular implementation is a standalone wrapper: it renders `HlmFormField` / `HlmError` / `HlmHint` from `@egose/shadcn-theme-ng/form-field`, `HlmLabel`, and `
|
|
5
|
+
The Angular implementation is a standalone wrapper: it renders `HlmFormField` / `HlmError` / `HlmHint` from `@egose/shadcn-theme-ng/form-field`, `HlmLabel`, and `HlmInputOtpControl` + `HlmInputOtp*` parts from `@egose/shadcn-theme-ng/input-otp`. The adapter inherits `BrnInputOtp` behavior. The OTP input is bound with `[formControlName]="controlName()"` and renders one `hlm-input-otp-slot` per `length` unit in a single group. It must live inside a `FormGroupDirective` (`[formGroup]` parent); ids come from `HlmFormIdGenerator` unless overridden.
|
|
6
6
|
|
|
7
7
|
> **Ships as:** `@egose/shadcn-theme-ng/form-input-otp` and `@egose/shadcn-theme-ng-tw/form-input-otp`
|
|
8
8
|
> (the `tw:`-prefixed Tailwind variant). Both expose the identical TypeScript surface; only the
|
|
@@ -53,7 +53,7 @@ NgModule-based consumer: add `EgFormInputOtp` (and `ReactiveFormsModule`) to the
|
|
|
53
53
|
`imports` — it is a standalone component, not a module.
|
|
54
54
|
|
|
55
55
|
`ControlValueAccessor` behavior: `eg-form-input-otp` itself is not a `ControlValueAccessor`; the
|
|
56
|
-
inner `
|
|
56
|
+
inner `hlm-input-otp` is (bound via `formControlName`), so `formControlName`/`formGroup` handling,
|
|
57
57
|
`Validators`, and `disabled` state from the control all flow through the parent form.
|
|
58
58
|
|
|
59
59
|
## Anatomy / Structure
|
|
@@ -169,8 +169,10 @@ export class PinComponent {
|
|
|
169
169
|
## Accessibility notes
|
|
170
170
|
|
|
171
171
|
- Label `for` ↔ OTP `inputId` wiring is automatic via `effectiveId` — always pass a `label`.
|
|
172
|
-
- `
|
|
173
|
-
-
|
|
172
|
+
- The wrapper uses `HlmInputOtpControl` (`hlm-input-otp`), a declarative adapter retaining `BrnInputOtp`'s CVA/editing behavior. Its native input references the displayed error (invalid + touched/dirty/submitted), otherwise the hint; correction/reset restores the hint.
|
|
173
|
+
- Optional `[aria-describedby]` (`string | null`, default `null`) adds consumer-owned IDs before the current message ID, normalized and deduplicated. Keep external elements mounted and reserve `<effectiveId>-error` / `-hint` for the wrapper. Bind the input rather than `[attr.aria-describedby]` on the wrapper host.
|
|
174
|
+
- `required` supplies the visual `*` and native `aria-required`; configure `Validators.required` (plus `minLength`) independently. Native `aria-invalid` follows the form control.
|
|
175
|
+
- `HlmError` is not a live region. Rendered DOM/focus regressions are automated coverage, not screen-reader testing.
|
|
174
176
|
|
|
175
177
|
## Theming / CSS variables
|
|
176
178
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, input, numberAttribute, computed, Component } from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, ChangeDetectorRef, DestroyRef, input, numberAttribute, computed, Component } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { FormGroupDirective, ReactiveFormsModule, ControlContainer } from '@angular/forms';
|
|
5
5
|
import { injectEgFormErrorMessages, HlmFormIdGenerator, injectEgFormSharedConfig, resolveEgFormError, HlmFormField, HlmError, HlmHint } from '@egose/shadcn-theme-ng/form-field';
|
|
6
6
|
import { HlmLabel } from '@egose/shadcn-theme-ng/label';
|
|
7
|
-
import { BrnInputOtp } from '@spartan-ng/brain/input-otp';
|
|
8
7
|
import * as i2 from '@egose/shadcn-theme-ng/input-otp';
|
|
9
8
|
import { HlmInputOtpImports } from '@egose/shadcn-theme-ng/input-otp';
|
|
10
9
|
import { hlm } from '@egose/shadcn-theme-ng/utils';
|
|
10
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
11
11
|
|
|
12
12
|
const EgFormInputOtpConfigToken = new InjectionToken('EgFormInputOtpConfig');
|
|
13
13
|
function provideEgFormInputOtpConfig(config) {
|
|
@@ -23,6 +23,14 @@ class EgFormInputOtp {
|
|
|
23
23
|
generatedId = inject(HlmFormIdGenerator).generate('eg-form-input-otp');
|
|
24
24
|
_config = injectEgFormInputOtpConfig();
|
|
25
25
|
_shared = injectEgFormSharedConfig();
|
|
26
|
+
_cdr = inject(ChangeDetectorRef);
|
|
27
|
+
_destroyRef = inject(DestroyRef);
|
|
28
|
+
ngOnInit() {
|
|
29
|
+
// Submit/reset may change message visibility without changing the field's value or validity.
|
|
30
|
+
this.formGroupDirective.form.events
|
|
31
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
32
|
+
.subscribe(() => this._cdr.markForCheck());
|
|
33
|
+
}
|
|
26
34
|
label = input(undefined, /* @ts-ignore */
|
|
27
35
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
28
36
|
controlId = input(undefined, /* @ts-ignore */
|
|
@@ -40,6 +48,8 @@ class EgFormInputOtp {
|
|
|
40
48
|
...(ngDevMode ? [{ debugName: "autoError" }] : /* istanbul ignore next */ []));
|
|
41
49
|
hint = input(undefined, /* @ts-ignore */
|
|
42
50
|
...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
|
|
51
|
+
/** Consumer-owned description IDs, merged with the current error and hint IDs. */
|
|
52
|
+
ariaDescribedBy = input(null, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
|
|
43
53
|
id = input(undefined, /* @ts-ignore */
|
|
44
54
|
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
45
55
|
length = input(6, { ...(ngDevMode ? { debugName: "length" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
@@ -89,8 +99,10 @@ class EgFormInputOtp {
|
|
|
89
99
|
effectiveDisabled() {
|
|
90
100
|
return this.disabled() || !!this.formGroupDirective.form.get(this.controlName())?.disabled;
|
|
91
101
|
}
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
describedBy() {
|
|
103
|
+
const ids = [this.ariaDescribedBy(), this.showError() ? this.errorId() : this.hint() ? this.hintId() : null];
|
|
104
|
+
return [...new Set(ids.filter(Boolean).join(' ').split(/\s+/).filter(Boolean))].join(' ') || null;
|
|
105
|
+
}
|
|
94
106
|
// Styling
|
|
95
107
|
userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
|
|
96
108
|
labelClass = input('', /* @ts-ignore */
|
|
@@ -113,7 +125,7 @@ class EgFormInputOtp {
|
|
|
113
125
|
$hintClass = computed(() => hlm("mt-0", this._shared.hintClass, this._config.hintClass, this.hintClass()), /* @ts-ignore */
|
|
114
126
|
...(ngDevMode ? [{ debugName: "$hintClass" }] : /* istanbul ignore next */ []));
|
|
115
127
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormInputOtp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
116
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormInputOtp, isStandalone: true, selector: "eg-form-input-otp", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, autoError: { classPropertyName: "autoError", publicName: "autoError", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, length: { classPropertyName: "length", publicName: "length", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, otpClass: { classPropertyName: "otpClass", publicName: "otpClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <
|
|
128
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormInputOtp, isStandalone: true, selector: "eg-form-input-otp", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, autoError: { classPropertyName: "autoError", publicName: "autoError", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, length: { classPropertyName: "length", publicName: "length", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, otpClass: { classPropertyName: "otpClass", publicName: "otpClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <hlm-input-otp\n [length]=\"length()\"\n [formControlName]=\"cnm\"\n [inputId]=\"effectiveId()\"\n [aria-describedby]=\"describedBy()\"\n [required]=\"rqrd\"\n [disabled]=\"effectiveDisabled()\"\n [class]=\"$otpClass()\"\n >\n <div hlmInputOtpGroup>\n @for (i of slotIndexes(); track i) {\n <hlm-input-otp-slot [index]=\"i\" />\n }\n </div>\n </hlm-input-otp>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ", isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: HlmFormField, selector: "hlm-form-field", inputs: ["class"] }, { kind: "directive", type: HlmError, selector: "hlm-error", inputs: ["class"] }, { kind: "directive", type: HlmHint, selector: "hlm-hint", inputs: ["class"] }, { kind: "directive", type: HlmLabel, selector: "[hlmLabel]", inputs: ["class"] }, { kind: "component", type: i2.HlmInputOtpControl, selector: "hlm-input-otp", inputs: ["aria-describedby", "required"] }, { kind: "directive", type: i2.HlmInputOtpGroup, selector: "[hlmInputOtpGroup],hlm-input-otp-group" }, { kind: "component", type: i2.HlmInputOtpSlot, selector: "hlm-input-otp-slot", inputs: ["index"] }] });
|
|
117
129
|
}
|
|
118
130
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormInputOtp, decorators: [{
|
|
119
131
|
type: Component,
|
|
@@ -123,11 +135,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
123
135
|
host: {
|
|
124
136
|
'[class]': '$userClass()',
|
|
125
137
|
},
|
|
126
|
-
imports: [ReactiveFormsModule, HlmFormField, HlmError, HlmHint, HlmLabel,
|
|
138
|
+
imports: [ReactiveFormsModule, HlmFormField, HlmError, HlmHint, HlmLabel, HlmInputOtpImports],
|
|
127
139
|
providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
|
|
128
|
-
template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <
|
|
140
|
+
template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <hlm-input-otp\n [length]=\"length()\"\n [formControlName]=\"cnm\"\n [inputId]=\"effectiveId()\"\n [aria-describedby]=\"describedBy()\"\n [required]=\"rqrd\"\n [disabled]=\"effectiveDisabled()\"\n [class]=\"$otpClass()\"\n >\n <div hlmInputOtpGroup>\n @for (i of slotIndexes(); track i) {\n <hlm-input-otp-slot [index]=\"i\" />\n }\n </div>\n </hlm-input-otp>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ",
|
|
129
141
|
}]
|
|
130
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], controlId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlId", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], autoError: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoError", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], length: [{ type: i0.Input, args: [{ isSignal: true, alias: "length", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], otpClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "otpClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
|
|
142
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], controlId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlId", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], autoError: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoError", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-describedby", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], length: [{ type: i0.Input, args: [{ isSignal: true, alias: "length", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], otpClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "otpClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
|
|
131
143
|
|
|
132
144
|
/*
|
|
133
145
|
* Public API Surface of form-input-otp
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import * as _angular_core from '@angular/core';
|
|
2
|
-
import { ValueProvider } from '@angular/core';
|
|
2
|
+
import { OnInit, ValueProvider } from '@angular/core';
|
|
3
3
|
import { ClassValue } from 'clsx';
|
|
4
4
|
import { NumberInput } from '@angular/cdk/coercion';
|
|
5
5
|
|
|
6
|
-
declare class EgFormInputOtp {
|
|
6
|
+
declare class EgFormInputOtp implements OnInit {
|
|
7
7
|
private readonly formGroupDirective;
|
|
8
8
|
private readonly _errorMessages;
|
|
9
9
|
private readonly generatedId;
|
|
10
10
|
private readonly _config;
|
|
11
11
|
private readonly _shared;
|
|
12
|
+
private readonly _cdr;
|
|
13
|
+
private readonly _destroyRef;
|
|
14
|
+
ngOnInit(): void;
|
|
12
15
|
label: _angular_core.InputSignal<string | undefined>;
|
|
13
16
|
controlId: _angular_core.InputSignal<string | undefined>;
|
|
14
17
|
controlName: _angular_core.InputSignal<string>;
|
|
@@ -20,6 +23,8 @@ declare class EgFormInputOtp {
|
|
|
20
23
|
*/
|
|
21
24
|
autoError: _angular_core.InputSignal<boolean>;
|
|
22
25
|
hint: _angular_core.InputSignal<string | undefined>;
|
|
26
|
+
/** Consumer-owned description IDs, merged with the current error and hint IDs. */
|
|
27
|
+
ariaDescribedBy: _angular_core.InputSignal<string | null>;
|
|
23
28
|
id: _angular_core.InputSignal<string | undefined>;
|
|
24
29
|
length: _angular_core.InputSignalWithTransform<number, NumberInput>;
|
|
25
30
|
disabled: _angular_core.InputSignal<boolean>;
|
|
@@ -47,6 +52,7 @@ declare class EgFormInputOtp {
|
|
|
47
52
|
* wrapper-toggle + `control.disable()` cannot clobber CVA state.
|
|
48
53
|
*/
|
|
49
54
|
effectiveDisabled(): boolean;
|
|
55
|
+
protected describedBy(): string | null;
|
|
50
56
|
userClass: _angular_core.InputSignal<ClassValue>;
|
|
51
57
|
labelClass: _angular_core.InputSignal<string>;
|
|
52
58
|
otpClass: _angular_core.InputSignal<string>;
|
|
@@ -58,7 +64,7 @@ declare class EgFormInputOtp {
|
|
|
58
64
|
$errorClass: _angular_core.Signal<string>;
|
|
59
65
|
$hintClass: _angular_core.Signal<string>;
|
|
60
66
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EgFormInputOtp, never>;
|
|
61
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormInputOtp, "eg-form-input-otp", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "controlId": { "alias": "controlId"; "required": false; "isSignal": true; }; "controlName": { "alias": "controlName"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "autoError": { "alias": "autoError"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "length": { "alias": "length"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "otpClass": { "alias": "otpClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
67
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormInputOtp, "eg-form-input-otp", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "controlId": { "alias": "controlId"; "required": false; "isSignal": true; }; "controlName": { "alias": "controlName"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "autoError": { "alias": "autoError"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "ariaDescribedBy": { "alias": "aria-describedby"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "length": { "alias": "length"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "otpClass": { "alias": "otpClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
interface EgFormInputOtpConfig {
|
|
@@ -100,6 +100,19 @@ No outputs. Readonly computeds/methods (usable via template ref): `effectiveId()
|
|
|
100
100
|
|
|
101
101
|
`SelectOption` is `export interface SelectOption { label: string; value: string }` from `@egose/shadcn-theme-ng/searchable-multiselect`.
|
|
102
102
|
|
|
103
|
+
### Local search and form values
|
|
104
|
+
|
|
105
|
+
Additional copy inputs:
|
|
106
|
+
|
|
107
|
+
- `searchLabel: string` (default `'Search options'`): visible native search label.
|
|
108
|
+
- `searchPlaceholder: string` (default `'Type to filter…'`): hint inside the search input.
|
|
109
|
+
- `emptyMessage: string` (default `'No matching options'`): polite status message for no matches or no supplied options.
|
|
110
|
+
- `removeLabel: (option: SelectOption) => string` (default ``option => `Remove ${option.label}` ``): pure accessible chip-remove name formatter; unresolved labels are raw IDs.
|
|
111
|
+
|
|
112
|
+
Pass the full options list. The inner control trims the query and matches label substrings case-insensitively in source order. Filtering retains hidden selections and their labels, including unresolved IDs written before asynchronous options arrive. Searching/clearing the query never changes or touches the form value. The query persists across closing/reopening and form resets; clearing its text restores all options. Wrapper and reactive-form disabled states disable the search even if the popover is already open.
|
|
113
|
+
|
|
114
|
+
The search has an associated visible label, no-match feedback uses a polite live region, and each chip remove button has an accessible name. All four copy inputs above are forwarded unchanged. For example, on the wrapper in the complete reactive-form example below, add `searchLabel="Find tags" searchPlaceholder="Type a tag name…" emptyMessage="No tags match"`. Import `SelectOption` from the multiselect subpath to type a localized `[removeLabel]` callback. See the [multiselect usage](../searchable-multiselect/README.md#local-search) for keyboard and async ownership details. This is local label search, with no remote request or virtualization behavior.
|
|
115
|
+
|
|
103
116
|
## Examples
|
|
104
117
|
|
|
105
118
|
### 1. Basic reactive form
|
|
@@ -237,7 +250,8 @@ import { EgFormSearchableMultiselect } from '@egose/shadcn-theme-ng/form-searcha
|
|
|
237
250
|
controlId="framework-picker"
|
|
238
251
|
label="Frameworks"
|
|
239
252
|
placeholder="Search frameworks…"
|
|
240
|
-
|
|
253
|
+
searchLabel="Find frameworks"
|
|
254
|
+
hint="Type to filter, then Tab to a checkbox and Space to select"
|
|
241
255
|
[options]="frameworks"
|
|
242
256
|
class="tw:max-w-md"
|
|
243
257
|
labelClass="tw:font-semibold"
|
|
@@ -87,6 +87,18 @@ class EgFormSearchableMultiselect {
|
|
|
87
87
|
// Multiselect-specific
|
|
88
88
|
options = input([], /* @ts-ignore */
|
|
89
89
|
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ [])); // [{ label, value }]
|
|
90
|
+
/** Visible native search label; local label filtering preserves every selected form value. */
|
|
91
|
+
searchLabel = input('Search options', /* @ts-ignore */
|
|
92
|
+
...(ngDevMode ? [{ debugName: "searchLabel" }] : /* istanbul ignore next */ []));
|
|
93
|
+
/** Search hint forwarded independently of the empty-selection placeholder. */
|
|
94
|
+
searchPlaceholder = input('Type to filter…', /* @ts-ignore */
|
|
95
|
+
...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
|
|
96
|
+
/** Polite live-region copy for an empty filtered option list. */
|
|
97
|
+
emptyMessage = input('No matching options', /* @ts-ignore */
|
|
98
|
+
...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
|
|
99
|
+
/** Pure accessible chip-remove name formatter; receives the resolved label or raw unresolved ID. */
|
|
100
|
+
removeLabel = input((option) => `Remove ${option.label}`, /* @ts-ignore */
|
|
101
|
+
...(ngDevMode ? [{ debugName: "removeLabel" }] : /* istanbul ignore next */ []));
|
|
90
102
|
// Styling classes
|
|
91
103
|
userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
|
|
92
104
|
labelClass = input('', /* @ts-ignore */
|
|
@@ -109,7 +121,7 @@ class EgFormSearchableMultiselect {
|
|
|
109
121
|
$hintClass = computed(() => hlm("mt-0", this._shared.hintClass, this._config.hintClass, this.hintClass()), /* @ts-ignore */
|
|
110
122
|
...(ngDevMode ? [{ debugName: "$hintClass" }] : /* istanbul ignore next */ []));
|
|
111
123
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormSearchableMultiselect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
112
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormSearchableMultiselect, isStandalone: true, selector: "eg-form-searchable-multiselect", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, autoError: { classPropertyName: "autoError", publicName: "autoError", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, controlClass: { classPropertyName: "controlClass", publicName: "controlClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <eg-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\">\n {{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <eg-searchable-multiselect\n [id]=\"effectiveId()\"\n [options]=\"options()\"\n [placeholder]=\"placeholder()\"\n [formControlName]=\"cnm\"\n [required]=\"rqrd\"\n [wrapperDisabled]=\"disabled()\"\n [ariaLabel]=\"lbl\"\n [ariaDescribedby]=\"describedBy()\"\n [class]=\"$controlClass()\"\n ></eg-searchable-multiselect>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </eg-form-field>\n ", isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: EgFormField, selector: "eg-form-field", inputs: ["class"] }, { kind: "directive", type: HlmError, selector: "hlm-error", inputs: ["class"] }, { kind: "directive", type: HlmHint, selector: "hlm-hint", inputs: ["class"] }, { kind: "directive", type: HlmLabel, selector: "[hlmLabel]", inputs: ["class"] }, { kind: "component", type: EgSearchableMultiselect, selector: "eg-searchable-multiselect", inputs: ["options", "placeholder", "id", "disabled", "wrapperDisabled", "ariaLabel", "ariaDescribedby", "class", "value"], outputs: ["valueChange"] }] });
|
|
124
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormSearchableMultiselect, isStandalone: true, selector: "eg-form-searchable-multiselect", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, autoError: { classPropertyName: "autoError", publicName: "autoError", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, searchLabel: { classPropertyName: "searchLabel", publicName: "searchLabel", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, removeLabel: { classPropertyName: "removeLabel", publicName: "removeLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, controlClass: { classPropertyName: "controlClass", publicName: "controlClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <eg-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\">\n {{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <eg-searchable-multiselect\n [id]=\"effectiveId()\"\n [options]=\"options()\"\n [placeholder]=\"placeholder()\"\n [searchLabel]=\"searchLabel()\"\n [searchPlaceholder]=\"searchPlaceholder()\"\n [emptyMessage]=\"emptyMessage()\"\n [removeLabel]=\"removeLabel()\"\n [formControlName]=\"cnm\"\n [required]=\"rqrd\"\n [wrapperDisabled]=\"disabled()\"\n [ariaLabel]=\"lbl\"\n [ariaDescribedby]=\"describedBy()\"\n [class]=\"$controlClass()\"\n ></eg-searchable-multiselect>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </eg-form-field>\n ", isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: EgFormField, selector: "eg-form-field", inputs: ["class"] }, { kind: "directive", type: HlmError, selector: "hlm-error", inputs: ["class"] }, { kind: "directive", type: HlmHint, selector: "hlm-hint", inputs: ["class"] }, { kind: "directive", type: HlmLabel, selector: "[hlmLabel]", inputs: ["class"] }, { kind: "component", type: EgSearchableMultiselect, selector: "eg-searchable-multiselect", inputs: ["options", "placeholder", "searchLabel", "searchPlaceholder", "emptyMessage", "removeLabel", "id", "disabled", "wrapperDisabled", "ariaLabel", "ariaDescribedby", "class", "value"], outputs: ["valueChange"] }] });
|
|
113
125
|
}
|
|
114
126
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormSearchableMultiselect, decorators: [{
|
|
115
127
|
type: Component,
|
|
@@ -121,9 +133,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
121
133
|
},
|
|
122
134
|
imports: [ReactiveFormsModule, EgFormField, HlmError, HlmHint, HlmLabel, EgSearchableMultiselect],
|
|
123
135
|
providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
|
|
124
|
-
template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <eg-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\">\n {{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <eg-searchable-multiselect\n [id]=\"effectiveId()\"\n [options]=\"options()\"\n [placeholder]=\"placeholder()\"\n [formControlName]=\"cnm\"\n [required]=\"rqrd\"\n [wrapperDisabled]=\"disabled()\"\n [ariaLabel]=\"lbl\"\n [ariaDescribedby]=\"describedBy()\"\n [class]=\"$controlClass()\"\n ></eg-searchable-multiselect>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </eg-form-field>\n ",
|
|
136
|
+
template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <eg-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\">\n {{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <eg-searchable-multiselect\n [id]=\"effectiveId()\"\n [options]=\"options()\"\n [placeholder]=\"placeholder()\"\n [searchLabel]=\"searchLabel()\"\n [searchPlaceholder]=\"searchPlaceholder()\"\n [emptyMessage]=\"emptyMessage()\"\n [removeLabel]=\"removeLabel()\"\n [formControlName]=\"cnm\"\n [required]=\"rqrd\"\n [wrapperDisabled]=\"disabled()\"\n [ariaLabel]=\"lbl\"\n [ariaDescribedby]=\"describedBy()\"\n [class]=\"$controlClass()\"\n ></eg-searchable-multiselect>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </eg-form-field>\n ",
|
|
125
137
|
}]
|
|
126
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], controlId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlId", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], autoError: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoError", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], controlClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
|
|
138
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], controlId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlId", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], autoError: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoError", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], searchLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchLabel", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], controlClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
|
|
127
139
|
|
|
128
140
|
/*
|
|
129
141
|
* Public API Surface of form-searchable-multiselect
|
|
@@ -42,6 +42,14 @@ declare class EgFormSearchableMultiselect {
|
|
|
42
42
|
protected showError(): boolean;
|
|
43
43
|
describedBy(): string | null;
|
|
44
44
|
options: _angular_core.InputSignal<SelectOption[]>;
|
|
45
|
+
/** Visible native search label; local label filtering preserves every selected form value. */
|
|
46
|
+
searchLabel: _angular_core.InputSignal<string>;
|
|
47
|
+
/** Search hint forwarded independently of the empty-selection placeholder. */
|
|
48
|
+
searchPlaceholder: _angular_core.InputSignal<string>;
|
|
49
|
+
/** Polite live-region copy for an empty filtered option list. */
|
|
50
|
+
emptyMessage: _angular_core.InputSignal<string>;
|
|
51
|
+
/** Pure accessible chip-remove name formatter; receives the resolved label or raw unresolved ID. */
|
|
52
|
+
removeLabel: _angular_core.InputSignal<(option: SelectOption) => string>;
|
|
45
53
|
userClass: _angular_core.InputSignal<ClassValue>;
|
|
46
54
|
labelClass: _angular_core.InputSignal<string>;
|
|
47
55
|
controlClass: _angular_core.InputSignal<string>;
|
|
@@ -53,7 +61,7 @@ declare class EgFormSearchableMultiselect {
|
|
|
53
61
|
$errorClass: _angular_core.Signal<string>;
|
|
54
62
|
$hintClass: _angular_core.Signal<string>;
|
|
55
63
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EgFormSearchableMultiselect, never>;
|
|
56
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormSearchableMultiselect, "eg-form-searchable-multiselect", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "controlId": { "alias": "controlId"; "required": false; "isSignal": true; }; "controlName": { "alias": "controlName"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "autoError": { "alias": "autoError"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "controlClass": { "alias": "controlClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
64
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormSearchableMultiselect, "eg-form-searchable-multiselect", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "controlId": { "alias": "controlId"; "required": false; "isSignal": true; }; "controlName": { "alias": "controlName"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "autoError": { "alias": "autoError"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "searchLabel": { "alias": "searchLabel"; "required": false; "isSignal": true; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "removeLabel": { "alias": "removeLabel"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "controlClass": { "alias": "controlClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
interface EgFormSearchableMultiselectConfig {
|
package/form-slider/README.md
CHANGED
|
@@ -158,7 +158,9 @@ export class PriceComponent {
|
|
|
158
158
|
## Accessibility notes
|
|
159
159
|
|
|
160
160
|
- The label is linked via `aria-labelledby` (sliders have no `for`-compatible control); always pass a `label`.
|
|
161
|
-
-
|
|
161
|
+
- Each focusable thumb carries `aria-labelledby`, form-control `aria-invalid`, and `aria-describedby` for the displayed error (invalid + touched/dirty/submitted), otherwise the hint. Correction/reset restores the hint, including range sliders.
|
|
162
|
+
- Optional `[aria-describedby]` (`string | null`, default `null`) adds consumer-owned IDs before the current message ID, normalized and deduplicated. Keep external elements mounted and reserve `<effectiveId>-error` / `-hint` for the wrapper. Bind the input rather than `[attr.aria-describedby]` on the wrapper host.
|
|
163
|
+
- The `required` asterisk remains visual; configure validators independently. The slider role does not support `aria-required`. `HlmError` is not a live region; automated DOM/focus coverage does not establish screen-reader announcement behavior.
|
|
162
164
|
|
|
163
165
|
## Theming / CSS variables
|
|
164
166
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, input, numberAttribute, computed, Component } from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, ChangeDetectorRef, DestroyRef, input, numberAttribute, computed, Component } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { FormGroupDirective, ReactiveFormsModule, ControlContainer } from '@angular/forms';
|
|
5
5
|
import { injectEgFormErrorMessages, HlmFormIdGenerator, injectEgFormSharedConfig, resolveEgFormError, HlmFormField, HlmError, HlmHint } from '@egose/shadcn-theme-ng/form-field';
|
|
6
6
|
import { HlmLabel } from '@egose/shadcn-theme-ng/label';
|
|
7
7
|
import { HlmSlider } from '@egose/shadcn-theme-ng/slider';
|
|
8
8
|
import { hlm } from '@egose/shadcn-theme-ng/utils';
|
|
9
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
9
10
|
|
|
10
11
|
const EgFormSliderConfigToken = new InjectionToken('EgFormSliderConfig');
|
|
11
12
|
function provideEgFormSliderConfig(config) {
|
|
@@ -21,6 +22,14 @@ class EgFormSlider {
|
|
|
21
22
|
generatedId = inject(HlmFormIdGenerator).generate('eg-form-slider');
|
|
22
23
|
_config = injectEgFormSliderConfig();
|
|
23
24
|
_shared = injectEgFormSharedConfig();
|
|
25
|
+
_cdr = inject(ChangeDetectorRef);
|
|
26
|
+
_destroyRef = inject(DestroyRef);
|
|
27
|
+
ngOnInit() {
|
|
28
|
+
// Submit/reset may change message visibility without changing the field's value or validity.
|
|
29
|
+
this.formGroupDirective.form.events
|
|
30
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
31
|
+
.subscribe(() => this._cdr.markForCheck());
|
|
32
|
+
}
|
|
24
33
|
label = input(undefined, /* @ts-ignore */
|
|
25
34
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
26
35
|
controlId = input(undefined, /* @ts-ignore */
|
|
@@ -38,6 +47,8 @@ class EgFormSlider {
|
|
|
38
47
|
...(ngDevMode ? [{ debugName: "autoError" }] : /* istanbul ignore next */ []));
|
|
39
48
|
hint = input(undefined, /* @ts-ignore */
|
|
40
49
|
...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
|
|
50
|
+
/** Consumer-owned description IDs, merged with the current error and hint IDs on every thumb. */
|
|
51
|
+
ariaDescribedBy = input(null, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
|
|
41
52
|
id = input(undefined, /* @ts-ignore */
|
|
42
53
|
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
43
54
|
disabled = input(false, /* @ts-ignore */
|
|
@@ -89,8 +100,10 @@ class EgFormSlider {
|
|
|
89
100
|
effectiveDisabled() {
|
|
90
101
|
return this.disabled() || !!this.formGroupDirective.form.get(this.controlName())?.disabled;
|
|
91
102
|
}
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
describedBy() {
|
|
104
|
+
const ids = [this.ariaDescribedBy(), this.showError() ? this.errorId() : this.hint() ? this.hintId() : null];
|
|
105
|
+
return [...new Set(ids.filter(Boolean).join(' ').split(/\s+/).filter(Boolean))].join(' ') || null;
|
|
106
|
+
}
|
|
94
107
|
// Styling
|
|
95
108
|
userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
|
|
96
109
|
labelClass = input('', /* @ts-ignore */
|
|
@@ -113,7 +126,7 @@ class EgFormSlider {
|
|
|
113
126
|
$hintClass = computed(() => hlm("mt-0", this._shared.hintClass, this._config.hintClass, this.hintClass()), /* @ts-ignore */
|
|
114
127
|
...(ngDevMode ? [{ debugName: "$hintClass" }] : /* istanbul ignore next */ []));
|
|
115
128
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormSlider, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
116
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormSlider, isStandalone: true, selector: "eg-form-slider", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, autoError: { classPropertyName: "autoError", publicName: "autoError", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", 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 }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, sliderClass: { classPropertyName: "sliderClass", publicName: "sliderClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [id]=\"labelId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <hlm-slider\n [id]=\"effectiveId()\"\n [formControlName]=\"cnm\"\n [min]=\"min()\"\n [max]=\"max()\"\n [step]=\"step()\"\n [disabled]=\"effectiveDisabled()\"\n [aria-labelledby]=\"lbl ? labelId() : null\"\n [class]=\"$sliderClass()\"\n />\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ", isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: HlmFormField, selector: "hlm-form-field", inputs: ["class"] }, { kind: "directive", type: HlmError, selector: "hlm-error", inputs: ["class"] }, { kind: "directive", type: HlmHint, selector: "hlm-hint", inputs: ["class"] }, { kind: "directive", type: HlmLabel, selector: "[hlmLabel]", inputs: ["class"] }, { kind: "component", type: HlmSlider, selector: "hlm-slider, brn-slider [hlm]" }] });
|
|
129
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormSlider, isStandalone: true, selector: "eg-form-slider", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, autoError: { classPropertyName: "autoError", publicName: "autoError", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", 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 }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, sliderClass: { classPropertyName: "sliderClass", publicName: "sliderClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [id]=\"labelId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <hlm-slider\n [id]=\"effectiveId()\"\n [formControlName]=\"cnm\"\n [min]=\"min()\"\n [max]=\"max()\"\n [step]=\"step()\"\n [disabled]=\"effectiveDisabled()\"\n [aria-labelledby]=\"lbl ? labelId() : null\"\n [aria-describedby]=\"describedBy()\"\n [class]=\"$sliderClass()\"\n />\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ", isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: HlmFormField, selector: "hlm-form-field", inputs: ["class"] }, { kind: "directive", type: HlmError, selector: "hlm-error", inputs: ["class"] }, { kind: "directive", type: HlmHint, selector: "hlm-hint", inputs: ["class"] }, { kind: "directive", type: HlmLabel, selector: "[hlmLabel]", inputs: ["class"] }, { kind: "component", type: HlmSlider, selector: "hlm-slider, brn-slider [hlm]", inputs: ["aria-describedby"] }] });
|
|
117
130
|
}
|
|
118
131
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormSlider, decorators: [{
|
|
119
132
|
type: Component,
|
|
@@ -125,9 +138,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
125
138
|
},
|
|
126
139
|
imports: [ReactiveFormsModule, HlmFormField, HlmError, HlmHint, HlmLabel, HlmSlider],
|
|
127
140
|
providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
|
|
128
|
-
template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [id]=\"labelId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <hlm-slider\n [id]=\"effectiveId()\"\n [formControlName]=\"cnm\"\n [min]=\"min()\"\n [max]=\"max()\"\n [step]=\"step()\"\n [disabled]=\"effectiveDisabled()\"\n [aria-labelledby]=\"lbl ? labelId() : null\"\n [class]=\"$sliderClass()\"\n />\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ",
|
|
141
|
+
template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [id]=\"labelId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <hlm-slider\n [id]=\"effectiveId()\"\n [formControlName]=\"cnm\"\n [min]=\"min()\"\n [max]=\"max()\"\n [step]=\"step()\"\n [disabled]=\"effectiveDisabled()\"\n [aria-labelledby]=\"lbl ? labelId() : null\"\n [aria-describedby]=\"describedBy()\"\n [class]=\"$sliderClass()\"\n />\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ",
|
|
129
142
|
}]
|
|
130
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], controlId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlId", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], autoError: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoError", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], 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 }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], sliderClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "sliderClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
|
|
143
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], controlId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlId", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], autoError: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoError", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-describedby", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], 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 }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], sliderClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "sliderClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
|
|
131
144
|
|
|
132
145
|
/*
|
|
133
146
|
* Public API Surface of form-slider
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import * as _angular_core from '@angular/core';
|
|
2
|
-
import { ValueProvider } from '@angular/core';
|
|
2
|
+
import { OnInit, ValueProvider } from '@angular/core';
|
|
3
3
|
import { ClassValue } from 'clsx';
|
|
4
4
|
import { NumberInput } from '@angular/cdk/coercion';
|
|
5
5
|
|
|
6
|
-
declare class EgFormSlider {
|
|
6
|
+
declare class EgFormSlider implements OnInit {
|
|
7
7
|
private readonly formGroupDirective;
|
|
8
8
|
private readonly _errorMessages;
|
|
9
9
|
private readonly generatedId;
|
|
10
10
|
private readonly _config;
|
|
11
11
|
private readonly _shared;
|
|
12
|
+
private readonly _cdr;
|
|
13
|
+
private readonly _destroyRef;
|
|
14
|
+
ngOnInit(): void;
|
|
12
15
|
label: _angular_core.InputSignal<string | undefined>;
|
|
13
16
|
controlId: _angular_core.InputSignal<string | undefined>;
|
|
14
17
|
controlName: _angular_core.InputSignal<string>;
|
|
@@ -20,6 +23,8 @@ declare class EgFormSlider {
|
|
|
20
23
|
*/
|
|
21
24
|
autoError: _angular_core.InputSignal<boolean>;
|
|
22
25
|
hint: _angular_core.InputSignal<string | undefined>;
|
|
26
|
+
/** Consumer-owned description IDs, merged with the current error and hint IDs on every thumb. */
|
|
27
|
+
ariaDescribedBy: _angular_core.InputSignal<string | null>;
|
|
23
28
|
id: _angular_core.InputSignal<string | undefined>;
|
|
24
29
|
disabled: _angular_core.InputSignal<boolean>;
|
|
25
30
|
required: _angular_core.InputSignal<boolean>;
|
|
@@ -49,6 +54,7 @@ declare class EgFormSlider {
|
|
|
49
54
|
* wrapper-toggle + `control.disable()` cannot clobber CVA state.
|
|
50
55
|
*/
|
|
51
56
|
effectiveDisabled(): boolean;
|
|
57
|
+
protected describedBy(): string | null;
|
|
52
58
|
userClass: _angular_core.InputSignal<ClassValue>;
|
|
53
59
|
labelClass: _angular_core.InputSignal<string>;
|
|
54
60
|
sliderClass: _angular_core.InputSignal<string>;
|
|
@@ -60,7 +66,7 @@ declare class EgFormSlider {
|
|
|
60
66
|
$errorClass: _angular_core.Signal<string>;
|
|
61
67
|
$hintClass: _angular_core.Signal<string>;
|
|
62
68
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EgFormSlider, never>;
|
|
63
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormSlider, "eg-form-slider", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "controlId": { "alias": "controlId"; "required": false; "isSignal": true; }; "controlName": { "alias": "controlName"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "autoError": { "alias": "autoError"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "step": { "alias": "step"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "sliderClass": { "alias": "sliderClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
69
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormSlider, "eg-form-slider", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "controlId": { "alias": "controlId"; "required": false; "isSignal": true; }; "controlName": { "alias": "controlName"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "autoError": { "alias": "autoError"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "ariaDescribedBy": { "alias": "aria-describedby"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "step": { "alias": "step"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "sliderClass": { "alias": "sliderClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
interface EgFormSliderConfig {
|
package/form-toggle/README.md
CHANGED
|
@@ -77,23 +77,24 @@ Selector (from source): `eg-form-toggle` (standalone component, host `tw:w-full`
|
|
|
77
77
|
|
|
78
78
|
Bind `formControlName` on the element; there is no `controlName` input.
|
|
79
79
|
|
|
80
|
-
| Input
|
|
81
|
-
|
|
|
82
|
-
| `label`
|
|
83
|
-
| `error`
|
|
84
|
-
| `autoError`
|
|
85
|
-
| `hint`
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
80
|
+
| Input | Type | Default | Description |
|
|
81
|
+
| ------------------ | --------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
82
|
+
| `label` | `string \| undefined` | — | Label text beside the toggle (hidden when omitted). |
|
|
83
|
+
| `error` | `string \| undefined` | — | Error text shown when invalid. |
|
|
84
|
+
| `autoError` | `boolean` | `true` | Auto-resolve the message from the control's `ValidationErrors` when `error` is unset. Explicit `error` always wins; `error=""` counts as unset. Global wording via `provideEgFormErrorMessages`. |
|
|
85
|
+
| `hint` | `string \| undefined` | — | Hint text shown otherwise. |
|
|
86
|
+
| `aria-describedby` | `string \| null` | `null` | Consumer-owned description IDs merged with the displayed error or hint on the native button. |
|
|
87
|
+
| `controlId` | `string \| undefined` | — | Explicit id (first priority for `effectiveId`). |
|
|
88
|
+
| `id` | `string \| undefined` | — | Fallback id (second priority). |
|
|
89
|
+
| `disabled` | `boolean` | `false` | Locks interaction (form control stays enabled). |
|
|
90
|
+
| `required` | `boolean` | `false` | Shows a red `*` next to the label. |
|
|
91
|
+
| `variant` | `'default' \| 'outline'` | `'default'` | Toggle variant forwarded to `hlmToggle`. |
|
|
92
|
+
| `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | Toggle size forwarded to `hlmToggle`. |
|
|
93
|
+
| `class` | `ClassValue` | `''` | Extra host classes (base `tw:w-full`). |
|
|
94
|
+
| `toggleClass` | `string` | `''` | Extra toggle-button classes. |
|
|
95
|
+
| `labelClass` | `string` | `''` | Extra label classes. |
|
|
96
|
+
| `errorClass` | `string` | `''` | Extra error classes (base `tw:mt-0`). |
|
|
97
|
+
| `hintClass` | `string` | `''` | Extra hint classes (base `tw:mt-0`). |
|
|
97
98
|
|
|
98
99
|
| Member | Description |
|
|
99
100
|
| -------------------- | ------------------------------------------------------------------------------ |
|
|
@@ -163,7 +164,10 @@ export class ConsentToggleComponent {
|
|
|
163
164
|
## Accessibility notes
|
|
164
165
|
|
|
165
166
|
- Label `for` ↔ button `id` wiring is automatic via `effectiveId` — always pass a `label`.
|
|
166
|
-
- The
|
|
167
|
+
- The actual toggle button exposes `aria-pressed` and references the **currently displayed error or hint** with `aria-describedby`. Touch/dirty-only changes and submission update the description; correction/reset restore the hint when present. Removing a hint removes its reference.
|
|
168
|
+
- Pass consumer-owned IDs with `[aria-describedby]="'notification-help notification-policy'"`. They precede the active wrapper message ID; whitespace is normalized and duplicate IDs are removed. Keep those external elements mounted. `${effectiveId}-error` and `${effectiveId}-hint` are reserved for wrapper messages; do not supply them as consumer IDs.
|
|
169
|
+
- The button exposes `aria-invalid="true"` while its validation error is displayed and removes the attribute when feedback clears. The native label association and pressed state remain independent of validation.
|
|
170
|
+
- The required `*` is visual; configure `Validators.requiredTrue` independently. Toggle buttons do not support `aria-required`.
|
|
167
171
|
|
|
168
172
|
## Theming / CSS variables
|
|
169
173
|
|