diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.html b/projects/igniteui-angular/src/lib/combo/combo.component.html index 9e795a6f3d3..9fa321d2514 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.html +++ b/projects/igniteui-angular/src/lib/combo/combo.component.html @@ -9,7 +9,7 @@ , set2: Set): any[] => { IgxComboAddItemComponent, IgxButtonDirective, IgxRippleDirective, + IgxReadOnlyInputDirective, IgxComboFilteringPipe, IgxComboGroupingPipe ] diff --git a/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-component.scss b/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-component.scss index 00062ada101..bf077148fc2 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-component.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-component.scss @@ -42,11 +42,11 @@ } @include e(notch) { - @extend %igx-input-group__notch !optional; + @extend %igx-input-group__notch !optional; } @include e(filler) { - @extend %igx-input-group__filler !optional; + @extend %igx-input-group__filler !optional; } @include e(input) { @@ -111,20 +111,24 @@ @extend %suffixed !optional; } + @include m(readonly) { + @extend %form-group-display--readonly !optional; + } + // Textarea modifier @include m(textarea-group) { @extend %textarea-group !optional; @include e(bundle-main) { - @extend %form-group-textarea-group-bundle-main !optional; + @extend %form-group-textarea-group-bundle-main !optional; } @include e(bundle) { - @extend %form-group-textarea-group-bundle !optional; + @extend %form-group-textarea-group-bundle !optional; } @include e(label) { - @extend %form-group-textarea-label !optional; + @extend %form-group-textarea-label !optional; } } @@ -282,6 +286,8 @@ } @include m(invalid) { + @extend %form-group-display--invalid !optional; + @include e(label) { @extend %form-group-label--error !optional; } diff --git a/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss index 66643c96e65..05b21fa297c 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss @@ -286,6 +286,91 @@ } } + %form-group-display--readonly:not(%form-group-display--file) { + igx-prefix, + [igxPrefix], + igx-suffix, + [igxSuffix] { + color: var-get($theme, 'disabled-text-color'); + + @if $variant == 'fluent' { + background: transparent; + } + + @if $variant == 'bootstrap' { + background: var-get($theme, 'border-disabled-background'); + } + } + + @if $variant == 'bootstrap' { + %form-group-input { + background: var-get($theme, 'border-disabled-background'); + } + } + + %form-group-bundle--hover::after { + @if $variant == 'material' { + border-block-end-color: var-get($theme, 'idle-bottom-line-color'); + } + } + + @if $variant == 'indigo' { + %form-group-bundle--hover:not(:focus-within) { + background: unset; + + &::after { + border-block-end-color: var-get($theme, 'disabled-text-color'); + } + } + } + + &%igx-input-group-fluent:not(:focus-within) { + %form-group-bundle--hover::before { + box-shadow: inset 0 0 0 var(--_fluent-input-border-size) var-get($theme, 'border-color'); + } + } + + &%form-group-display--box:not(%form-group-display--disabled) { + %form-group-bundle { + background: var-get($theme, 'box-background-focus'); + } + } + + &%form-group-display--border:not(%form-group-display--disabled) { + %form-group-bundle:hover:not(:focus-within) { + %form-group-bundle-start, + %igx-input-group__filler, + %form-group-bundle-end { + border-color: var-get($theme, 'border-color'); + } + + %igx-input-group__notch { + border-block-start-color: var-get($theme, 'border-color'); + border-block-end-color: var-get($theme, 'border-color'); + } + } + } + + &%form-group-display--search { + %form-group-bundle-search--hover:not(:focus-within) { + box-shadow: var-get($theme, 'search-resting-shadow'); + } + } + + &:hover { + %form-group-input--hover { + cursor: default; + color: var-get($theme, 'filled-text-color'); + + &:not(:focus-within) { + &::placeholder { + color: var-get($theme, 'placeholder-color'); + } + } + } + } + } + %form-group-display--disabled { pointer-events: none; user-select: none; @@ -361,8 +446,6 @@ } %form-group-bundle--hover { - //cursor: pointer; - &::after { border-block-end-width: rem(1px); border-block-end-color: var-get($theme, 'hover-bottom-line-color'); @@ -396,13 +479,6 @@ caret-color: initial; } - %form-group-bundle--error { - &::after { - border-block-end-color: var-get($theme, 'error-secondary-color'); - } - caret-color: initial; - } - %form-group-bundle--disabled { cursor: default; @@ -612,8 +688,7 @@ %bootstrap-file-focused, %bootstrap-file-valid, %bootstrap-file-warning, - %bootstrap-file-invalid - { + %bootstrap-file-invalid { %form-group-bundle { border-radius: var-get($theme, 'box-border-radius'); transition: box-shadow .15s ease-out, border .15s ease-out; @@ -906,11 +981,9 @@ } &:hover { - %form-group-bundle-start { - border-color: var-get($theme, 'hover-border-color'); - } - - %igx-input-group__filler { + %form-group-bundle-start, + %igx-input-group__filler, + %form-group-bundle-end { border-color: var-get($theme, 'hover-border-color'); } @@ -918,10 +991,6 @@ border-block-start-color: var-get($theme, 'hover-border-color'); border-block-end-color: var-get($theme, 'hover-border-color'); } - - %form-group-bundle-end { - border-color: var-get($theme, 'hover-border-color'); - } } } @@ -1242,10 +1311,6 @@ color: var-get($theme, 'success-secondary-color'); } - %form-group-label--error { - color: var-get($theme, 'error-secondary-color'); - } - %form-group-label--required { &::after { content: '#{$required-symbol}'; @@ -1517,11 +1582,8 @@ } } - %form-group-line--error { - background: var-get($theme, 'error-secondary-color'); - } - - %form-group-border--error { + %form-group-border--error:not(%form-group-display--readonly), + %form-group-border--error%form-group-display--file { %form-group-bundle-start { border-inline-start-color: var-get($theme, 'error-secondary-color'); border-block-start-color: var-get($theme, 'error-secondary-color'); @@ -1611,10 +1673,6 @@ %form-group-helper--warning { color: var-get($theme, 'warning-secondary-color'); } - - %form-group-helper--error { - color: var-get($theme, 'error-secondary-color'); - } } %form-group-helper-item { @@ -1861,14 +1919,6 @@ } } - %form-group-bundle-error--fluent, - %form-group-bundle-error--fluent--hover, - %form-group-bundle-error--fluent--focus { - &::before { - box-shadow: inset 0 0 0 var(--_fluent-input-border-size) var-get($theme, 'error-secondary-color'); - } - } - %form-group-bundle-success--fluent, %form-group-bundle-success--fluent--hover, %form-group-bundle-success--fluent--focus { @@ -1898,6 +1948,48 @@ } } + %form-group-display--invalid:not(%form-group-display--readonly), + %form-group-display--invalid%form-group-display--file { + @if $variant != 'indigo' { + %form-group-label--error, + %form-group-helper--error { + color: var-get($theme, 'error-secondary-color'); + } + } + + %form-group-line--error { + background: var-get($theme, 'error-secondary-color'); + } + + %form-group-bundle--error { + &::after { + border-block-end-color: var-get($theme, 'error-secondary-color'); + } + + caret-color: initial; + } + + &%form-group-display--bootstrap { + %bootstrap-input--error { + border: rem(1px) solid var-get($theme, 'error-secondary-color'); + + &:focus { + box-shadow: 0 0 0 rem(4px) var-get($theme, 'error-shadow-color'); + } + } + } + + &%igx-input-group-fluent { + %form-group-bundle-error--fluent, + %form-group-bundle-error--fluent--hover, + %form-group-bundle-error--fluent--focus { + &::before { + box-shadow: inset 0 0 0 var(--_fluent-input-border-size) var-get($theme, 'error-secondary-color'); + } + } + } + } + // Native input %fluent-input { font-size: rem(14px); @@ -2266,14 +2358,16 @@ box-shadow: 0 0 0 rem(4px) color($color: 'warn', $variant: '500', $opacity: .38); } - %bootstrap-input--error { - border: rem(1px) solid var-get($theme, 'error-secondary-color'); + %form-group-display:not(%form-group-display--file) { + %bootstrap-input--error { + border: rem(1px) solid var-get($theme, 'error-secondary-color'); - &:focus { - box-shadow: 0 0 0 rem(4px) var-get($theme, 'error-shadow-color'); - - + %bootstrap-file-input { + &:focus { box-shadow: 0 0 0 rem(4px) var-get($theme, 'error-shadow-color'); + + + %bootstrap-file-input { + box-shadow: 0 0 0 rem(4px) var-get($theme, 'error-shadow-color'); + } } } } diff --git a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html index 81159a032bd..551816ddb1c 100644 --- a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html +++ b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html @@ -14,7 +14,9 @@ } - - diff --git a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts index e09b95b35ce..46347ae67c4 100644 --- a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts +++ b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts @@ -37,6 +37,7 @@ import { getCurrentResourceStrings } from '../core/i18n/resources'; import { fadeIn, fadeOut } from 'igniteui-angular/animations'; import { PickerCalendarOrientation } from '../date-common/types'; import { calendarRange, isDateInRanges } from '../calendar/common/helpers'; +import { IgxReadOnlyInputDirective } from '../directives/input/read-only-input.directive'; const SingleInputDatesConcatenationString = ' - '; @@ -76,6 +77,7 @@ const SingleInputDatesConcatenationString = ' - '; IgxInputDirective, IgxPrefixDirective, IgxSuffixDirective, + IgxReadOnlyInputDirective, DateRangePickerFormatPipe ] }) @@ -351,6 +353,10 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective @Input({ transform: booleanAttribute }) public showWeekNumbers = false; + /** @hidden @internal */ + @Input({ transform: booleanAttribute }) + public readOnly = false; + /** * Emitted when the picker's value changes. Used for two-way binding. * @@ -643,7 +649,7 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective * ``` */ public open(overlaySettings?: OverlaySettings): void { - if (!this.collapsed || this.disabled) { + if (!this.collapsed || this.disabled || this.readOnly) { return; } diff --git a/projects/igniteui-angular/src/lib/directives/input/read-only-input.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/input/read-only-input.directive.spec.ts new file mode 100644 index 00000000000..43f7b4edfc0 --- /dev/null +++ b/projects/igniteui-angular/src/lib/directives/input/read-only-input.directive.spec.ts @@ -0,0 +1,61 @@ +import { Component, ViewChild } from '@angular/core'; +import { TestBed, waitForAsync } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { IgxReadOnlyInputDirective } from './read-only-input.directive'; +import { IgxDatePickerComponent, IgxInputGroupComponent } from 'igniteui-angular'; +import { By } from '@angular/platform-browser'; + +describe('IgxReadOnlyInputDirective', () => { + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + NoopAnimationsModule, + TestComponent + ] + }) + .compileComponents(); + })); + + it('should update readOnly property and `igx-input-group--readonly` class correctly', () => { + const fixture = TestBed.createComponent(TestComponent); + fixture.detectChanges(); + + const inputGroupDebug = fixture.debugElement.query(By.directive(IgxInputGroupComponent)); + const inputGroupEl = inputGroupDebug.nativeElement as HTMLElement; + expect(inputGroupEl.classList.contains('igx-input-group--readonly')).toBeFalse(); + + const inputDebug = fixture.debugElement.query(By.css('input')); + const inputEl = inputDebug.nativeElement as HTMLInputElement; + expect(inputEl.readOnly).toBeFalse(); + + fixture.componentInstance.datePicker.readOnly = true; + fixture.detectChanges(); + expect(inputGroupEl.classList.contains('igx-input-group--readonly')).toBeTrue(); + expect(inputEl.readOnly).toBeTrue(); + + fixture.componentInstance.datePicker.readOnly = false; + fixture.detectChanges(); + expect(inputGroupEl.classList.contains('igx-input-group--readonly')).toBeFalse(); + expect(inputEl.readOnly).toBeFalse(); + + // When the date-picker component is in dialog mode, the native input is always readonly + fixture.componentInstance.datePicker.mode = 'dialog'; + fixture.detectChanges(); + expect(inputGroupEl.classList.contains('igx-input-group--readonly')).toBeFalse(); + expect(inputEl.readOnly).toBeTrue(); + + fixture.componentInstance.datePicker.readOnly = true; + fixture.detectChanges(); + expect(inputGroupEl.classList.contains('igx-input-group--readonly')).toBeTrue(); + expect(inputEl.readOnly).toBeTrue(); + }); +}); + +@Component({ + template: ``, + imports: [IgxDatePickerComponent, IgxReadOnlyInputDirective] +}) +class TestComponent { + @ViewChild(IgxDatePickerComponent, { static: true }) + public datePicker!: IgxDatePickerComponent; +} diff --git a/projects/igniteui-angular/src/lib/directives/input/read-only-input.directive.ts b/projects/igniteui-angular/src/lib/directives/input/read-only-input.directive.ts new file mode 100644 index 00000000000..e3163465608 --- /dev/null +++ b/projects/igniteui-angular/src/lib/directives/input/read-only-input.directive.ts @@ -0,0 +1,26 @@ +import { Directive, effect, inject, input } from '@angular/core'; +import { IgxInputGroupComponent } from '../../input-group/input-group.component'; + +@Directive({ + selector: '[igxReadOnlyInput]', + exportAs: 'igxReadOnlyInput', + standalone: true +}) +export class IgxReadOnlyInputDirective { + public igxReadOnlyInput = input(false); + + private _inputGroup: IgxInputGroupComponent | null = inject( + IgxInputGroupComponent, + { + optional: true + } + ); + + constructor() { + effect(() => { + if (this._inputGroup) { + this._inputGroup.readOnly = this.igxReadOnlyInput(); + } + }); + } +} diff --git a/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.html b/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.html index 48bf1c5c0a1..ce397fa1313 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.html @@ -60,7 +60,6 @@ - Angular Date Picker