From 8992a56f18cd1d82b267eebaa95045d0ad509591 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 10:25:24 +0200 Subject: [PATCH 01/21] scaffolding --- playwright/cps-accessibility.spec.ts | 2 +- .../src/app/api-data/cps-input.json | 8 +++++ .../input-page/input-page.component.html | 4 +-- .../input-page/input-page.component.scss | 4 +-- .../cps-input/cps-input.component.html | 1 + .../cps-input/cps-input.component.ts | 30 ++++++++++++++++++- 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/playwright/cps-accessibility.spec.ts b/playwright/cps-accessibility.spec.ts index 0fb58f96..4d6f7326 100644 --- a/playwright/cps-accessibility.spec.ts +++ b/playwright/cps-accessibility.spec.ts @@ -106,7 +106,7 @@ const components: ComponentEntry[] = [ { route: '/file-upload', name: 'File upload', selector: 'cps-file-upload' }, // { route: '/icon', name: 'Icon', selector: 'cps-icon' }, { route: '/info-circle', name: 'Info circle', selector: 'cps-info-circle' }, - // { route: '/input', name: 'Input', selector: 'cps-input' }, + { route: '/input', name: 'Input', selector: 'cps-input' }, // { route: '/loader', name: 'Loader', selector: 'cps-loader' }, { route: '/menu', diff --git a/projects/composition/src/app/api-data/cps-input.json b/projects/composition/src/app/api-data/cps-input.json index e6b579b3..6eb18791 100644 --- a/projects/composition/src/app/api-data/cps-input.json +++ b/projects/composition/src/app/api-data/cps-input.json @@ -13,6 +13,14 @@ "default": "", "description": "Label of the input element." }, + { + "name": "ariaLabel", + "optional": false, + "readonly": false, + "type": "string", + "default": "", + "description": "Aria label for the input component, used for accessibility, it takes precedence over label." + }, { "name": "hint", "optional": false, diff --git a/projects/composition/src/app/pages/input-page/input-page.component.html b/projects/composition/src/app/pages/input-page/input-page.component.html index 540a9c5b..c177eb21 100644 --- a/projects/composition/src/app/pages/input-page/input-page.component.html +++ b/projects/composition/src/app/pages/input-page/input-page.component.html @@ -43,10 +43,10 @@ placeholder="Enter amount">
> diff --git a/projects/composition/src/app/pages/input-page/input-page.component.scss b/projects/composition/src/app/pages/input-page/input-page.component.scss index a8c849a8..e7e36c66 100644 --- a/projects/composition/src/app/pages/input-page/input-page.component.scss +++ b/projects/composition/src/app/pages/input-page/input-page.component.scss @@ -1,5 +1,5 @@ .inputs-group { - gap: 24px; + gap: 1.5rem; display: flex; flex-direction: column; } @@ -8,6 +8,6 @@ display: flex; align-items: center; .sync-val { - margin-left: 24px; + margin-left: 1.5rem; } } diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html index febc6601..66d134ef 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html @@ -28,6 +28,7 @@ [class.underlined]="appearance === 'underlined'"> @if (!valueToDisplay) { Date: Fri, 22 May 2026 12:01:42 +0200 Subject: [PATCH 02/21] remove calculations in ngAfterViewInit --- .../cps-input/cps-input.component.html | 58 ++++++------ .../cps-input/cps-input.component.scss | 91 ++++++++++--------- .../cps-input/cps-input.component.ts | 25 +---- 3 files changed, 78 insertions(+), 96 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html index 66d134ef..1e3f3c53 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html @@ -1,4 +1,4 @@ -
+
@if (label) {
} @@ -51,35 +48,34 @@ [value]="valueToDisplay" [disabled]="true" [readonly]="true" - [ngStyle]="{ - width: cvtWidth, - 'padding-left': prefixWidth || 'none' - }" /> + [style.width]="cvtWidth" /> } -
- @if (prefixIcon) { - - - - - } + @if (prefixIcon || prefixText) { +
+ @if (prefixIcon) { + + + + + } - @if (prefixText) { - - {{ prefixText }} - - } -
+ @if (prefixText) { + + {{ prefixText }} + + } +
+ } @if (!disabled && !readonly) {
diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss index 17316443..e60c41d1 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss @@ -28,17 +28,35 @@ $hover-transition-duration: 0.2s; .cps-input-wrap { position: relative; overflow: hidden; + display: flex; + align-items: stretch; + min-height: 2.375rem; + border: 0.0625rem solid $input-border-color; + border-radius: var(--cps-border-radius-small); + background: var(--cps-input-background); + transition-duration: $hover-transition-duration; &:hover { - input:enabled:not(:read-only) { - border: 1px solid $color-calm; + &:has(input:enabled:not(:read-only)) { + border-color: $color-calm; + } + .cps-input-action-btns { + .clear-btn { + cps-icon { + opacity: 0.5; + } + } } } + &:focus-within:not(:has(input:read-only)) { + border-color: $color-calm; + } + &:has(input:disabled:not([readonly])) { + background-color: $input-background-disabled; + } &-error { - input { - border-color: $color-error !important; - &:not(:focus) { - background: $error-background !important; - } + border-color: $color-error !important; + &:not(:focus-within) { + background: $error-background !important; } .cps-input-prefix-icon { color: $color-error !important; @@ -46,24 +64,20 @@ $hover-transition-duration: 0.2s; } input { - min-height: 38px; font-family: inherit; font-size: 1rem; color: $input-text-color; - background: var(--cps-input-background); + background: transparent; padding: 0.375rem 0.75rem; line-height: 1.5; - border: 1px solid $input-border-color; - transition-duration: $hover-transition-duration; + border: none; appearance: none; - border-radius: var(--cps-border-radius-small); - width: 100%; + border-radius: 0; + flex: 1; + min-width: 0; &:focus { outline: 0; } - &:focus:not(:read-only) { - border-color: $color-calm; - } &:read-only { cursor: default; } @@ -73,11 +87,15 @@ $hover-transition-duration: 0.2s; } &:disabled:not([readonly]) { color: $input-text-disabled-color; - background-color: $input-background-disabled; + background-color: transparent; pointer-events: none; } } + &:has(.cps-input-prefix) input { + padding-left: 0; + } + input:focus:not(:read-only) + .cps-input-prefix > .cps-input-prefix-icon, input:hover:not(:read-only) + .cps-input-prefix > .cps-input-prefix-icon { color: $color-calm; @@ -87,27 +105,17 @@ $hover-transition-duration: 0.2s; color: $input-prefix-icon-color; } - input:focus + .cps-input-prefix + .cps-input-action-btns > .clear-btn { + input:focus ~ .cps-input-action-btns > .clear-btn { cps-icon { opacity: 0.5; } } - &:hover { - .cps-input-action-btns { - .clear-btn { - cps-icon { - opacity: 0.5; - } - } - } - } - .cps-input-action-btns { display: flex; position: absolute; top: 50%; - right: 0.75rem; + right: 0.6875rem; margin-top: -0.5rem; .clear-btn { @@ -145,17 +153,15 @@ $hover-transition-duration: 0.2s; .cps-input-prefix { display: flex; - position: absolute; - height: 100%; - top: 50%; - left: 0.8rem; - transform: translate(0, -50%); + order: -1; + flex-shrink: 0; + padding-left: 0.75rem; &-icon { display: flex; flex-direction: column; justify-content: center; transition-duration: $hover-transition-duration; - margin-right: 0.5rem; + padding-right: 0.5rem; color: $input-prefix-icon-color; } &-text { @@ -165,27 +171,26 @@ $hover-transition-duration: 0.2s; color: $input-prefix-text-color; cursor: default; line-height: 1.2; + padding-right: 0.5rem; } } .cps-input-progress-bar { position: absolute; - bottom: 1px; - padding: 0 1px; + bottom: 0.0625rem; + padding: 0 0.0625rem; display: block; } &.borderless, &.underlined { + border: none !important; + border-radius: 0; input { line-height: 1; - border: none !important; - border-radius: 0; } } &.underlined { - input { - border-bottom: 1px solid $input-border-color !important; - } + border-bottom: 0.0625rem solid $input-border-color !important; } } @@ -233,7 +238,7 @@ $hover-transition-duration: 0.2s; font-weight: 600; .cps-input-label-info-circle { - margin-left: 8px; + margin-left: 0.5rem; } &-disabled { diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts index bb205f89..d43df0a4 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts @@ -1,7 +1,5 @@ import { CommonModule } from '@angular/common'; import { - AfterViewInit, - ChangeDetectorRef, Component, ElementRef, EventEmitter, @@ -12,8 +10,7 @@ import { Optional, Output, Self, - SimpleChanges, - ViewChild + SimpleChanges } from '@angular/core'; import { ControlValueAccessor, NgControl } from '@angular/forms'; import { Subscription } from 'rxjs'; @@ -50,7 +47,7 @@ export type CpsInputAppearanceType = 'outlined' | 'underlined' | 'borderless'; styleUrls: ['./cps-input.component.scss'] }) export class CpsInputComponent - implements ControlValueAccessor, OnInit, OnChanges, AfterViewInit, OnDestroy + implements ControlValueAccessor, OnInit, OnChanges, OnDestroy { /** * Label of the input element. @@ -253,10 +250,7 @@ export class CpsInputComponent */ @Output() enterClicked = new EventEmitter(); - @ViewChild('prefixTextSpan') prefixTextSpan: ElementRef | undefined; - currentType = ''; - prefixWidth = ''; cvtWidth = ''; private _statusChangesSubscription?: Subscription; @@ -264,8 +258,7 @@ export class CpsInputComponent constructor( @Self() @Optional() private _control: NgControl, - public elementRef: ElementRef, - private cdRef: ChangeDetectorRef + public elementRef: ElementRef ) { if (this._control) { this._control.valueAccessor = this; @@ -283,18 +276,6 @@ export class CpsInputComponent ); } - ngAfterViewInit() { - let w = 0; - if (this.prefixText) { - w = this.prefixTextSpan?.nativeElement?.offsetWidth + 22; - } - if (this.prefixIcon) { - w += 38 - (this.prefixText ? 14 : 0); - } - this.prefixWidth = w > 0 ? `${w}px` : ''; - this.cdRef.detectChanges(); - } - ngOnChanges(changes: SimpleChanges) { if (changes.width) { this.cvtWidth = convertSize(this.width); From 019db600d71cf4d6133b2a0129a7912ec46f0170 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 13:20:57 +0200 Subject: [PATCH 03/21] fix progressbar position --- .../src/lib/components/cps-input/cps-input.component.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss index e60c41d1..ae39f49c 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss @@ -176,8 +176,7 @@ $hover-transition-duration: 0.2s; } .cps-input-progress-bar { position: absolute; - bottom: 0.0625rem; - padding: 0 0.0625rem; + bottom: 0; display: block; } From 7b1e643939d7561e01e98963618cb033743010e7 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 17:51:47 +0200 Subject: [PATCH 04/21] further a11y improvements --- .../input-page/input-page.component.html | 4 +- .../pages/input-page/input-page.component.ts | 13 +- .../cps-input/cps-input.component.html | 43 ++++- .../cps-input/cps-input.component.scss | 23 +++ .../cps-input/cps-input.component.spec.ts | 163 ++++++++++++++++-- .../cps-input/cps-input.component.ts | 25 ++- 6 files changed, 243 insertions(+), 28 deletions(-) diff --git a/projects/composition/src/app/pages/input-page/input-page.component.html b/projects/composition/src/app/pages/input-page/input-page.component.html index c177eb21..e3d78d2f 100644 --- a/projects/composition/src/app/pages/input-page/input-page.component.html +++ b/projects/composition/src/app/pages/input-page/input-page.component.html @@ -53,7 +53,9 @@ {{ syncVal }}
- + @if (infoTooltip) { @if (!valueToDisplay) { @if (prefixIcon) { - + + "> } @@ -86,14 +100,29 @@ ? 'visible' : 'hidden' " - class="clear-btn"> + role="button" + tabindex="0" + aria-label="Clear" + class="clear-btn" + (mousedown)="$event.preventDefault()" + (keydown.enter)="onClear()" + (keydown.space)="$event.preventDefault(); onClear()"> } @if (type === 'password') { + role="button" + tabindex="0" + [attr.aria-label]=" + currentType === 'password' ? 'Show password' : 'Hide password' + " + [attr.aria-pressed]="currentType !== 'password'" + [class.password-show-btn-active]="currentType === 'text'" + (mousedown)="$event.preventDefault()" + (keydown.enter)="togglePassword()" + (keydown.space)="$event.preventDefault(); togglePassword()"> } @if (error && !hideDetails) { -
+ } diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss index ae39f49c..aaec2bd2 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss @@ -1,3 +1,5 @@ +@use '../../../../styles/mixins' as *; + $color-calm: var(--cps-color-calm); $input-hint-color: var(--cps-color-text-mild); $input-label-disabled-color: var(--cps-color-text-mild); @@ -129,6 +131,15 @@ $hover-transition-duration: 0.2s; opacity: 1 !important; } } + &:focus { + outline: none; + } + &:focus-visible { + @include focus-ring(0.125rem, 0.25rem, 50%); + cps-icon { + opacity: 1; + } + } } .password-show-btn { @@ -144,6 +155,12 @@ $hover-transition-duration: 0.2s; color: $color-calm; } } + &:focus { + outline: none; + } + &:focus-visible { + @include focus-ring(); + } } } @@ -163,6 +180,12 @@ $hover-transition-duration: 0.2s; transition-duration: $hover-transition-duration; padding-right: 0.5rem; color: $input-prefix-icon-color; + &:focus { + outline: none; + } + &:focus-visible { + @include focus-ring(); + } } &-text { display: flex; diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.spec.ts b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.spec.ts index 292d3273..e8220a0c 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.spec.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.spec.ts @@ -1,4 +1,3 @@ -import { ElementRef } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -434,20 +433,6 @@ describe('CpsInputComponent', () => { expect(unsubscribeSpy).toHaveBeenCalled(); }); - - it('should calculate prefix width after view init', () => { - fixture.componentRef.setInput('prefixText', 'USD'); - fixture.componentRef.setInput('prefixIcon', 'search'); - - // Mock prefixTextSpan - component.prefixTextSpan = { - nativeElement: { offsetWidth: 50 } - } as ElementRef; - - component.ngAfterViewInit(); - - expect(component.prefixWidth).toBeTruthy(); - }); }); describe('Edge Cases', () => { @@ -513,4 +498,152 @@ describe('CpsInputComponent', () => { expect(component.value).toBe('text'); }); }); + + describe('Accessibility', () => { + it('should associate label with input via for/id', () => { + fixture.componentRef.setInput('label', 'Username'); + fixture.detectChanges(); + + const label = fixture.nativeElement.querySelector('label'); + const input = fixture.nativeElement.querySelector('input'); + expect(label.getAttribute('for')).toBeTruthy(); + expect(label.getAttribute('for')).toBe(input.getAttribute('id')); + }); + + it('should set unique inputId', () => { + expect(component.inputId).toMatch(/^cps-input-/); + }); + + it('should set aria-invalid on input when error is set', () => { + fixture.componentRef.setInput('error', 'Field is required'); + fixture.detectChanges(); + + const input = fixture.nativeElement.querySelector('input'); + expect(input.getAttribute('aria-invalid')).toBe('true'); + }); + + it('should not set aria-invalid when there is no error', () => { + fixture.componentRef.setInput('error', ''); + fixture.detectChanges(); + + const input = fixture.nativeElement.querySelector('input'); + expect(input.getAttribute('aria-invalid')).toBeNull(); + }); + + it('should render error div with role="alert"', () => { + fixture.componentRef.setInput('error', 'Something went wrong'); + fixture.detectChanges(); + + const errorDiv = fixture.nativeElement.querySelector('.cps-input-error'); + expect(errorDiv).toBeTruthy(); + expect(errorDiv.getAttribute('role')).toBe('alert'); + }); + + describe('Clear button accessibility', () => { + beforeEach(() => { + fixture.componentRef.setInput('clearable', true); + component.writeValue('some value'); + fixture.detectChanges(); + }); + + it('should have role="button" on clear button', () => { + const clearBtn = fixture.nativeElement.querySelector('.clear-btn'); + expect(clearBtn.getAttribute('role')).toBe('button'); + }); + + it('should have tabindex="0" on clear button', () => { + const clearBtn = fixture.nativeElement.querySelector('.clear-btn'); + expect(clearBtn.getAttribute('tabindex')).toBe('0'); + }); + + it('should have aria-label="Clear" on clear button', () => { + const clearBtn = fixture.nativeElement.querySelector('.clear-btn'); + expect(clearBtn.getAttribute('aria-label')).toBe('Clear'); + }); + + it('should clear value on Enter keydown on clear button', () => { + const clearBtn = fixture.nativeElement.querySelector('.clear-btn'); + clearBtn.dispatchEvent( + new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }) + ); + expect(component.value).toBe(''); + }); + + it('should clear value on Space keydown on clear button', () => { + const clearBtn = fixture.nativeElement.querySelector('.clear-btn'); + clearBtn.dispatchEvent( + new KeyboardEvent('keydown', { key: ' ', bubbles: true }) + ); + expect(component.value).toBe(''); + }); + }); + + describe('Password toggle button accessibility', () => { + beforeEach(() => { + component.type = 'password'; + component.ngOnInit(); + fixture.detectChanges(); + }); + + it('should have role="button" on password toggle', () => { + const toggleBtn = + fixture.nativeElement.querySelector('.password-show-btn'); + expect(toggleBtn.getAttribute('role')).toBe('button'); + }); + + it('should have tabindex="0" on password toggle', () => { + const toggleBtn = + fixture.nativeElement.querySelector('.password-show-btn'); + expect(toggleBtn.getAttribute('tabindex')).toBe('0'); + }); + + it('should have aria-label "Show password" when password is hidden', () => { + const toggleBtn = + fixture.nativeElement.querySelector('.password-show-btn'); + expect(toggleBtn.getAttribute('aria-label')).toBe('Show password'); + }); + + it('should have aria-label "Hide password" after toggling', () => { + component.togglePassword(); + fixture.detectChanges(); + + const toggleBtn = + fixture.nativeElement.querySelector('.password-show-btn'); + expect(toggleBtn.getAttribute('aria-label')).toBe('Hide password'); + }); + + it('should have aria-pressed="false" when password is hidden', () => { + const toggleBtn = + fixture.nativeElement.querySelector('.password-show-btn'); + expect(toggleBtn.getAttribute('aria-pressed')).toBe('false'); + }); + + it('should have aria-pressed="true" after toggling', () => { + component.togglePassword(); + fixture.detectChanges(); + + const toggleBtn = + fixture.nativeElement.querySelector('.password-show-btn'); + expect(toggleBtn.getAttribute('aria-pressed')).toBe('true'); + }); + + it('should toggle on Enter keydown', () => { + const toggleBtn = + fixture.nativeElement.querySelector('.password-show-btn'); + toggleBtn.dispatchEvent( + new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }) + ); + expect(component.currentType).toBe('text'); + }); + + it('should toggle on Space keydown', () => { + const toggleBtn = + fixture.nativeElement.querySelector('.password-show-btn'); + toggleBtn.dispatchEvent( + new KeyboardEvent('keydown', { key: ' ', bubbles: true }) + ); + expect(component.currentType).toBe('text'); + }); + }); + }); }); diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts index d43df0a4..1ac6e490 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts @@ -12,7 +12,7 @@ import { Self, SimpleChanges } from '@angular/core'; -import { ControlValueAccessor, NgControl } from '@angular/forms'; +import { ControlValueAccessor, NgControl, Validators } from '@angular/forms'; import { Subscription } from 'rxjs'; import { CpsTooltipPosition } from '../../directives/cps-tooltip/cps-tooltip.directive'; import { convertSize } from '../../utils/internal/size-utils'; @@ -23,7 +23,10 @@ import { } from '../cps-icon/cps-icon.component'; import { CpsInfoCircleComponent } from '../cps-info-circle/cps-info-circle.component'; import { CpsProgressLinearComponent } from '../cps-progress-linear/cps-progress-linear.component'; -import { getComputedLabel } from '../../utils/internal/accessibility-utils'; +import { + generateUniqueId, + getComputedLabel +} from '../../utils/internal/accessibility-utils'; /** * CpsInputAppearanceType is used to define the border of the input field. @@ -125,7 +128,7 @@ export class CpsInputComponent * Size of icon before input value. * @group Props */ - @Input() prefixIconSize: iconSizeType = '18px'; + @Input() prefixIconSize: iconSizeType = '1.125rem'; /** * Text before input value. @@ -193,6 +196,12 @@ export class CpsInputComponent */ @Input() valueToDisplay = ''; + /** + * Aria label for the clickable prefix icon, required when prefixIconClickable is true. + * @group Props + */ + @Input() prefixIconAriaLabel = ''; + /** * Value of the input. * @default '' @@ -252,6 +261,7 @@ export class CpsInputComponent currentType = ''; cvtWidth = ''; + readonly inputId = generateUniqueId('cps-input'); private _statusChangesSubscription?: Subscription; private _value = ''; @@ -285,6 +295,11 @@ export class CpsInputComponent 'CpsInputComponent: unlabeled input component must have an ariaLabel for accessibility.' ); } + if (this.prefixIconClickable && !this.prefixIconAriaLabel?.trim()) { + console.error( + 'CpsInputComponent: prefixIconClickable requires a prefixIconAriaLabel for accessibility.' + ); + } } ngOnDestroy() { @@ -394,6 +409,10 @@ export class CpsInputComponent // eslint-disable-next-line @typescript-eslint/no-empty-function setDisabledState(_disabled: boolean) {} + get isRequired(): boolean { + return this._control?.control?.hasValidator(Validators.required) ?? false; + } + onClickPrefixIcon() { if (!this.prefixIconClickable || this.readonly || this.disabled) return; this.prefixIconClicked.emit(); From 663880c241d56fd1385141c757e2e50534362e4f Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 17:53:21 +0200 Subject: [PATCH 05/21] update api --- projects/composition/src/app/api-data/cps-input.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/projects/composition/src/app/api-data/cps-input.json b/projects/composition/src/app/api-data/cps-input.json index 6eb18791..803f96e3 100644 --- a/projects/composition/src/app/api-data/cps-input.json +++ b/projects/composition/src/app/api-data/cps-input.json @@ -106,7 +106,7 @@ "optional": false, "readonly": false, "type": "iconSizeType", - "default": "18px", + "default": "1.125rem", "description": "Size of icon before input value." }, { @@ -197,6 +197,14 @@ "default": "", "description": "Readonly value to display inside of input field." }, + { + "name": "prefixIconAriaLabel", + "optional": false, + "readonly": false, + "type": "string", + "default": "", + "description": "Aria label for the clickable prefix icon, required when prefixIconClickable is true." + }, { "name": "value", "optional": false, From 127f4a49098ef9feff7d2ebdb9c584ac36ef0b07 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 18:03:52 +0200 Subject: [PATCH 06/21] don't show focus ring on mouse activation --- .../lib/components/cps-input/cps-input.component.html | 4 +++- .../lib/components/cps-input/cps-input.component.scss | 7 +++++++ .../src/lib/components/cps-input/cps-input.component.ts | 9 +++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html index 6b0b48a0..202ca437 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html @@ -25,7 +25,8 @@ [class.clearable]="clearable" [class.persistent-clear]="persistentClear" [class.borderless]="appearance === 'borderless'" - [class.underlined]="appearance === 'underlined'"> + [class.underlined]="appearance === 'underlined'" + [class.keyboard-focused]="isKeyboardFocused"> @if (!valueToDisplay) { } diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss index aaec2bd2..ab9561f3 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss @@ -52,6 +52,13 @@ $hover-transition-duration: 0.2s; &:focus-within:not(:has(input:read-only)) { border-color: $color-calm; } + &.keyboard-focused { + @include focus-ring(0, -0.0625rem); + &::before, + &::after { + pointer-events: none; + } + } &:has(input:disabled:not([readonly])) { background-color: $input-background-disabled; } diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts index 1ac6e490..ff7fbe08 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts @@ -261,8 +261,10 @@ export class CpsInputComponent currentType = ''; cvtWidth = ''; + isKeyboardFocused = false; readonly inputId = generateUniqueId('cps-input'); + private _mouseActivated = false; private _statusChangesSubscription?: Subscription; private _value = ''; @@ -419,15 +421,22 @@ export class CpsInputComponent } onBlur() { + this.isKeyboardFocused = false; this._checkErrors(); this.blurred.emit(); } onFocus() { + this.isKeyboardFocused = !this._mouseActivated; + this._mouseActivated = false; this._control?.control?.markAsTouched(); this.focused.emit(); } + onInputMousedown() { + this._mouseActivated = true; + } + focus() { this.elementRef?.nativeElement?.querySelector('input')?.focus(); } From 3fbc3e0de352077626235607f1d8f752da4d89a3 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 19:45:51 +0200 Subject: [PATCH 07/21] fix a11y issues --- .../pages/input-page/input-page.component.html | 1 + .../cps-input/cps-input.component.html | 12 ++++++------ .../cps-input/cps-input.component.scss | 17 +++++++++++------ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/projects/composition/src/app/pages/input-page/input-page.component.html b/projects/composition/src/app/pages/input-page/input-page.component.html index e3d78d2f..f56b5eb9 100644 --- a/projects/composition/src/app/pages/input-page/input-page.component.html +++ b/projects/composition/src/app/pages/input-page/input-page.component.html @@ -55,6 +55,7 @@ @if (clearable) { @@ -115,19 +115,19 @@ @if (type === 'password') { } diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss index ab9561f3..0337a4e7 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss @@ -123,14 +123,16 @@ $hover-transition-duration: 0.2s; .cps-input-action-btns { display: flex; position: absolute; - top: 50%; - right: 0.6875rem; - margin-top: -0.5rem; + top: 0; + bottom: 0; + right: 0.5rem; + align-items: center; .clear-btn { display: flex; cursor: pointer; color: var(--cps-state-error); + padding: 0.25rem; cps-icon { opacity: 0; transition-duration: $hover-transition-duration; @@ -150,9 +152,12 @@ $hover-transition-duration: 0.2s; } .password-show-btn { - margin-left: 0.5rem; + margin-top: 0.125rem; + margin-left: 0.0625rem; cursor: pointer; color: $input-pass-show-btn-color; + display: flex; + padding: 0.1875rem; &-active { color: $color-calm; } @@ -234,12 +239,12 @@ $hover-transition-duration: 0.2s; } .password.clearable > input { - padding-right: 3.8rem; + padding-right: 3.75rem; } .password > input, .clearable > input { - padding-right: 2.2rem; + padding-right: 2.5rem; } .cps-input-hint { From 71b45a182ad5ecc83614ffb2ea208556f73e8eb9 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 20:07:20 +0200 Subject: [PATCH 08/21] fix a11y tests --- playwright/cps-accessibility.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/cps-accessibility.spec.ts b/playwright/cps-accessibility.spec.ts index 763cfb23..9909d645 100644 --- a/playwright/cps-accessibility.spec.ts +++ b/playwright/cps-accessibility.spec.ts @@ -106,7 +106,7 @@ const components: ComponentEntry[] = [ { route: '/file-upload', name: 'File upload', selector: 'cps-file-upload' }, // { route: '/icon', name: 'Icon', selector: 'cps-icon' }, { route: '/info-circle', name: 'Info circle', selector: 'cps-info-circle' }, - { route: '/input', name: 'Input', selector: 'cps-input' }, + { route: '/input', name: 'Input', selector: '.example-content cps-input' }, // { route: '/loader', name: 'Loader', selector: 'cps-loader' }, { route: '/menu', From 1f1d10dd2a2c65c330a5f4d18bf3405459b0c01a Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 20:37:57 +0200 Subject: [PATCH 09/21] fix readonly focus ring --- .../lib/components/cps-input/cps-input.component.scss | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss index 0337a4e7..fc6daeca 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss @@ -52,13 +52,21 @@ $hover-transition-duration: 0.2s; &:focus-within:not(:has(input:read-only)) { border-color: $color-calm; } + &.keyboard-focused:has(input:read-only) { + border-color: $color-calm; + } &.keyboard-focused { - @include focus-ring(0, -0.0625rem); + @include focus-ring(0.0625rem, 0, 0.25rem); &::before, &::after { pointer-events: none; } } + &.keyboard-focused.underlined, + &.keyboard-focused.borderless { + @include focus-ring(0, -0.0625rem, 0.25rem); + } + &:has(input:disabled:not([readonly])) { background-color: $input-background-disabled; } @@ -280,6 +288,7 @@ $hover-transition-duration: 0.2s; } } ::placeholder { + user-select: none; font-family: inherit; color: $input-placeholder-color; font-style: italic; From 43cf1d9c26c62326fb24a78935f5c42120aca933 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 20:41:33 +0200 Subject: [PATCH 10/21] simplify --- .../src/lib/components/cps-input/cps-input.component.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss index fc6daeca..743a7f31 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss @@ -49,9 +49,7 @@ $hover-transition-duration: 0.2s; } } } - &:focus-within:not(:has(input:read-only)) { - border-color: $color-calm; - } + &:focus-within:not(:has(input:read-only)), &.keyboard-focused:has(input:read-only) { border-color: $color-calm; } From 94b83b892ff4b8d2c14f4761a854f66cb12ec0d7 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 21:09:26 +0200 Subject: [PATCH 11/21] align prefix ixon --- .../lib/components/cps-input/cps-input.component.scss | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss index 743a7f31..60886f33 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss @@ -190,13 +190,14 @@ $hover-transition-duration: 0.2s; display: flex; order: -1; flex-shrink: 0; - padding-left: 0.75rem; + padding-left: 0.5rem; &-icon { display: flex; - flex-direction: column; - justify-content: center; + align-self: center; + padding: 0.1875rem; + margin-left: 0.0625rem; + margin-right: 0.3125rem; transition-duration: $hover-transition-duration; - padding-right: 0.5rem; color: $input-prefix-icon-color; &:focus { outline: none; @@ -212,6 +213,7 @@ $hover-transition-duration: 0.2s; color: $input-prefix-text-color; cursor: default; line-height: 1.2; + padding-left: 0.25rem; padding-right: 0.5rem; } } From e65ce926b2316e3805b01c44a6cd8fd6fdca88fd Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 21:15:37 +0200 Subject: [PATCH 12/21] fix focus rings --- .../src/lib/components/cps-input/cps-input.component.scss | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss index 60886f33..2918303c 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss @@ -150,7 +150,7 @@ $hover-transition-duration: 0.2s; outline: none; } &:focus-visible { - @include focus-ring(0.125rem, 0.25rem, 50%); + @include focus-ring(-0.125rem, -0.25rem, 50%); cps-icon { opacity: 1; } @@ -177,7 +177,7 @@ $hover-transition-duration: 0.2s; outline: none; } &:focus-visible { - @include focus-ring(); + @include focus-ring(-0.125rem, -0.25rem, 0.375rem); } } } @@ -201,9 +201,10 @@ $hover-transition-duration: 0.2s; color: $input-prefix-icon-color; &:focus { outline: none; + color: $color-calm; } &:focus-visible { - @include focus-ring(); + @include focus-ring(0, -0.0625rem, 0.375rem); } } &-text { From 65f66823153f224fbf99eb603615e220d7a5ff36 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 21:28:01 +0200 Subject: [PATCH 13/21] fix lost focus --- .../src/lib/components/cps-input/cps-input.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts index ff7fbe08..5b23eb3a 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts @@ -360,7 +360,7 @@ export class CpsInputComponent onTouched = () => {}; onInputEnterKeyDown() { - this.elementRef?.nativeElement?.querySelector('input')?.blur(); + this._checkErrors(); this.enterClicked.emit(); } @@ -398,6 +398,7 @@ export class CpsInputComponent onClear() { this.clear(); this.cleared.emit(); + this.focus(); } clear() { From 4ea09ce96d8df4ed3089c96da05781140d4a0d6e Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 21:29:38 +0200 Subject: [PATCH 14/21] update ut --- .../src/lib/components/cps-input/cps-input.component.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.spec.ts b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.spec.ts index e8220a0c..c28d480b 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.spec.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.spec.ts @@ -379,13 +379,13 @@ describe('CpsInputComponent', () => { expect(focusSpy).toHaveBeenCalled(); }); - it('should blur input on enter key', () => { + it('should not blur input on enter key', () => { const input = fixture.nativeElement.querySelector('input'); const blurSpy = jest.spyOn(input, 'blur'); component.onInputEnterKeyDown(); - expect(blurSpy).toHaveBeenCalled(); + expect(blurSpy).not.toHaveBeenCalled(); }); it('should mark control as touched on focus', () => { From 4e69dc296a1503cd48bc858dd5b9e78211f0f2f7 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 21:37:16 +0200 Subject: [PATCH 15/21] add aria-busy --- .../components/cps-autocomplete/cps-autocomplete.component.html | 1 + .../src/lib/components/cps-input/cps-input.component.html | 2 ++ .../src/lib/components/cps-select/cps-select.component.html | 1 + 3 files changed, 4 insertions(+) diff --git a/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.html b/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.html index b1990e6f..6dfcfb84 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.html @@ -327,6 +327,7 @@ role="combobox" aria-autocomplete="list" aria-haspopup="listbox" + [attr.aria-busy]="loading || validating ? true : null" [attr.aria-invalid]="error || externalError ? 'true' : null" [attr.aria-controls]="optionsListId" [attr.aria-activedescendant]="activeDescendantId" diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html index 69b85a8e..02e86745 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html @@ -33,6 +33,7 @@ [attr.aria-label]="computedLabel" [attr.aria-invalid]="error ? 'true' : null" [attr.aria-required]="isRequired || null" + [attr.aria-busy]="loading ? true : null" spellcheck="false" [type]="currentType" autocomplete="off" @@ -51,6 +52,7 @@ @if (valueToDisplay) { Date: Fri, 22 May 2026 22:07:19 +0200 Subject: [PATCH 16/21] add arialabel --- .../navigation-sidebar.component.html | 1 + .../colors-page/colors-page.component.html | 1 + .../icons-page/icons-page.component.html | 1 + .../cps-datepicker.component.html | 2 ++ .../cps-datepicker/cps-datepicker.component.ts | 17 ++++++++++++++++- .../cps-input/cps-input.component.spec.ts | 1 + .../cps-scheduler/cps-scheduler.component.html | 2 +- ...able-column-filter-constraint.component.html | 3 +++ .../cps-table/cps-table.component.html | 1 + .../cps-tree-table.component.html | 1 + 10 files changed, 28 insertions(+), 2 deletions(-) diff --git a/projects/composition/src/app/components/navigation-sidebar/navigation-sidebar.component.html b/projects/composition/src/app/components/navigation-sidebar/navigation-sidebar.component.html index 04a53cb2..4fc848e4 100644 --- a/projects/composition/src/app/components/navigation-sidebar/navigation-sidebar.component.html +++ b/projects/composition/src/app/components/navigation-sidebar/navigation-sidebar.component.html @@ -13,6 +13,7 @@

Styles

Components

{ fixture = TestBed.createComponent(CpsInputComponent); component = fixture.componentInstance; + fixture.componentRef.setInput('ariaLabel', 'Test input'); fixture.detectChanges(); }); diff --git a/projects/cps-ui-kit/src/lib/components/cps-scheduler/cps-scheduler.component.html b/projects/cps-ui-kit/src/lib/components/cps-scheduler/cps-scheduler.component.html index c65adac0..f76e3910 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-scheduler/cps-scheduler.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-scheduler/cps-scheduler.component.html @@ -532,7 +532,7 @@
Date: Fri, 22 May 2026 22:08:30 +0200 Subject: [PATCH 17/21] update api docs --- projects/composition/src/app/api-data/cps-datepicker.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/projects/composition/src/app/api-data/cps-datepicker.json b/projects/composition/src/app/api-data/cps-datepicker.json index 96902e76..62e0c1d8 100644 --- a/projects/composition/src/app/api-data/cps-datepicker.json +++ b/projects/composition/src/app/api-data/cps-datepicker.json @@ -13,6 +13,14 @@ "default": "", "description": "Label of the datepicker element." }, + { + "name": "ariaLabel", + "optional": false, + "readonly": false, + "type": "string", + "default": "", + "description": "Aria label for the datepicker component, used for accessibility, it takes precedence over label." + }, { "name": "disabled", "optional": false, From 84aa84bb370b0d51944d72d7affe9a1cdf325704 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 22:22:07 +0200 Subject: [PATCH 18/21] make prefix icon focused before the input --- .../cps-datepicker.component.scss | 35 +++++++--- .../cps-input/cps-input.component.html | 64 +++++++++---------- .../cps-input/cps-input.component.scss | 11 ++-- 3 files changed, 64 insertions(+), 46 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-datepicker/cps-datepicker.component.scss b/projects/cps-ui-kit/src/lib/components/cps-datepicker/cps-datepicker.component.scss index a1ea7dbc..75e9eaea 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-datepicker/cps-datepicker.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-datepicker/cps-datepicker.component.scss @@ -13,8 +13,8 @@ $disabled-day-color: var(--cps-color-text-lightest); width: 100%; &.focused { ::ng-deep { - input { - border: 1px solid $color-calm !important; + .cps-input-wrap { + border-color: $color-calm !important; } .cps-input-prefix-icon { color: $color-calm !important; @@ -82,8 +82,14 @@ $disabled-day-color: var(--cps-color-text-lightest); .p-datepicker .p-datepicker-header .p-datepicker-title { line-height: 2rem; } - .p-datepicker .p-datepicker-header .p-datepicker-title .p-datepicker-select-year, - .p-datepicker .p-datepicker-header .p-datepicker-title .p-datepicker-select-month { + .p-datepicker + .p-datepicker-header + .p-datepicker-title + .p-datepicker-select-year, + .p-datepicker + .p-datepicker-header + .p-datepicker-title + .p-datepicker-select-month { color: $text-color; transition: background-color 0.2s, @@ -102,7 +108,10 @@ $disabled-day-color: var(--cps-color-text-lightest); .p-datepicker-select-month:enabled:hover { color: $color-calm; } - .p-datepicker .p-datepicker-header .p-datepicker-title .p-datepicker-select-month { + .p-datepicker + .p-datepicker-header + .p-datepicker-title + .p-datepicker-select-month { margin-right: 0.5rem; } .p-datepicker table { @@ -206,7 +215,9 @@ $disabled-day-color: var(--cps-color-text-lightest); transition: box-shadow 0.2s; border-radius: 6px; } - .p-datepicker .p-datepicker-month-view .p-datepicker-month.p-datepicker-day-selected { + .p-datepicker + .p-datepicker-month-view + .p-datepicker-month.p-datepicker-day-selected { color: $color-calm; background: $color-selected-background; font-weight: bold; @@ -219,7 +230,9 @@ $disabled-day-color: var(--cps-color-text-lightest); transition: box-shadow 0.2s; border-radius: 6px; } - .p-datepicker .p-datepicker-year-view .p-datepicker-year.p-datepicker-day-selected { + .p-datepicker + .p-datepicker-year-view + .p-datepicker-year.p-datepicker-day-selected { color: $color-calm; background: $color-selected-background; font-weight: bold; @@ -254,7 +267,9 @@ $disabled-day-color: var(--cps-color-text-lightest); } .p-datepicker:not(.p-disabled) .p-datepicker-month-view - .p-datepicker-month:not(.p-disabled):not(.p-datepicker-day-selected):hover { + .p-datepicker-month:not(.p-disabled):not( + .p-datepicker-day-selected + ):hover { background: $color-highlight; } .p-datepicker:not(.p-disabled) @@ -266,7 +281,9 @@ $disabled-day-color: var(--cps-color-text-lightest); } .p-datepicker:not(.p-disabled) .p-yearpicker - .p-datepicker-year:not(.p-disabled):not(.p-datepicker-day-selected):hover { + .p-datepicker-year:not(.p-disabled):not( + .p-datepicker-day-selected + ):hover { background: $color-highlight; } .p-datepicker:not(.p-disabled) diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html index 02e86745..20264714 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html @@ -27,38 +27,6 @@ [class.borderless]="appearance === 'borderless'" [class.underlined]="appearance === 'underlined'" [class.keyboard-focused]="isKeyboardFocused"> - @if (!valueToDisplay) { - - } - - @if (valueToDisplay) { - - } - @if (prefixIcon || prefixText) {
@if (prefixIcon) { @@ -95,6 +63,38 @@
} + @if (!valueToDisplay) { + + } + + @if (valueToDisplay) { + + } + @if (!disabled && !readonly) {
@if (clearable) { diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss index 2918303c..dda2ada9 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss @@ -111,12 +111,12 @@ $hover-transition-duration: 0.2s; padding-left: 0; } - input:focus:not(:read-only) + .cps-input-prefix > .cps-input-prefix-icon, - input:hover:not(:read-only) + .cps-input-prefix > .cps-input-prefix-icon { + &:has(input:focus:not(:read-only)) .cps-input-prefix-icon, + &:has(input:hover:not(:read-only)) .cps-input-prefix-icon { color: $color-calm; } - input:disabled + .cps-input-prefix > .cps-input-prefix-icon { + &:has(input:disabled) .cps-input-prefix-icon { color: $input-prefix-icon-color; } @@ -182,13 +182,14 @@ $hover-transition-duration: 0.2s; } } - input:not(:read-only) + .cps-input-prefix:hover > .cps-input-prefix-icon { + &:has(input:not(:read-only)) + .cps-input-prefix:hover + > .cps-input-prefix-icon { color: $color-calm; } .cps-input-prefix { display: flex; - order: -1; flex-shrink: 0; padding-left: 0.5rem; &-icon { From 352c0e2a5a8a8164b0c833b96940508e22b9356a Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 22:26:43 +0200 Subject: [PATCH 19/21] fix border radius --- .../src/lib/components/cps-input/cps-input.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss index dda2ada9..f920b25d 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss @@ -34,7 +34,7 @@ $hover-transition-duration: 0.2s; align-items: stretch; min-height: 2.375rem; border: 0.0625rem solid $input-border-color; - border-radius: var(--cps-border-radius-small); + border-radius: 0.25rem; background: var(--cps-input-background); transition-duration: $hover-transition-duration; &:hover { From 8502708dcf0ecd3b83474a54ee70fce248491d40 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 22:36:04 +0200 Subject: [PATCH 20/21] move prefixIconAriaLabel up --- .../composition/src/app/api-data/cps-input.json | 16 ++++++++-------- .../components/cps-input/cps-input.component.ts | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/projects/composition/src/app/api-data/cps-input.json b/projects/composition/src/app/api-data/cps-input.json index 803f96e3..71b3220b 100644 --- a/projects/composition/src/app/api-data/cps-input.json +++ b/projects/composition/src/app/api-data/cps-input.json @@ -109,6 +109,14 @@ "default": "1.125rem", "description": "Size of icon before input value." }, + { + "name": "prefixIconAriaLabel", + "optional": false, + "readonly": false, + "type": "string", + "default": "", + "description": "Aria label for the clickable prefix icon, required when prefixIconClickable is true." + }, { "name": "prefixText", "optional": false, @@ -197,14 +205,6 @@ "default": "", "description": "Readonly value to display inside of input field." }, - { - "name": "prefixIconAriaLabel", - "optional": false, - "readonly": false, - "type": "string", - "default": "", - "description": "Aria label for the clickable prefix icon, required when prefixIconClickable is true." - }, { "name": "value", "optional": false, diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts index 5b23eb3a..bf3571bf 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.ts @@ -130,6 +130,12 @@ export class CpsInputComponent */ @Input() prefixIconSize: iconSizeType = '1.125rem'; + /** + * Aria label for the clickable prefix icon, required when prefixIconClickable is true. + * @group Props + */ + @Input() prefixIconAriaLabel = ''; + /** * Text before input value. * @group Props @@ -196,12 +202,6 @@ export class CpsInputComponent */ @Input() valueToDisplay = ''; - /** - * Aria label for the clickable prefix icon, required when prefixIconClickable is true. - * @group Props - */ - @Input() prefixIconAriaLabel = ''; - /** * Value of the input. * @default '' From 5c65b7d2440dcdb3b38db490bf6cf31ee530c049 Mon Sep 17 00:00:00 2001 From: fateeand Date: Fri, 22 May 2026 22:47:03 +0200 Subject: [PATCH 21/21] address feedback from copilot --- .../components/cps-input/cps-input.component.html | 10 +++++----- .../components/cps-input/cps-input.component.scss | 15 +++++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html index 20264714..e40f7091 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.html @@ -87,6 +87,7 @@ @if (valueToDisplay) { - + } @if (type === 'password') { @@ -125,12 +127,10 @@ " [attr.aria-pressed]="currentType !== 'password'" (mousedown)="$event.preventDefault()" + (click)="togglePassword()" (keydown.enter)="togglePassword()" (keydown.space)="$event.preventDefault(); togglePassword()"> - + }
diff --git a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss index f920b25d..b820a212 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss +++ b/projects/cps-ui-kit/src/lib/components/cps-input/cps-input.component.scss @@ -124,6 +124,9 @@ $hover-transition-duration: 0.2s; cps-icon { opacity: 0.5; } + &:hover cps-icon { + opacity: 1; + } } .cps-input-action-btns { @@ -142,9 +145,9 @@ $hover-transition-duration: 0.2s; cps-icon { opacity: 0; transition-duration: $hover-transition-duration; - &:hover { - opacity: 1 !important; - } + } + &:hover cps-icon { + opacity: 1; } &:focus { outline: none; @@ -169,9 +172,9 @@ $hover-transition-duration: 0.2s; } cps-icon { transition-duration: $hover-transition-duration; - &:hover { - color: $color-calm; - } + } + &:hover cps-icon { + color: $color-calm; } &:focus { outline: none;