diff --git a/commitlint.config.js b/commitlint.config.js index 86f5acd8a99..8d89acd6611 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -6,9 +6,9 @@ const Configuration = { 'always', ['core', 'platform', 'docs', 'e2e', 'release', 'deps', 'deps-dev', 'changelog', 'ci', 'cx', 'cdk', 'shared'] ], - 'body-max-line-length': [2, 'always', 200], - 'footer-max-line-length': [2, 'always', 200], - 'header-max-length': [2, 'always', 200] + 'body-max-line-length': [2, 'always', 400], + 'footer-max-line-length': [2, 'always', 400], + 'header-max-length': [2, 'always', 400] } }; diff --git a/libs/cdk/src/lib/utils/components/dynamic-portal/dynamic-portal.component.ts b/libs/cdk/src/lib/utils/components/dynamic-portal/dynamic-portal.component.ts index 7a260ba0624..8c35186ffca 100644 --- a/libs/cdk/src/lib/utils/components/dynamic-portal/dynamic-portal.component.ts +++ b/libs/cdk/src/lib/utils/components/dynamic-portal/dynamic-portal.component.ts @@ -1,15 +1,30 @@ import { + AfterViewInit, Component, + ComponentRef, + DestroyRef, ElementRef, + EmbeddedViewRef, + EventEmitter, inject, Input, - OnChanges, - SimpleChanges, + Output, + Renderer2, TemplateRef, + ViewChild, ViewContainerRef } from '@angular/core'; -import { ComponentPortal, ComponentType, DomPortal, Portal, PortalModule, TemplatePortal } from '@angular/cdk/portal'; +import { + CdkPortalOutlet, + ComponentPortal, + ComponentType, + DomPortal, + PortalModule, + TemplatePortal +} from '@angular/cdk/portal'; import { coerceElement } from '@angular/cdk/coercion'; +import { BehaviorSubject, filter, map, tap } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; /** * A component that can be used to attach a portal to a DOM element, without explicitly creating portal instances on place. @@ -20,35 +35,88 @@ import { coerceElement } from '@angular/cdk/coercion'; selector: 'fdk-dynamic-portal', standalone: true, imports: [PortalModule], - template: ` ` + template: ` ` }) -export class DynamicPortalComponent implements OnChanges { +export class DynamicPortalComponent implements AfterViewInit { /** The DOM element to attach */ - @Input() domElement: HTMLElement | ElementRef; + @Input() + set domElement(value: HTMLElement | ElementRef | string) { + this.portalContent$.next(value); + } /** The component to attach */ @Input() - component: ComponentType; + set component(value: ComponentType) { + this.portalContent$.next(value); + } /** The template to attach */ @Input() - template: TemplateRef; + set template(value: TemplateRef) { + this.portalContent$.next(value); + } + + /** The Content which should be attached and can be any of the items */ + @Input() + set auto(value: DynamicPortalComponent['domElement' | 'component' | 'template']) { + this.portalContent$.next(value); + } + + /** Emits when the portal is attached */ + @Output() + attached = new EventEmitter | EmbeddedViewRef | Element>(); - /** Portal associated with the Portal outlet. */ - portal?: Portal; + /** @hidden */ + @ViewChild(CdkPortalOutlet) + portalOutlet?: CdkPortalOutlet; + + /** @hidden */ + private portalContent$ = new BehaviorSubject< + DynamicPortalComponent['domElement' | 'component' | 'template'] | undefined + >(undefined); + + /** @hidden */ + private readonly _destroyRef = inject(DestroyRef); /** @hidden */ private viewContainerRef = inject(ViewContainerRef); /** @hidden */ - ngOnChanges(changes: SimpleChanges): void { - this.portal?.detach(); - if (changes['domElement']) { - this.portal = new DomPortal(coerceElement(this.domElement)); - } else if (changes['component']) { - this.portal = new ComponentPortal(this.component); - } else if (changes['template']) { - this.portal = new TemplatePortal(this.template, this.viewContainerRef); - } + private renderer = inject(Renderer2); + + /** @hidden */ + private elementRef = inject(ElementRef); + + /** @hidden */ + ngAfterViewInit(): void { + const portalOutlet = this.portalOutlet as CdkPortalOutlet; + this.portalContent$ + .pipe( + tap(() => portalOutlet.detach()), + filter(Boolean), + map((content) => { + if (typeof content === 'string') { + const textElement = this.renderer.createText(content); + this.renderer.appendChild(this.elementRef.nativeElement, textElement); + return new DomPortal(textElement); + } else if (content instanceof HTMLElement || content instanceof ElementRef) { + return new DomPortal(coerceElement(content as HTMLElement | ElementRef)); + } else if (content instanceof TemplateRef) { + return new TemplatePortal(content, this.viewContainerRef); + } + return new ComponentPortal(content); + }), + filter(Boolean), + takeUntilDestroyed(this._destroyRef) + ) + .subscribe((portal) => { + const ref = portalOutlet.attach(portal); + if (portal instanceof DomPortal) { + // DomPortal Attachment does not refer return Ref, like other portals + this.attached.emit(portal.element); + } else { + this.attached.emit(ref); + } + }); } } diff --git a/libs/cdk/src/lib/utils/directives/focusable-item/focusable-item.directive.ts b/libs/cdk/src/lib/utils/directives/focusable-item/focusable-item.directive.ts index bd05d9fd5c7..e9a5a03fe5b 100644 --- a/libs/cdk/src/lib/utils/directives/focusable-item/focusable-item.directive.ts +++ b/libs/cdk/src/lib/utils/directives/focusable-item/focusable-item.directive.ts @@ -61,6 +61,9 @@ export class FocusableItemDirective implements FocusableItem { @Output() readonly cellFocused = new EventEmitter(); + /** Element reference. */ + readonly elementRef = inject>(ElementRef); + /** @hidden */ readonly keydown = new Subject(); @@ -73,8 +76,6 @@ export class FocusableItemDirective implements FocusableItem { return this._tabbable ? 0 : -1; } - /** Element reference. */ - readonly elementRef = inject>(ElementRef); /** @hidden */ protected readonly _destroyRef = inject(DestroyRef); /** @hidden */ @@ -88,7 +89,6 @@ export class FocusableItemDirective implements FocusableItem { /** @hidden */ private _tabbable = true; - /** @hidden */ private _timerId: ReturnType | null; /** @hidden */ diff --git a/libs/core/src/lib/busy-indicator/busy-indicator-extended/busy-indicator-extended.directive.spec.ts b/libs/core/src/lib/busy-indicator/busy-indicator-extended/busy-indicator-extended.directive.spec.ts index 3e1dc538be9..cb8e196a56c 100644 --- a/libs/core/src/lib/busy-indicator/busy-indicator-extended/busy-indicator-extended.directive.spec.ts +++ b/libs/core/src/lib/busy-indicator/busy-indicator-extended/busy-indicator-extended.directive.spec.ts @@ -1,6 +1,7 @@ import { Component, ElementRef, TemplateRef, ViewChild } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { BusyIndicatorModule } from '../busy-indicator.module'; +import { BusyIndicatorExtendedDirective } from './busy-indicator-extended.directive'; +import { BusyIndicatorComponent } from '../busy-indicator.component'; @Component({ template: `
@@ -21,7 +22,7 @@ describe('BusyIndicatorExtendedDirective', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [TestComponent], - imports: [BusyIndicatorModule] + imports: [BusyIndicatorExtendedDirective, BusyIndicatorComponent] }).compileComponents(); })); diff --git a/libs/core/src/lib/busy-indicator/busy-indicator-extended/busy-indicator-extended.directive.ts b/libs/core/src/lib/busy-indicator/busy-indicator-extended/busy-indicator-extended.directive.ts index 6bbd1493dab..8e1d8696c8e 100644 --- a/libs/core/src/lib/busy-indicator/busy-indicator-extended/busy-indicator-extended.directive.ts +++ b/libs/core/src/lib/busy-indicator/busy-indicator-extended/busy-indicator-extended.directive.ts @@ -6,7 +6,8 @@ const messageToastClass = 'fd-message-toast'; @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector - selector: '[fd-busy-indicator-extended]' + selector: '[fd-busy-indicator-extended]', + standalone: true }) export class BusyIndicatorExtendedDirective implements AfterContentInit { /** @hidden */ diff --git a/libs/core/src/lib/busy-indicator/busy-indicator.component.spec.ts b/libs/core/src/lib/busy-indicator/busy-indicator.component.spec.ts index fa0161dc5de..55910294f61 100644 --- a/libs/core/src/lib/busy-indicator/busy-indicator.component.spec.ts +++ b/libs/core/src/lib/busy-indicator/busy-indicator.component.spec.ts @@ -1,13 +1,16 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { NgIf } from '@angular/common'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { BusyIndicatorComponent } from './busy-indicator.component'; -import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ template: ` - ` + `, + standalone: true, + imports: [NgIf, BusyIndicatorComponent] }) class TestWrapperComponent { block = true; @@ -22,7 +25,7 @@ describe('BusyIndicatorComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - declarations: [TestWrapperComponent, BusyIndicatorComponent] + imports: [TestWrapperComponent] }) .overrideComponent(BusyIndicatorComponent, { set: { changeDetection: ChangeDetectionStrategy.Default } diff --git a/libs/core/src/lib/busy-indicator/busy-indicator.component.ts b/libs/core/src/lib/busy-indicator/busy-indicator.component.ts index f294049d07a..adc7e864860 100644 --- a/libs/core/src/lib/busy-indicator/busy-indicator.component.ts +++ b/libs/core/src/lib/busy-indicator/busy-indicator.component.ts @@ -11,6 +11,7 @@ import { KeyUtil } from '@fundamental-ngx/cdk/utils'; import { Nullable } from '@fundamental-ngx/cdk/utils'; import { TAB } from '@angular/cdk/keycodes'; import { FD_BUSY_INDICATOR_COMPONENT } from './tokens'; +import { NgIf } from '@angular/common'; export type BusyIndicatorSize = 's' | 'm' | 'l'; @@ -35,7 +36,9 @@ export type BusyIndicatorSize = 's' | 'm' | 'l'; '[attr.title]': 'title', '[class.fd-busy-indicator__container]': 'true', '[class.fd-busy-indicator__container--inline]': '!block' - } + }, + standalone: true, + imports: [NgIf] }) export class BusyIndicatorComponent { /** Whether to display the loading indicator animation. */ @@ -60,7 +63,7 @@ export class BusyIndicatorComponent { /** add loading label value */ @Input() - label: string; + label?: string; /** Aria live attribute value. */ @Input() diff --git a/libs/core/src/lib/busy-indicator/busy-indicator.module.ts b/libs/core/src/lib/busy-indicator/busy-indicator.module.ts index 42e2d04851d..a881aca3e2d 100644 --- a/libs/core/src/lib/busy-indicator/busy-indicator.module.ts +++ b/libs/core/src/lib/busy-indicator/busy-indicator.module.ts @@ -4,8 +4,7 @@ import { BusyIndicatorComponent } from './busy-indicator.component'; import { BusyIndicatorExtendedDirective } from './busy-indicator-extended/busy-indicator-extended.directive'; @NgModule({ - declarations: [BusyIndicatorComponent, BusyIndicatorExtendedDirective], exports: [BusyIndicatorComponent, BusyIndicatorExtendedDirective], - imports: [CommonModule] + imports: [CommonModule, BusyIndicatorComponent, BusyIndicatorExtendedDirective] }) export class BusyIndicatorModule {} diff --git a/libs/core/src/lib/card/card-footer.component.html b/libs/core/src/lib/card/card-footer.component.html index 8f14f982a3f..675d6faaee5 100644 --- a/libs/core/src/lib/card/card-footer.component.html +++ b/libs/core/src/lib/card/card-footer.component.html @@ -1,6 +1,6 @@ diff --git a/libs/core/src/lib/carousel/carousel-item.directive.ts b/libs/core/src/lib/carousel/carousel-item.directive.ts index d395a5ebcff..862078f6a63 100644 --- a/libs/core/src/lib/carousel/carousel-item.directive.ts +++ b/libs/core/src/lib/carousel/carousel-item.directive.ts @@ -4,7 +4,8 @@ let carouselItemCounter = 0; @Directive({ selector: '[fd-carousel-item], [fdCarouselItem]', - exportAs: 'fdCarouselItem' + exportAs: 'fdCarouselItem', + standalone: true }) export class CarouselItemDirective { /** Value of the item , to keep some information inside */ diff --git a/libs/core/src/lib/carousel/carousel-item/carousel-item.component.ts b/libs/core/src/lib/carousel/carousel-item/carousel-item.component.ts index 66e49860f85..9bb90170f2b 100644 --- a/libs/core/src/lib/carousel/carousel-item/carousel-item.component.ts +++ b/libs/core/src/lib/carousel/carousel-item/carousel-item.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, ElementRef, HostBinding, Input, ViewEncapsulation } from '@angular/core'; import { CarouselItemInterface } from '../carousel.service'; import { Nullable } from '@fundamental-ngx/cdk/utils'; +import { BusyIndicatorComponent } from '@fundamental-ngx/core/busy-indicator'; export type Visibility = 'visible' | 'hidden'; @@ -10,7 +11,9 @@ let carouselItemCounter = 0; selector: 'fd-carousel-item', templateUrl: './carousel-item.component.html', changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None + encapsulation: ViewEncapsulation.None, + standalone: true, + imports: [BusyIndicatorComponent] }) export class CarouselItemComponent implements CarouselItemInterface { /** Id of the Carousel items. */ diff --git a/libs/core/src/lib/carousel/carousel.component.html b/libs/core/src/lib/carousel/carousel.component.html index 795bbdbcc60..6bf9995e501 100644 --- a/libs/core/src/lib/carousel/carousel.component.html +++ b/libs/core/src/lib/carousel/carousel.component.html @@ -17,6 +17,7 @@ - diff --git a/libs/core/src/lib/slider/slider.component.spec.ts b/libs/core/src/lib/slider/slider.component.spec.ts index 261b81ad2e8..0d00ed050c9 100644 --- a/libs/core/src/lib/slider/slider.component.spec.ts +++ b/libs/core/src/lib/slider/slider.component.spec.ts @@ -4,7 +4,11 @@ import { FormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { whenStable } from '@fundamental-ngx/core/tests'; -import { ContentDensityMode, mockedLocalContentDensityDirective } from '@fundamental-ngx/core/content-density'; +import { + ContentDensityMode, + ContentDensityModule, + mockedLocalContentDensityDirective +} from '@fundamental-ngx/core/content-density'; import { SliderComponent } from './slider.component'; import { SliderModule } from './slider.module'; @@ -79,7 +83,7 @@ class TestSliderComponent { } /** TODO: #6317 */ -xdescribe('SliderComponent', () => { +describe('SliderComponent', () => { let component: TestSliderComponent; let fixture: ComponentFixture; let sliders: SliderComponent[]; @@ -88,7 +92,7 @@ xdescribe('SliderComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [TestSliderComponent], - imports: [SliderModule, FormsModule], + imports: [SliderModule, FormsModule, ContentDensityModule], providers: [contentDensityDirectiveProvider] }).compileComponents(); })); @@ -100,7 +104,7 @@ xdescribe('SliderComponent', () => { await whenStable(fixture); sliders = component.sliders.toArray(); - bodyClientWidth = fixture.nativeElement.ownerDocument.body.clientWidth; + bodyClientWidth = 100; }); it('should create', () => { @@ -112,7 +116,53 @@ xdescribe('SliderComponent', () => { fixture.detectChanges(); expect(fixture.nativeElement.querySelectorAll('.fd-slider--lg').length).toBe(sliders.length); setContentDensity(ContentDensityMode.COMPACT); - expect(fixture.nativeElement.querySelectorAll('.fd-slider--lg').length).toBe(0); + expect(fixture.nativeElement.querySelectorAll('.fd-slider--lg').length).toBe(1); + }); + + it('should consume vertical state', () => { + const slider = component.sliders.get(0)!; + slider.vertical = true; + slider.buildComponentCssClass(); + expect(slider?.elementRef.nativeElement.classList).toContain('fd-slider--vertical'); + }); + + it('should move range when dragging range group', () => { + const slider = sliders[3]; + + const event = new MouseEvent('mousedown'); + + jest.spyOn(slider.rangeHandle1.nativeElement as any, 'getBoundingClientRect').mockImplementation(() => ({ + height: 20, + width: 40, + x: 0, + y: 0, + bottom: 0, + left: 0, + right: 0, + top: 0 + })); + + jest.spyOn(slider.trackEl.nativeElement as any, 'getBoundingClientRect').mockImplementation(() => ({ + height: 2, + width: bodyClientWidth, + x: 0, + y: 0, + bottom: 0, + left: 0, + right: 0, + top: 0 + })); + + slider.onHandleClick(event, true); + + const mousemove = new MouseEvent('mousemove', { clientX: getPixelsByPercentage(bodyClientWidth, 20) }); + fixture.nativeElement.ownerDocument.dispatchEvent(mousemove); + + const upEvent = new MouseEvent('mouseup'); + + fixture.nativeElement.ownerDocument.dispatchEvent(upEvent); + + expect(component.value4).toEqual([40, 90]); }); it('handle should be on the center of slider', () => { @@ -125,6 +175,17 @@ xdescribe('SliderComponent', () => { const slider = sliders[0]; const event = new MouseEvent('mousedown'); + jest.spyOn(slider.trackEl.nativeElement as any, 'getBoundingClientRect').mockImplementation(() => ({ + height: 2, + width: bodyClientWidth, + x: 0, + y: 0, + bottom: 0, + left: 0, + right: 0, + top: 0 + })); + slider.onHandleClick(event); const mousemove = new MouseEvent('mousemove', { clientX: getPixelsByPercentage(bodyClientWidth, 10) }); @@ -158,6 +219,18 @@ xdescribe('SliderComponent', () => { }); it('should display 11 ticks marks and 11 Labels', () => { + jest.spyOn(sliders[0].trackEl.nativeElement as any, 'getBoundingClientRect').mockImplementation(() => ({ + height: 2, + width: bodyClientWidth, + x: 0, + y: 0, + bottom: 0, + left: 0, + right: 0, + top: 0 + })); + (sliders[0] as any)._onResize(); + fixture.detectChanges(); const ticksMarks = fixture.debugElement.queryAll(By.css('.example-1 .fd-slider__tick')); const labels = fixture.debugElement.queryAll(By.css('.example-1 .fd-slider__label')); @@ -166,6 +239,18 @@ xdescribe('SliderComponent', () => { }); it('should display 11 ticks marks and 6 Labels', () => { + jest.spyOn(sliders[1].trackEl.nativeElement as any, 'getBoundingClientRect').mockImplementation(() => ({ + height: 2, + width: bodyClientWidth, + x: 0, + y: 0, + bottom: 0, + left: 0, + right: 0, + top: 0 + })); + (sliders[1] as any)._onResize(); + fixture.detectChanges(); const ticksMarks = fixture.debugElement.queryAll(By.css('.example-2 .fd-slider__tick')); const labels = fixture.debugElement.queryAll(By.css('.example-2 .fd-slider__label')); @@ -177,7 +262,7 @@ xdescribe('SliderComponent', () => { const labels = fixture.debugElement.queryAll(By.css('.example-3 .fd-slider__label')); expect(labels.length).toEqual(component.customValues.length); - expect(labels[0].nativeElement.innerHTML).toEqual(component.customValues[0].label); + expect(labels[0].nativeElement.innerHTML.trim()).toEqual(component.customValues[0].label); }); it('range slider should display 2 handles', async () => { @@ -195,6 +280,17 @@ xdescribe('SliderComponent', () => { const event = { target: slider.rangeHandle2.nativeElement } as any; const valueOfFirstHandleBeforeMoving = slider.value[0]; + jest.spyOn(slider.trackEl.nativeElement as any, 'getBoundingClientRect').mockImplementation(() => ({ + height: 2, + width: bodyClientWidth, + x: 0, + y: 0, + bottom: 0, + left: 0, + right: 0, + top: 0 + })); + slider.onHandleClick(event); const mousemove = new MouseEvent('mousemove', { clientX: getPixelsByPercentage(bodyClientWidth, 11) }); diff --git a/libs/core/src/lib/slider/slider.component.ts b/libs/core/src/lib/slider/slider.component.ts index 68499e2a6c5..b7432c4d046 100644 --- a/libs/core/src/lib/slider/slider.component.ts +++ b/libs/core/src/lib/slider/slider.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/member-ordering */ import { AfterViewInit, ChangeDetectionStrategy, @@ -17,7 +18,7 @@ import { ViewChildren, ViewEncapsulation } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; import { DOWN_ARROW, ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE, UP_ARROW } from '@angular/cdk/keycodes'; import { Platform } from '@angular/cdk/platform'; import { coerceNumberProperty } from '@angular/cdk/coercion'; @@ -34,8 +35,9 @@ import { take, takeUntil } from 'rxjs/operators'; -import { PopoverComponent } from '@fundamental-ngx/core/popover'; -import { Nullable } from '@fundamental-ngx/cdk/utils'; + +import { PopoverComponent, PopoverModule } from '@fundamental-ngx/core/popover'; +import { Nullable, OnlyDigitsDirective } from '@fundamental-ngx/cdk/utils'; import { SliderControlValue, SliderCustomValue, @@ -51,6 +53,9 @@ import { ContentDensityObserver, contentDensityObserverProviders } from '@fundamental-ngx/core/content-density'; +import { SliderPositionDirective } from './slider-position.directive'; +import { NgTemplateOutlet, NgIf, NgFor, AsyncPipe } from '@angular/common'; +import { FdTranslatePipe } from '@fundamental-ngx/i18n'; export const SLIDER_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, @@ -78,7 +83,19 @@ let sliderId = 0; '(mouseenter)': 'this._componentHovered$.next(true)', '(mouseleave)': 'this._componentHovered$.next(false)', '(focusout)': 'onTouched()' - } + }, + standalone: true, + imports: [ + NgTemplateOutlet, + NgIf, + NgFor, + SliderPositionDirective, + PopoverModule, + FormsModule, + OnlyDigitsDirective, + AsyncPipe, + FdTranslatePipe + ] }) export class SliderComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy, ControlValueAccessor, CssClassBuilder, FormItemControl @@ -189,6 +206,12 @@ export class SliderComponent @Input() disabled = false; + /** + * Whether slider should have vertical alignment. + */ + @Input() + vertical = false; + /** @hidden */ _position: number | number[] = 0; @@ -207,9 +230,7 @@ export class SliderComponent } /** @hidden */ - @ViewChild('track', { - read: ElementRef - }) + @ViewChild('track', { read: ElementRef, static: true }) trackEl: ElementRef; /** @hidden */ @@ -230,6 +251,10 @@ export class SliderComponent }) rangeHandle2: ElementRef; + /** @hidden */ + @ViewChild('rangeGroupHandle', { read: ElementRef, static: false }) + _rangeGroupHandle: ElementRef; + /** @hidden */ @ViewChildren(PopoverComponent) _popovers: QueryList; @@ -281,6 +306,9 @@ export class SliderComponent */ _useSliderValuePrefix = true; + /** @hidden */ + _handles = SliderRangeHandles; + /** @hidden */ private _min = 0; @@ -337,6 +365,25 @@ export class SliderComponent _contentDensityObserver.subscribe(); } + /** + * @hidden + * CssClassBuilder interface implementation + * function must return single string + * function is responsible for order which css classes are applied + */ + @applyCssClass + buildComponentCssClass(): string[] { + return [ + 'fd-slider', + this.disabled ? 'is-disabled' : '', + this._isRange ? 'fd-slider--range' : '', + this.showTicks && this.showTicksLabels ? 'fd-slider--with-labels' : '', + this.vertical ? 'fd-slider--vertical' : '', + this.class, + this._platform.EDGE || this._platform.TRIDENT ? 'fd-slider__alternative-tick-width' : '' + ]; + } + /** @hidden */ ngOnInit(): void { this._subscribeToRtl(); @@ -368,11 +415,6 @@ export class SliderComponent this._onDestroy$.complete(); } - /** @hidden */ - private isRtl(): boolean { - return this._rtlService?.rtl.getValue(); - } - /** @hidden */ getValuenow(position: number | number[], sliderValueTarget: SliderValueTargets): string | number { return this.customValues.length > 0 @@ -390,23 +432,6 @@ export class SliderComponent return this.customValues.length > 0 ? this.customValues[this.max as number].label : this.max; } - /** - * @hidden - * CssClassBuilder interface implementation - * function must return single string - * function is responsible for order which css classes are applied - */ - @applyCssClass - buildComponentCssClass(): string[] { - return [ - 'fd-slider', - this.disabled ? 'is-disabled' : '', - this._isRange ? 'fd-slider--range' : '', - this.showTicks && this.showTicksLabels ? 'fd-slider--with-labels' : '', - this.class, - this._platform.EDGE || this._platform.TRIDENT ? 'fd-slider__alternative-tick-width' : '' - ]; - } /** @hidden */ onChange: (value: SliderControlValue) => void = () => {}; @@ -444,25 +469,57 @@ export class SliderComponent } /** @hidden */ - onHandleClick(event: MouseEvent): void { + onHandleClick(event: MouseEvent, group = false): void { if (this.disabled) { return; } - const unsubscribeFromMousemove = this._renderer.listen('document', 'mousemove', (moveEvent) => { + + let firstHandleCoords: { x: number; y: number }; + let rangeSliderStartCoords: { x: number; y: number }; + + if (group) { + rangeSliderStartCoords = { + x: event.clientX, + y: event.clientY + }; + + const firstHandleRect = this.rangeHandle1.nativeElement.getBoundingClientRect(); + + firstHandleCoords = { + x: firstHandleRect.x + firstHandleRect.width / 2, + y: firstHandleRect.y + firstHandleRect.height / 2 + }; + } + + const unsubscribeFromMousemove = this._renderer.listen('document', 'mousemove', (moveEvent: MouseEvent) => { this._updatePopoversPosition(); if (!this._isRange) { this._setValue(this._calculateValueFromPointerPosition(moveEvent)); return; } + + let coords = { + clientX: moveEvent.clientX, + clientY: moveEvent.clientY + }; + let handleIndex: SliderRangeHandles; - if (event.target === this.rangeHandle1.nativeElement) { + if (group) { + handleIndex = SliderRangeHandles.Both; + // Mimic dragging first handle to calculate the difference between old and new values. + coords = { + clientX: firstHandleCoords!.x + (moveEvent.clientX - rangeSliderStartCoords!.x), + clientY: firstHandleCoords!.y + (moveEvent.clientY - rangeSliderStartCoords!.y) + }; + } else if (event.target === this.rangeHandle1.nativeElement) { handleIndex = SliderRangeHandles.First; } else if (event.target === this.rangeHandle2.nativeElement) { handleIndex = SliderRangeHandles.Second; } else { return; } - const value = this._calculateValueFromPointerPosition(moveEvent, false) as number; + + const value = this._calculateValueFromPointerPosition(coords, false) as number; this._setRangeHandleValueAndPosition(handleIndex, value); this._setValue(this._constructRangeModelValue()); }); @@ -473,7 +530,7 @@ export class SliderComponent } /** @hidden */ - onKeyDown(event: KeyboardEvent): void { + onKeyDown(event: KeyboardEvent, handle?: SliderRangeHandles): void { if (this.disabled) { return; } @@ -482,44 +539,35 @@ export class SliderComponent return; } event.preventDefault(); - const diff = event.shiftKey ? this.jump : this.step; - let newValue: number | SliderTickMark | null = null; - let prevValue = this._position as number; - let handleIndex: SliderRangeHandles | undefined; - if (this._isRange) { - if (event.target === this.rangeHandle1.nativeElement) { - prevValue = this._handle1Value; - handleIndex = SliderRangeHandles.First; - } else if (event.target === this.rangeHandle2.nativeElement) { - prevValue = this._handle2Value; - handleIndex = SliderRangeHandles.Second; - } - } - if (this.isRtl()) { - if (KeyUtil.isKeyCode(event, LEFT_ARROW) || KeyUtil.isKeyCode(event, UP_ARROW)) { - newValue = prevValue + diff; - } - if (KeyUtil.isKeyCode(event, RIGHT_ARROW) || KeyUtil.isKeyCode(event, DOWN_ARROW)) { - newValue = prevValue - diff; - } - } else { - if (KeyUtil.isKeyCode(event, LEFT_ARROW) || KeyUtil.isKeyCode(event, DOWN_ARROW)) { - newValue = prevValue - diff; - } - if (KeyUtil.isKeyCode(event, RIGHT_ARROW) || KeyUtil.isKeyCode(event, UP_ARROW)) { - newValue = prevValue + diff; - } - } - if (newValue === null) { + const upActionKey = KeyUtil.isKeyCode(event, [UP_ARROW, this._isRtl ? LEFT_ARROW : RIGHT_ARROW]); + const downActionKey = KeyUtil.isKeyCode(event, [DOWN_ARROW, this._isRtl ? RIGHT_ARROW : LEFT_ARROW]); + + if (!upActionKey && !downActionKey) { return; } - newValue = this._processNewValue(newValue as number, !this._isRange); + + const diff = event.shiftKey ? this.jump : this.step; + let newValue: number | SliderTickMark | null = null; if (!this._isRange) { + newValue = (this._position as number) + diff * (upActionKey ? 1 : -1); + + if (newValue === null) { + return; + } + + newValue = this._processNewValue(newValue as number, !this._isRange); + this._setValue(newValue); - } else if (handleIndex) { - this._setRangeHandleValueAndPosition(handleIndex, newValue as number); - this._setValue(this._constructRangeModelValue()); + this._updatePopoversPosition(); + return; } + + this._handleRangeKeydown( + handle === SliderRangeHandles.Both ? [SliderRangeHandles.First, SliderRangeHandles.Second] : handle!, + diff, + upActionKey + ); + this._updatePopoversPosition(); } @@ -544,6 +592,36 @@ export class SliderComponent this._updatePopoversPosition(); } + /** @hidden */ + private _handleRangeKeydown(handles: SliderRangeHandles | SliderRangeHandles[], diff: number, up: boolean): void { + handles = Array.isArray(handles) ? handles : [handles]; + const valueMap = new Map< + SliderRangeHandles, + { prev: number | SliderTickMark | null; current: number | SliderTickMark | null } + >(); + handles.forEach((handle) => { + const prevValue = handle === SliderRangeHandles.First ? this._handle1Value : this._handle2Value; + let newValue: number | SliderTickMark | null = prevValue + diff * (up ? 1 : -1); + if (newValue === null) { + return; + } + + newValue = this._processNewValue(newValue as number, !this._isRange); + + valueMap.set(handle, { prev: prevValue, current: newValue }); + }); + + if (Array.from(valueMap.values()).some((entry) => entry.current === entry.prev)) { + return; + } + + valueMap.forEach((entry, handle) => { + this._setRangeHandleValueAndPosition(handle, entry.current as number); + }); + + this._setValue(this._constructRangeModelValue()); + } + /** @hidden */ private _setValue(value: SliderControlValue, emitEvent = true): void { if (this._isRange) { @@ -565,9 +643,13 @@ export class SliderComponent } /** @hidden */ - private _calculateValueFromPointerPosition(event: MouseEvent, takeCustomValue = true): number | SliderTickMark { - const { left, width } = this.trackEl.nativeElement.getBoundingClientRect(); - let percentage = (event.clientX - left) / width; + private _calculateValueFromPointerPosition( + coords: { clientY: number; clientX: number }, + takeCustomValue = true + ): number | SliderTickMark { + const { left, width, bottom, height } = this.trackEl.nativeElement.getBoundingClientRect(); + let percentage = this.vertical ? (bottom - coords.clientY) / height : (coords.clientX - left) / width; + if (this._isRtl) { percentage = 1 - percentage; } @@ -604,10 +686,25 @@ export class SliderComponent if (handleIndex === SliderRangeHandles.First) { this._handle1Value = value; this._handle1Position = position; - } - if (handleIndex === SliderRangeHandles.Second) { + } else if (handleIndex === SliderRangeHandles.Second) { this._handle2Value = value; this._handle2Position = position; + } else if (handleIndex === SliderRangeHandles.Both) { + // Calculate how much steps being skipped. + const oldValueIndex = this._valuesBySteps.indexOf(this._handle1Value); + const newValueIndex = this._valuesBySteps.indexOf(value); + const diff = oldValueIndex - newValueIndex; + const handle2ValueIndex = this._valuesBySteps.indexOf(this._handle2Value); + + if (diff === 0 || this._valuesBySteps.length <= handle2ValueIndex - diff) { + return; + } + + const positionShift = position - this._handle1Position; + this._handle1Position = position; + this._handle2Position = this._handle2Position + positionShift; + this._handle1Value = value; + this._handle2Value = this._valuesBySteps[handle2ValueIndex - diff]; } this._rangeProgress = Math.abs(this._handle2Position - this._handle1Position); } @@ -715,7 +812,10 @@ export class SliderComponent if (!this.trackEl || !this.trackEl.nativeElement) { return; } - return Math.floor(this.trackEl.nativeElement.getBoundingClientRect().width / MIN_DISTANCE_BETWEEN_TICKS); + + const { width, height } = this.trackEl.nativeElement.getBoundingClientRect(); + + return Math.floor((this.vertical ? height : width) / MIN_DISTANCE_BETWEEN_TICKS); } /** @hidden */ diff --git a/libs/core/src/lib/slider/slider.model.ts b/libs/core/src/lib/slider/slider.model.ts index 84ed73051b9..30f1668f635 100644 --- a/libs/core/src/lib/slider/slider.model.ts +++ b/libs/core/src/lib/slider/slider.model.ts @@ -6,7 +6,8 @@ export enum SliderValueTargets { export enum SliderRangeHandles { First, - Second + Second, + Both } export interface SliderTickMark { diff --git a/libs/core/src/lib/slider/slider.module.ts b/libs/core/src/lib/slider/slider.module.ts index b4b840acc27..d9ea123c4b0 100644 --- a/libs/core/src/lib/slider/slider.module.ts +++ b/libs/core/src/lib/slider/slider.module.ts @@ -9,9 +9,10 @@ import { OnlyDigitsModule } from '@fundamental-ngx/cdk/utils'; import { I18nModule } from '@fundamental-ngx/i18n'; import { ContentDensityModule } from '@fundamental-ngx/core/content-density'; +const EXPORTS = [SliderComponent, SliderPositionDirective]; + @NgModule({ - declarations: [SliderComponent, SliderPositionDirective], - imports: [CommonModule, PopoverModule, FormsModule, OnlyDigitsModule, ContentDensityModule, I18nModule], - exports: [SliderComponent, ContentDensityModule] + imports: [CommonModule, PopoverModule, FormsModule, OnlyDigitsModule, ContentDensityModule, I18nModule, ...EXPORTS], + exports: [...EXPORTS] }) export class SliderModule {} diff --git a/libs/core/src/lib/table/table.component.scss b/libs/core/src/lib/table/table.component.scss index bbe703fd920..a3bafaea549 100644 --- a/libs/core/src/lib/table/table.component.scss +++ b/libs/core/src/lib/table/table.component.scss @@ -12,8 +12,3 @@ margin: 0; } } - -// TODO: remove hardcoded style -.fd-table__expand { - min-width: var(--fdTable_Header_Cell_Height) !important; -} diff --git a/libs/core/src/lib/text/text.component.spec.ts b/libs/core/src/lib/text/text.component.spec.ts index 8dc46837d0e..d2d6a7af808 100644 --- a/libs/core/src/lib/text/text.component.spec.ts +++ b/libs/core/src/lib/text/text.component.spec.ts @@ -1,7 +1,6 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { TextComponent } from './text.component'; -import { TextModule } from './text.module'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { patchLanguage } from '@fundamental-ngx/i18n'; @@ -14,7 +13,7 @@ describe('TextComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [TextModule], + imports: [TextComponent], schemas: [NO_ERRORS_SCHEMA] }) .overrideComponent(TextComponent, { diff --git a/libs/core/src/lib/text/text.component.ts b/libs/core/src/lib/text/text.component.ts index bb92372e3a2..1dad6114bb8 100644 --- a/libs/core/src/lib/text/text.component.ts +++ b/libs/core/src/lib/text/text.component.ts @@ -7,7 +7,10 @@ import { Output, ViewEncapsulation } from '@angular/core'; -import { Nullable } from '@fundamental-ngx/cdk/utils'; +import { LineClampDirective, LineClampTargetDirective, Nullable } from '@fundamental-ngx/cdk/utils'; +import { LinkComponent } from '@fundamental-ngx/core/link'; +import { FdTranslatePipe } from '@fundamental-ngx/i18n'; +import { NgIf } from '@angular/common'; /** Type of hyphenation */ export type HyphenationType = 'none' | 'manual' | 'auto' | null; @@ -17,7 +20,9 @@ export type HyphenationType = 'none' | 'manual' | 'auto' | null; templateUrl: './text.component.html', styleUrls: ['./text.component.scss'], encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [LineClampDirective, LineClampTargetDirective, LinkComponent, FdTranslatePipe, NgIf], + standalone: true }) export class TextComponent { /** diff --git a/libs/core/src/lib/text/text.module.ts b/libs/core/src/lib/text/text.module.ts index 49a3946d14f..0714a317d1b 100644 --- a/libs/core/src/lib/text/text.module.ts +++ b/libs/core/src/lib/text/text.module.ts @@ -1,14 +1,8 @@ import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - import { TextComponent } from './text.component'; -import { LinkModule } from '@fundamental-ngx/core/link'; -import { LineClampModule } from '@fundamental-ngx/cdk/utils'; -import { I18nModule } from '@fundamental-ngx/i18n'; @NgModule({ - declarations: [TextComponent], - imports: [CommonModule, LinkModule, LineClampModule, I18nModule], + imports: [TextComponent], exports: [TextComponent] }) export class TextModule {} diff --git a/libs/core/src/lib/title/title.component.spec.ts b/libs/core/src/lib/title/title.component.spec.ts index 868ba10a60e..300ea77ceff 100644 --- a/libs/core/src/lib/title/title.component.spec.ts +++ b/libs/core/src/lib/title/title.component.spec.ts @@ -1,6 +1,5 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { TitleComponent } from './title.component'; -import { TitleModule } from './title.module'; describe('TitleComponent', () => { let component: TitleComponent; @@ -8,7 +7,7 @@ describe('TitleComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [TitleModule] + imports: [TitleComponent] }).compileComponents(); })); diff --git a/libs/core/src/lib/title/title.component.ts b/libs/core/src/lib/title/title.component.ts index b4f1a30173a..9b528ed93c4 100644 --- a/libs/core/src/lib/title/title.component.ts +++ b/libs/core/src/lib/title/title.component.ts @@ -20,7 +20,8 @@ export abstract class TitleToken { styleUrls: ['./title.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - providers: [{ provide: TitleToken, useExisting: TitleComponent }] + providers: [{ provide: TitleToken, useExisting: TitleComponent }], + standalone: true }) export class TitleComponent extends TitleToken implements OnInit { /** The size of the header */ diff --git a/libs/core/src/lib/title/title.module.ts b/libs/core/src/lib/title/title.module.ts index 947176e7e44..a1933f51e3d 100644 --- a/libs/core/src/lib/title/title.module.ts +++ b/libs/core/src/lib/title/title.module.ts @@ -1,10 +1,8 @@ import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; import { TitleComponent } from './title.component'; @NgModule({ - declarations: [TitleComponent], - imports: [CommonModule], + imports: [TitleComponent], exports: [TitleComponent] }) export class TitleModule {} diff --git a/libs/core/src/lib/toolbar/deprecated-toolbar-size.directive.ts b/libs/core/src/lib/toolbar/deprecated-toolbar-size.directive.ts index 63cebd10666..f3431c69cd3 100644 --- a/libs/core/src/lib/toolbar/deprecated-toolbar-size.directive.ts +++ b/libs/core/src/lib/toolbar/deprecated-toolbar-size.directive.ts @@ -17,7 +17,8 @@ type ToolbarSize = 'cozy' | 'compact' | 'condensed' | null; provide: CONTENT_DENSITY_DIRECTIVE, useExisting: forwardRef(() => DeprecatedToolbarSizeDirective) } - ] + ], + standalone: true }) export class DeprecatedToolbarSizeDirective extends BehaviorSubject diff --git a/libs/core/src/lib/toolbar/toolbar-form-label.directive.ts b/libs/core/src/lib/toolbar/toolbar-form-label.directive.ts index 7118a8e6845..1fbc624b056 100644 --- a/libs/core/src/lib/toolbar/toolbar-form-label.directive.ts +++ b/libs/core/src/lib/toolbar/toolbar-form-label.directive.ts @@ -6,6 +6,7 @@ import { ToolbarItemDirective } from './toolbar-item.directive'; selector: '[fd-toolbar-form-label]', host: { class: 'fd-form-label fd-toolbar__overflow-form-label fd-toolbar__overflow-form-label--text' - } + }, + standalone: true }) export class ToolbarFormLabelDirective extends ToolbarItemDirective {} diff --git a/libs/core/src/lib/toolbar/toolbar-item.directive.ts b/libs/core/src/lib/toolbar/toolbar-item.directive.ts index 568f325a424..55f121c620d 100644 --- a/libs/core/src/lib/toolbar/toolbar-item.directive.ts +++ b/libs/core/src/lib/toolbar/toolbar-item.directive.ts @@ -5,7 +5,8 @@ import { OverflowPriorityEnum } from './toolbar.component'; @Directive({ selector: '[fd-toolbar-item], [fdOverflowGroup], [fdOverflowPriority]', - providers: [{ provide: ToolbarItem, useExisting: forwardRef(() => ToolbarItemDirective) }] + providers: [{ provide: ToolbarItem, useExisting: forwardRef(() => ToolbarItemDirective) }], + standalone: true }) export class ToolbarItemDirective implements ToolbarItem { /** @hidden */ diff --git a/libs/core/src/lib/toolbar/toolbar-label.directive.ts b/libs/core/src/lib/toolbar/toolbar-label.directive.ts index c27f50442ef..93c567caff5 100644 --- a/libs/core/src/lib/toolbar/toolbar-label.directive.ts +++ b/libs/core/src/lib/toolbar/toolbar-label.directive.ts @@ -7,6 +7,7 @@ import { ToolbarItemDirective } from './toolbar-item.directive'; selector: '[fd-toolbar-label]', host: { class: 'fd-label fd-toolbar__overflow-label' - } + }, + standalone: true }) export class ToolbarLabelDirective extends ToolbarItemDirective {} diff --git a/libs/core/src/lib/toolbar/toolbar-overflow-button-menu.directive.ts b/libs/core/src/lib/toolbar/toolbar-overflow-button-menu.directive.ts index fe3baf010f3..4ea3e8e6d1e 100644 --- a/libs/core/src/lib/toolbar/toolbar-overflow-button-menu.directive.ts +++ b/libs/core/src/lib/toolbar/toolbar-overflow-button-menu.directive.ts @@ -5,6 +5,7 @@ import { ToolbarItemDirective } from './toolbar-item.directive'; selector: '[fdToolbarOverflowButtonMenu], [fd-toolbar-overflow-button-menu]', host: { class: 'fd-toolbar__overflow-button--menu' - } + }, + standalone: true }) export class ToolbarOverflowButtonMenuDirective extends ToolbarItemDirective {} diff --git a/libs/core/src/lib/toolbar/toolbar-overflow-button.directive.ts b/libs/core/src/lib/toolbar/toolbar-overflow-button.directive.ts index 80d9aa05c67..e94803b780e 100644 --- a/libs/core/src/lib/toolbar/toolbar-overflow-button.directive.ts +++ b/libs/core/src/lib/toolbar/toolbar-overflow-button.directive.ts @@ -5,6 +5,7 @@ import { ToolbarItemDirective } from './toolbar-item.directive'; selector: '[fdToolbarOverflowButton], [fd-toolbar-overflow-button]', host: { class: 'fd-toolbar__overflow-button' - } + }, + standalone: true }) export class ToolbarOverflowButtonDirective extends ToolbarItemDirective {} diff --git a/libs/core/src/lib/toolbar/toolbar-separator.component.ts b/libs/core/src/lib/toolbar/toolbar-separator.component.ts index 40f6908b5ff..3138f71df06 100644 --- a/libs/core/src/lib/toolbar/toolbar-separator.component.ts +++ b/libs/core/src/lib/toolbar/toolbar-separator.component.ts @@ -7,6 +7,7 @@ import { Component, ViewEncapsulation, ChangeDetectionStrategy } from '@angular/ changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'fd-toolbar__separator' - } + }, + standalone: true }) export class ToolbarSeparatorComponent {} diff --git a/libs/core/src/lib/toolbar/toolbar-spacer.directive.ts b/libs/core/src/lib/toolbar/toolbar-spacer.directive.ts index 392a97a250e..5ccbb4e45a1 100644 --- a/libs/core/src/lib/toolbar/toolbar-spacer.directive.ts +++ b/libs/core/src/lib/toolbar/toolbar-spacer.directive.ts @@ -2,7 +2,8 @@ import { Directive, HostBinding, Input } from '@angular/core'; @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector - selector: 'fd-toolbar-spacer' + selector: 'fd-toolbar-spacer', + standalone: true }) export class ToolbarSpacerDirective { /** Determines the width of spacer when fixed property is set to true diff --git a/libs/core/src/lib/toolbar/toolbar.component.html b/libs/core/src/lib/toolbar/toolbar.component.html index 9f3ab1904d1..77cc5161151 100644 --- a/libs/core/src/lib/toolbar/toolbar.component.html +++ b/libs/core/src/lib/toolbar/toolbar.component.html @@ -1,35 +1,33 @@ -
-

{{ title }}

+

{{ title }}

- + - - - - - - - + + + + + + + - -
- - - - -
-
-
-
-
+ +
+ + + + +
+
+ + diff --git a/libs/core/src/lib/toolbar/toolbar.component.spec.ts b/libs/core/src/lib/toolbar/toolbar.component.spec.ts index c3da81e0f29..e790d815c8f 100644 --- a/libs/core/src/lib/toolbar/toolbar.component.spec.ts +++ b/libs/core/src/lib/toolbar/toolbar.component.spec.ts @@ -63,7 +63,7 @@ describe('ToolbarComponent', () => { expect(actualOverflowItems.length).toBeGreaterThan(0); doneFn(); }); - resizeService.trigger(toolbar.toolbar.nativeElement, []); + resizeService.trigger(toolbar.elementRef.nativeElement, []); }); }); }); @@ -111,7 +111,7 @@ describe('ToolbarComponent - Prioritization', () => { expect(actualOverflownItems.map((el) => el.priority)).toEqual(overflownItems); doneFn(); }); - resizeService.trigger(toolbar.toolbar.nativeElement, []); + resizeService.trigger(toolbar.elementRef.nativeElement, []); }); }); }); @@ -166,7 +166,7 @@ describe('ToolbarComponent - Prioritization and Grouping', () => { ); doneFn(); }); - resizeService.trigger(toolbar.toolbar.nativeElement, []); + resizeService.trigger(toolbar.elementRef.nativeElement, []); }); }); }); diff --git a/libs/core/src/lib/toolbar/toolbar.component.ts b/libs/core/src/lib/toolbar/toolbar.component.ts index 00a06b9de54..2b8162f2835 100644 --- a/libs/core/src/lib/toolbar/toolbar.component.ts +++ b/libs/core/src/lib/toolbar/toolbar.component.ts @@ -10,6 +10,8 @@ import { DestroyRef, ElementRef, forwardRef, + HostBinding, + inject, Inject, Input, Optional, @@ -36,6 +38,12 @@ import { } from '@fundamental-ngx/core/content-density'; import { ToolbarItem } from './abstract-toolbar-item.class'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { DynamicPortalComponent } from '@fundamental-ngx/cdk/utils'; +import { ButtonModule } from '@fundamental-ngx/core/button'; +import { PopoverModule } from '@fundamental-ngx/core/popover'; +import { ToolbarSeparatorComponent } from './toolbar-separator.component'; +import { ToolbarSpacerDirective } from './toolbar-spacer.directive'; +import { NgIf, NgFor } from '@angular/common'; const ELEMENT_MARGIN = 8; const OVERFLOW_SPACE = 50 + 2 * ELEMENT_MARGIN; @@ -61,6 +69,16 @@ export const enum OverflowPriorityEnum { contentDensityObserverProviders({ defaultContentDensity: ContentDensityMode.COMPACT }) + ], + standalone: true, + imports: [ + NgIf, + ToolbarSpacerDirective, + ToolbarSeparatorComponent, + PopoverModule, + ButtonModule, + NgFor, + DynamicPortalComponent ] }) export class ToolbarComponent implements AfterViewInit, AfterViewChecked, CssClassBuilder, AfterContentInit { @@ -119,9 +137,15 @@ export class ToolbarComponent implements AfterViewInit, AfterViewChecked, CssCla @Input() tabindex = -1; - /** @hidden */ - @ViewChild('toolbar') - toolbar: ElementRef; + /** Toolbar Aria-label attribute. */ + @Input() + @HostBinding('attr.aria-label') + ariaLabel: string; + + /** Toolbar Aria-labelledby attribute. */ + @Input() + @HostBinding('attr.aria-labelledby') + ariaLabelledBy: string; /** @hidden */ @ViewChild('titleElement') @@ -147,6 +171,13 @@ export class ToolbarComponent implements AfterViewInit, AfterViewChecked, CssCla /** @hidden */ overflownItems: ToolbarItem[] = []; + /** HTML Element Reference. */ + readonly elementRef = inject(ElementRef); + + /** @hidden */ + @HostBinding('attr.role') + private readonly _role = 'toolbar'; + /** @hidden */ private _titleComponent$: BehaviorSubject = new BehaviorSubject(null); @@ -155,7 +186,7 @@ export class ToolbarComponent implements AfterViewInit, AfterViewChecked, CssCla /** @hidden */ private get _toolbarWidth(): number { - return (this.toolbar.nativeElement as HTMLElement).clientWidth - OVERFLOW_SPACE; + return (this.elementRef.nativeElement as HTMLElement).clientWidth - OVERFLOW_SPACE; } /** @hidden */ @@ -191,7 +222,7 @@ export class ToolbarComponent implements AfterViewInit, AfterViewChecked, CssCla /** @hidden */ ngAfterViewInit(): void { this.overflowItems$ = combineLatest([ - this.resizeObserverService.observe(this.toolbar.nativeElement).pipe(map(() => this._toolbarWidth)), + this.resizeObserverService.observe(this.elementRef.nativeElement).pipe(map(() => this._toolbarWidth)), this.toolbarItems.changes.pipe( startWith(this.toolbarItems), map((toolbarItems) => toolbarItems.toArray()) @@ -276,11 +307,6 @@ export class ToolbarComponent implements AfterViewInit, AfterViewChecked, CssCla this._cd.detectChanges(); } - /** @hidden */ - get elementRef(): ElementRef { - return this.toolbar; - } - /** Method triggering collapsible toolbar */ updateCollapsibleItems(): void { this._refreshOverflow$.next(); diff --git a/libs/core/src/lib/toolbar/toolbar.module.ts b/libs/core/src/lib/toolbar/toolbar.module.ts index 31c6939ceb0..b7ad147a3c3 100644 --- a/libs/core/src/lib/toolbar/toolbar.module.ts +++ b/libs/core/src/lib/toolbar/toolbar.module.ts @@ -1,8 +1,4 @@ import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { ButtonModule } from '@fundamental-ngx/core/button'; -import { PopoverModule } from '@fundamental-ngx/core/popover'; import { ToolbarComponent } from './toolbar.component'; import { ToolbarItemDirective } from './toolbar-item.directive'; import { ToolbarSeparatorComponent } from './toolbar-separator.component'; @@ -13,7 +9,6 @@ import { ToolbarOverflowButtonMenuDirective } from './toolbar-overflow-button-me import { ToolbarFormLabelDirective } from './toolbar-form-label.directive'; import { DeprecatedToolbarSizeDirective } from './deprecated-toolbar-size.directive'; import { ContentDensityModule } from '@fundamental-ngx/core/content-density'; -import { DynamicPortalComponent } from '@fundamental-ngx/cdk/utils'; const components = [ ToolbarComponent, @@ -28,8 +23,7 @@ const components = [ ]; @NgModule({ - declarations: [...components], - imports: [CommonModule, ButtonModule, PopoverModule, ContentDensityModule, DynamicPortalComponent], + imports: [ContentDensityModule, ...components], exports: [...components, ContentDensityModule] }) export class ToolbarModule {} diff --git a/libs/core/src/lib/tree/tree.component.scss b/libs/core/src/lib/tree/tree.component.scss index 471f274659e..3afc690e207 100644 --- a/libs/core/src/lib/tree/tree.component.scss +++ b/libs/core/src/lib/tree/tree.component.scss @@ -31,11 +31,3 @@ $block: fd-tree; } } } - -// TODO: Remove these stylings after new stable fundamental-styles version bump. - -.fd-tree__item[aria-level='1'] > .fd-tree__item-container--active.is-focus:before, -.fd-tree__item[aria-level='1'] > .fd-tree__item-container--active:focus:before, -.fd-tree__item-container--active:focus:before { - bottom: 0.0625rem; -} diff --git a/libs/core/src/lib/vertical-navigation/vertical-navigation.component.spec.ts b/libs/core/src/lib/vertical-navigation/vertical-navigation.component.spec.ts index a53aaa19924..0a1763d6578 100644 --- a/libs/core/src/lib/vertical-navigation/vertical-navigation.component.spec.ts +++ b/libs/core/src/lib/vertical-navigation/vertical-navigation.component.spec.ts @@ -1,9 +1,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { Component, ElementRef, ViewChild } from '@angular/core'; +import { Component, ElementRef, NO_ERRORS_SCHEMA, ViewChild } from '@angular/core'; import { VerticalNavigationComponent } from './vertical-navigation.component'; -import { LinkComponent } from '@fundamental-ngx/core/link'; -import { IconModule } from '@fundamental-ngx/core/icon'; import { ListNavigationItemComponent, ListModule } from '@fundamental-ngx/core/list'; @Component({ @@ -56,8 +54,9 @@ describe('VerticalNavigationComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [VerticalNavigationComponent, TestVerticalNavigationComponent, LinkComponent], - imports: [IconModule, ListModule] + declarations: [VerticalNavigationComponent, TestVerticalNavigationComponent], + imports: [ListModule], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); })); diff --git a/libs/docs/core/card/e2e/card.po.ts b/libs/docs/core/card/e2e/card.po.ts index 9dfb8c874ec..b41a612ea8b 100644 --- a/libs/docs/core/card/e2e/card.po.ts +++ b/libs/docs/core/card/e2e/card.po.ts @@ -26,7 +26,7 @@ export class CardPo extends CoreBaseComponentPo { ftCardListItems = 'fd-card-footer-example fd-card:first-of-type li'; ftFooter = 'fd-card-footer-example fd-card-footer'; ftCardFooterActionItems = - 'fd-card-footer-example fd-card:first-of-type fd-card-footer .fd-card__footer-actions-item'; + 'fd-card-footer-example fd-card:first-of-type fd-card-footer .fd-card__footer-actions button'; // analytical card examples kpiCardHeader = 'fd-card-kpi-example fd-card-header'; kpiCardTitle = 'fd-card-kpi-example .fd-card__title'; diff --git a/libs/docs/core/carousel/carousel-docs.component.html b/libs/docs/core/carousel/carousel-docs.component.html index 0a65db7e3a9..75f99ed4824 100644 --- a/libs/docs/core/carousel/carousel-docs.component.html +++ b/libs/docs/core/carousel/carousel-docs.component.html @@ -151,3 +151,28 @@ + + +Background modifiers + +

+ The background of the Carousel Content and Page Indicator Container can be changed by providing + [contentBackground] and [pageIndicatorBackground] input properties. +

+

Background options can be following:

+
    +
  • translucent
  • +
  • transparent
  • +
  • solid
  • +
+

The default background for the Carousel Content is translucent.

+

The default background for the Page Indicator Container is solid.

+

+ Additionally, developers can pass [noPaginationContainerBorder]="true" to remove the border top or + bottom of the Pagination container. +

+
+ + + + diff --git a/libs/docs/core/carousel/carousel-docs.module.ts b/libs/docs/core/carousel/carousel-docs.module.ts index a1a02b4d8fd..b0cbd043667 100644 --- a/libs/docs/core/carousel/carousel-docs.module.ts +++ b/libs/docs/core/carousel/carousel-docs.module.ts @@ -22,6 +22,7 @@ import { CarouselLoopedNavigationExampleComponent } from './examples/carousel-lo import { CarouselErrorMessageExampleComponent } from './examples/carousel-error-message-example.component'; import { CarouselLoadingContentExampleComponent } from './examples/carousel-loading-content-example.component'; import { CarouselAutoSlidesExampleComponent } from './examples/carousel-auto-slides-example.component'; +import { CarouselBackgroundExampleComponent } from './examples/carousel-background-example.component'; const routes: Routes = [ { @@ -60,7 +61,8 @@ const routes: Routes = [ CarouselLoopedNavigationExampleComponent, CarouselErrorMessageExampleComponent, CarouselLoadingContentExampleComponent, - CarouselAutoSlidesExampleComponent + CarouselAutoSlidesExampleComponent, + CarouselBackgroundExampleComponent ], providers: [currentComponentProvider('carousel')] }) diff --git a/libs/docs/core/carousel/e2e/carousel.e2e-spec.ts b/libs/docs/core/carousel/e2e/carousel.e2e-spec.ts index ab44ebc9810..f6e264c1e52 100644 --- a/libs/docs/core/carousel/e2e/carousel.e2e-spec.ts +++ b/libs/docs/core/carousel/e2e/carousel.e2e-spec.ts @@ -215,17 +215,17 @@ describe('Carousel test suite', () => { describe('carousel with looped navigation example', () => { it('should check loop navigation', async () => { - const firstImg = await getAttributeByName(displayedImg, imgSource, 5); + const firstImg = await getAttributeByName(displayedImg, imgSource, 6); - await click(navBtns, 14); + await click(navBtns, 16); // pause for animation to complete await pause(1500); - await expect(await getAttributeByName(displayedImg, imgSource, 5)).not.toBe(firstImg); + await expect(await getAttributeByName(displayedImg, imgSource, 6)).not.toBe(firstImg); - await click(navBtns, 15); + await click(navBtns, 17); // pause for animation to complete await pause(1500); - await expect(await getAttributeByName(displayedImg, imgSource, 5)).toBe(firstImg); + await expect(await getAttributeByName(displayedImg, imgSource, 6)).toBe(firstImg); }); }); diff --git a/libs/docs/core/carousel/examples/carousel-background-example.component.html b/libs/docs/core/carousel/examples/carousel-background-example.component.html new file mode 100644 index 00000000000..244cb650162 --- /dev/null +++ b/libs/docs/core/carousel/examples/carousel-background-example.component.html @@ -0,0 +1,62 @@ +

Transparent Carousel Content:

+ +

Solid Carousel Content:

+ +

Transparent Page Indicator Container:

+ +

Translucent Page Indicator Container:

+ +

Page Indicator Container with no border:

+ + + +
+ + + + + + + + +
+
diff --git a/libs/docs/core/carousel/examples/carousel-background-example.component.ts b/libs/docs/core/carousel/examples/carousel-background-example.component.ts new file mode 100644 index 00000000000..b788036166d --- /dev/null +++ b/libs/docs/core/carousel/examples/carousel-background-example.component.ts @@ -0,0 +1,9 @@ +import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'fd-carousel-background-example', + templateUrl: './carousel-background-example.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class CarouselBackgroundExampleComponent {} diff --git a/libs/docs/core/carousel/examples/carousel-content-navigation-example.component.html b/libs/docs/core/carousel/examples/carousel-content-navigation-example.component.html index 948da29d4f2..6993b7624ee 100644 --- a/libs/docs/core/carousel/examples/carousel-content-navigation-example.component.html +++ b/libs/docs/core/carousel/examples/carousel-content-navigation-example.component.html @@ -1,39 +1,52 @@ -
- - - first image - - - - second image - - - - third image - - - - forth image - - - - fifth image - - - - sixth image - - - - seventh image - - - - eight image - - - - ninth image - - -
+

Horizontal navigation:

+ +

Vertical navigation:

+ + + +
+ + + first image + + + + second image + + + + third image + + + + forth image + + + + fifth image + + + + sixth image + + + + seventh image + + + + eight image + + + + ninth image + + +
+
diff --git a/libs/docs/core/checkbox/checkbox-docs.component.html b/libs/docs/core/checkbox/checkbox-docs.component.html index 0268b2958c6..f74d05eef3d 100644 --- a/libs/docs/core/checkbox/checkbox-docs.component.html +++ b/libs/docs/core/checkbox/checkbox-docs.component.html @@ -70,6 +70,21 @@ + + Checkbox with label wrapping + + + Checkbox labels by default do not wrap. If you want to wrap the labels, then you should use + fd-checkbox[wrapLabel] + input + + + + + + + + Checkbox styling properties Checkbox can be but in various states by modifying following properties: @@ -92,3 +107,17 @@ + + + + + Checkbox with Display-only mode + + + For cases when users should not interact with the Checkbox, developers can set it to 'Display-only' mode with + [displayOnly]="true" input property. + + + + + diff --git a/libs/docs/core/checkbox/checkbox-docs.component.ts b/libs/docs/core/checkbox/checkbox-docs.component.ts index 7e67e02d35c..353ed629145 100644 --- a/libs/docs/core/checkbox/checkbox-docs.component.ts +++ b/libs/docs/core/checkbox/checkbox-docs.component.ts @@ -8,6 +8,8 @@ const checkboxCustomValuesTsCode = 'checkbox-custom-values-example.component.ts' const checkboxReactiveFormsTsCode = 'checkbox-reactive-forms-example.component.ts'; const checkboxStatesTsCode = 'checkbox-states-example.component.ts'; const checkboxCustomLabelTsCode = 'checkbox-custom-label-example.component.ts'; +const checkboxWrappingTsCode = 'checkbox-label-wrapping-example.component.ts'; +const checkboxDisplayOnlyMode = 'checkbox-display-mode-example.component.ts'; @Component({ selector: 'app-input', @@ -67,4 +69,22 @@ export class CheckboxDocsComponent { code: getAssetFromModuleAssets(checkboxCustomLabelTsCode) } ]; + + checkboxLabelWrapping: ExampleFile[] = [ + { + language: 'typescript', + fileName: 'checkbox-label-wrapping-example', + component: 'CheckboxLabelWrappingExampleComponent', + code: getAssetFromModuleAssets(checkboxWrappingTsCode) + } + ]; + + checkboxDisplayOnlyMode: ExampleFile[] = [ + { + language: 'typescript', + fileName: 'checkbox-display-mode-example', + component: 'CheckboxDisplayModeExampleComponent', + code: getAssetFromModuleAssets(checkboxDisplayOnlyMode) + } + ]; } diff --git a/libs/docs/core/checkbox/checkbox-docs.module.ts b/libs/docs/core/checkbox/checkbox-docs.module.ts index 6c71b8c0927..fc499456ccf 100644 --- a/libs/docs/core/checkbox/checkbox-docs.module.ts +++ b/libs/docs/core/checkbox/checkbox-docs.module.ts @@ -6,6 +6,7 @@ import { CheckboxHeaderComponent } from './checkbox-header/checkbox-header.compo import { CheckboxDocsComponent } from './checkbox-docs.component'; import { examples } from './examples'; import { FormModule } from '@fundamental-ngx/core/form'; +import { CheckboxDisplayModeExampleComponent } from './examples/checkbox-display-mode-example.component'; import { CheckboxModule } from '@fundamental-ngx/core/checkbox'; const routes: Routes = [ @@ -21,8 +22,8 @@ const routes: Routes = [ @NgModule({ imports: [FormModule, CheckboxModule, RouterModule.forChild(routes), SharedDocumentationPageModule], - exports: [RouterModule], - declarations: [examples, CheckboxDocsComponent, CheckboxHeaderComponent], + exports: [RouterModule, CheckboxDisplayModeExampleComponent], + declarations: [examples, CheckboxDocsComponent, CheckboxHeaderComponent, CheckboxDisplayModeExampleComponent], providers: [currentComponentProvider('checkbox')] }) export class CheckboxDocsModule {} diff --git a/libs/docs/core/checkbox/e2e/checkbox-content.ts b/libs/docs/core/checkbox/e2e/checkbox-content.ts index 2d913e69fbf..c26791fba24 100644 --- a/libs/docs/core/checkbox/e2e/checkbox-content.ts +++ b/libs/docs/core/checkbox/e2e/checkbox-content.ts @@ -1,4 +1,3 @@ -export const emptyDataArr = ['""', null, undefined]; export const emptyString = '""'; export const customLabelsArr = ['Yes', 'No', 'I dont have an opinion']; export const allMarkedTrue = diff --git a/libs/docs/core/checkbox/e2e/checkbox.e2e-spec.ts b/libs/docs/core/checkbox/e2e/checkbox.e2e-spec.ts index 74db1c61d72..69525096839 100644 --- a/libs/docs/core/checkbox/e2e/checkbox.e2e-spec.ts +++ b/libs/docs/core/checkbox/e2e/checkbox.e2e-spec.ts @@ -1,7 +1,6 @@ import { CheckboxPo } from './checkbox.po'; // eslint-disable-next-line @nx/enforce-module-boundaries import { - applyState, browserIsSafari, click, executeScriptBeforeTagAttr, @@ -24,7 +23,6 @@ import { altCustomLabel, customLabel, customLabelsArr, - emptyDataArr, emptyString, stateClassesArr } from './checkbox-content'; @@ -41,6 +39,7 @@ describe('checkbox test suite', () => { checkbox, checkboxInput, checkboxLabel, + checkboxCheckmark, link, tristateOutput } = checkboxPage; @@ -53,14 +52,14 @@ describe('checkbox test suite', () => { it('should mark checkbox', async () => { await scrollIntoView(standardCheckbox); - await expect(await executeScriptBeforeTagAttr(standardCheckbox + checkboxLabel, 'content')).toEqual( + await expect(await executeScriptBeforeTagAttr(standardCheckbox + checkboxCheckmark, 'content')).toEqual( emptyString, 'mark is present' ); await click(standardCheckbox + checkboxLabel); - await expect(await executeScriptBeforeTagAttr(standardCheckbox + checkboxLabel, 'content')).not.toEqual( + await expect(await executeScriptBeforeTagAttr(standardCheckbox + checkboxCheckmark, 'content')).not.toEqual( emptyString, 'mark is not present' ); @@ -199,7 +198,7 @@ describe('checkbox test suite', () => { for (let i = 0; i < checkboxCount; i++) { await expect( - await executeScriptBeforeTagAttr(reactiveFormCheckbox + checkboxLabel, 'content', i) + await executeScriptBeforeTagAttr(reactiveFormCheckbox + checkboxCheckmark, 'content', i) ).not.toEqual(emptyString); } const textArr = (await getText(reactiveFormCheckbox)).split('\n'); @@ -218,7 +217,7 @@ describe('checkbox test suite', () => { for (let i = 0; i < checkboxCount; i++) { await expect( - await executeScriptBeforeTagAttr(reactiveFormCheckbox + checkboxLabel, 'content', i) + await executeScriptBeforeTagAttr(reactiveFormCheckbox + checkboxCheckmark, 'content', i) ).toEqual(emptyString); } const textArr2 = (await getText(reactiveFormCheckbox)).split('\n'); diff --git a/libs/docs/core/checkbox/e2e/checkbox.po.ts b/libs/docs/core/checkbox/e2e/checkbox.po.ts index 922a3fee332..ab61c205aa9 100644 --- a/libs/docs/core/checkbox/e2e/checkbox.po.ts +++ b/libs/docs/core/checkbox/e2e/checkbox.po.ts @@ -14,6 +14,7 @@ export class CheckboxPo extends CoreBaseComponentPo { checkbox = 'fd-checkbox'; checkboxInput = this.checkbox + ' input'; checkboxLabel = this.checkbox + ' label'; + checkboxCheckmark = this.checkboxLabel + ' span.fd-checkbox__checkmark'; link = this.checkbox + ' a'; tristateOutput = this.tristateCheckbox + 'div'; diff --git a/libs/docs/core/checkbox/examples/checkbox-display-mode-example.component.ts b/libs/docs/core/checkbox/examples/checkbox-display-mode-example.component.ts new file mode 100644 index 00000000000..f08b93cc246 --- /dev/null +++ b/libs/docs/core/checkbox/examples/checkbox-display-mode-example.component.ts @@ -0,0 +1,29 @@ +import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'fd-checkbox-display-mode-example', + template: ` +

Display-only checkboxes can be controlled programmatically. They will not react on any user interaction.

+ + + Value: {{ checkboxValue }} +
+ + `, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class CheckboxDisplayModeExampleComponent { + checkboxValue = false; + + toggleCheckboxValue(): void { + this.checkboxValue = !this.checkboxValue; + } +} diff --git a/libs/docs/core/checkbox/examples/checkbox-label-wrapping-example.component.ts b/libs/docs/core/checkbox/examples/checkbox-label-wrapping-example.component.ts new file mode 100644 index 00000000000..4203644d89f --- /dev/null +++ b/libs/docs/core/checkbox/examples/checkbox-label-wrapping-example.component.ts @@ -0,0 +1,34 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'fd-checkbox-label-wrapping-example', + template: ` +
+ + LABEL WILL BE POSITIONED IN THE MIDDLE. Lorem ipsum dolor sit amet, consectetur adipisicing elit. + Corporis ea eos in porro quae ullam. Aliquid corporis doloribus exercitationem facilis hic illo labore + laudantium quam reprehenderit sapiente, tempore voluptatibus voluptatum? + +
+
+ + LABEL WILL BE POSITIONED ON THE TOP. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Corporis + ea eos in porro quae ullam. Aliquid corporis doloribus exercitationem facilis hic illo labore laudantium + quam reprehenderit sapiente, tempore voluptatibus voluptatum? + +
+ `, + styles: [ + ` + :host { + display: flex; + flex-direction: column; + gap: 1rem; + } + div { + max-width: 300px; + } + ` + ] +}) +export class CheckboxLabelWrappingExampleComponent {} diff --git a/libs/docs/core/checkbox/examples/index.ts b/libs/docs/core/checkbox/examples/index.ts index 0c101e8ccde..e861d992702 100644 --- a/libs/docs/core/checkbox/examples/index.ts +++ b/libs/docs/core/checkbox/examples/index.ts @@ -4,6 +4,7 @@ import { CheckboxDefaultExampleComponent } from './checkbox-default-example.comp import { CheckboxReactiveFormsExampleComponent } from './checkbox-reactive-forms-example.component'; import { CheckboxStatesExampleComponent } from './checkbox-states-example.component'; import { CheckboxTristateExampleComponent } from './checkbox-tristate-example.component'; +import { CheckboxLabelWrappingExampleComponent } from './checkbox-label-wrapping-example.component'; export * from './checkbox-custom-label-example.component'; export * from './checkbox-custom-values-example.component'; @@ -18,5 +19,6 @@ export const examples = [ CheckboxTristateExampleComponent, CheckboxCustomLabelExampleComponent, CheckboxCustomValuesExampleComponent, - CheckboxReactiveFormsExampleComponent + CheckboxReactiveFormsExampleComponent, + CheckboxLabelWrappingExampleComponent ]; diff --git a/libs/docs/core/checkbox/index.ts b/libs/docs/core/checkbox/index.ts index 4ebc3c53c0c..408867e9222 100644 --- a/libs/docs/core/checkbox/index.ts +++ b/libs/docs/core/checkbox/index.ts @@ -1 +1,3 @@ export * from './checkbox-docs.module'; + +export * from './examples/checkbox-display-mode-example.component'; diff --git a/libs/docs/core/dialog/examples/dialog-state/dialog-state-example.component.ts b/libs/docs/core/dialog/examples/dialog-state/dialog-state-example.component.ts index 81e3183152f..f290f49e6cb 100644 --- a/libs/docs/core/dialog/examples/dialog-state/dialog-state-example.component.ts +++ b/libs/docs/core/dialog/examples/dialog-state/dialog-state-example.component.ts @@ -49,6 +49,10 @@ export class DialogStateExampleComponent { ariaLabelledBy: 'fd-dialog-header-7', ariaDescribedBy: 'fd-dialog-body-7' }); - dialogRef.loading(true); + dialogRef.loading({ + isLoading: true, + loadingLabel: 'Some loading label', + loadingContent: '... now loading data from a far far away server from far far away.' + }); } } diff --git a/libs/docs/core/list-byline/e2e/list-byline.e2e-spec.ts b/libs/docs/core/list-byline/e2e/list-byline.e2e-spec.ts index c37555de489..8c1a1ca2cdd 100644 --- a/libs/docs/core/list-byline/e2e/list-byline.e2e-spec.ts +++ b/libs/docs/core/list-byline/e2e/list-byline.e2e-spec.ts @@ -12,7 +12,7 @@ import { describe('List byline test suite', () => { const listBylinePage = new ListBylinePo(); - const { selectionExample, buttonExample, button, checkbox, listItem, radioButton, radioButtonInput } = + const { selectionExample, buttonExample, button, checkbox, listItem, radioButtonLabel, radioButtonInput } = listBylinePage; beforeAll(async () => { @@ -47,11 +47,14 @@ describe('List byline test suite', () => { }); it('verify that radio buttons work correctly', async () => { - const radioButtonLength = await getElementArrayLength(selectionExample + radioButton); - for (let i = 0; i < radioButtonLength; i++) { - await scrollIntoView(selectionExample + radioButton, i); - await click(selectionExample + radioButton, i); - await expect(await getAttributeByName(radioButtonInput, 'aria-checked', i)).toBe('true'); + const radioButtonLabels = await $$(`${selectionExample} ${radioButtonLabel}`); + const radioButtonInputs = await $$(`${selectionExample} ${radioButtonInput}`); + for (let i = 0; i < radioButtonLabels.length; i++) { + const label = radioButtonLabels[i]; + await label.scrollIntoView(); + await label.click(); + const ariaChecked = await radioButtonInputs[i].getAttribute('aria-checked'); + await expect(ariaChecked).toBe('true'); } }); diff --git a/libs/docs/core/list-byline/e2e/list-byline.po.ts b/libs/docs/core/list-byline/e2e/list-byline.po.ts index ca1b0a19348..5b775d52648 100644 --- a/libs/docs/core/list-byline/e2e/list-byline.po.ts +++ b/libs/docs/core/list-byline/e2e/list-byline.po.ts @@ -8,7 +8,7 @@ export class ListBylinePo extends CoreBaseComponentPo { button = ' button'; checkbox = ' fd-checkbox'; - radioButton = ' .fd-radio__label'; + radioButtonLabel = '.fd-radio__label'; radioButtonInput = '.fd-radio'; listItem = ' .fd-list__item'; diff --git a/libs/docs/core/list/e2e/standard-list.e2e-spec.ts b/libs/docs/core/list/e2e/standard-list.e2e-spec.ts index e07efa55559..58544674f88 100644 --- a/libs/docs/core/list/e2e/standard-list.e2e-spec.ts +++ b/libs/docs/core/list/e2e/standard-list.e2e-spec.ts @@ -4,7 +4,6 @@ import { click, clickAndDragElement, doesItExist, - executeScriptBeforeTagAttr, getElementArrayLength, getElementClass, getElementLocation, @@ -31,7 +30,6 @@ describe('Standard List test suite', () => { cozySingleSelectList, compactSingleSelectList, selectedItems, - keyboardSupportList, dragAndDropList, infiniteList, deleteButton, diff --git a/libs/docs/core/list/e2e/standard-list.po.ts b/libs/docs/core/list/e2e/standard-list.po.ts index bdde44155f6..fa639ff828b 100644 --- a/libs/docs/core/list/e2e/standard-list.po.ts +++ b/libs/docs/core/list/e2e/standard-list.po.ts @@ -4,22 +4,13 @@ export class StandardListPo extends CoreBaseComponentPo { url = '/list'; // example selectors - simpleList = 'fd-list-example '; - navigationList = 'fd-list-navigation-example '; - navigationIndicatorList = 'fd-list-nav-indicator-example '; actionList = 'fd-list-action-example '; filterAndSortList = 'fd-list-data-example '; - secondaryItemList = 'fd-list-secondary-example '; - iconList = 'fd-list-icon-example '; - borderlessList = 'fd-list-borderless-example '; - interactiveList = 'fd-list-interactive-example '; - complexList = 'fd-list-complex-example '; selectionList = 'fd-list-selection-example '; cozyMultiSelectList = this.selectionList + 'ul:first-of-type '; compactMultiSelectList = this.selectionList + 'ul:nth-of-type(2) '; cozySingleSelectList = this.selectionList + 'ul:nth-of-type(3) '; compactSingleSelectList = this.selectionList + 'ul:nth-of-type(4) '; - keyboardSupportList = 'fd-list-keyboard-example '; dragAndDropList = 'fd-list-dnd-example '; infiniteList = 'fd-list-infinite-scroll-example '; @@ -30,9 +21,6 @@ export class StandardListPo extends CoreBaseComponentPo { searchBar = 'fd-input-group input'; selectedItems = 'li[class~="is-selected"]'; checkbox = 'fd-checkbox'; - radioBtn = 'fd-radio-button'; - checkboxFocusElement = this.checkbox + ' input'; - radioBtnFocusElement = this.radioBtn + ' input'; deleteButton = 'button[glyph="delete"]'; listItemText = '.fd-list__title'; diff --git a/libs/docs/core/list/examples/list-secondary-example.component.html b/libs/docs/core/list/examples/list-secondary-example.component.html index 60887113f09..7ad26f5c070 100644 --- a/libs/docs/core/list/examples/list-secondary-example.component.html +++ b/libs/docs/core/list/examples/list-secondary-example.component.html @@ -1,16 +1,28 @@
  • List item 1 - 1 + 1
  • List item 2 - 2 + 2 +
  • +
  • + List item 3 + 3 +
  • +
  • + List item 4 + 4 +
  • +
  • + List item 5 + 5
  • - List item 3 with Counter + List item 6 with Counter
  • - List item 4 with Counter + List item 7 with Counter
diff --git a/libs/docs/core/list/list-docs.component.html b/libs/docs/core/list/list-docs.component.html index 9851e8ea0be..d885d9b9107 100644 --- a/libs/docs/core/list/list-docs.component.html +++ b/libs/docs/core/list/list-docs.component.html @@ -66,7 +66,17 @@ Secondary item on list -The list component can be customized by adding additional information in additional columns. + +

The list component can be customized by adding additional information in additional columns.

+

+ Secondary items can have different types of visual appearance and can be set with + [fd-list-secondary][type] property +

+

Available types are

+
    +
  • {{ secondaryListItemType }}
  • +
+
diff --git a/libs/docs/core/list/list-docs.component.ts b/libs/docs/core/list/list-docs.component.ts index 9dd80e20485..daca5c0a079 100644 --- a/libs/docs/core/list/list-docs.component.ts +++ b/libs/docs/core/list/list-docs.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { ExampleFile, getAssetFromModuleAssets } from '@fundamental-ngx/docs/shared'; +import { _secondaryListItemTypes } from '@fundamental-ngx/core/list'; const listSortPipe = 'sort.pipe.ts'; const listSrc = 'list-example.component.html'; @@ -193,4 +194,5 @@ export class ListDocsComponent { fileName: 'list-loading-example' } ]; + readonly _secondaryListItemTypes = _secondaryListItemTypes; } diff --git a/libs/docs/core/message-strip/examples/message-strip-custom-icon-example.component.ts b/libs/docs/core/message-strip/examples/message-strip-custom-icon-example.component.ts new file mode 100644 index 00000000000..f592ab68385 --- /dev/null +++ b/libs/docs/core/message-strip/examples/message-strip-custom-icon-example.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'fd-message-strip-custom-icon-example', + template: ` + + + A dismissible normal message strip with custom icon. + + ` +}) +export class MessageStripCustomIconExampleComponent {} diff --git a/libs/docs/core/message-strip/examples/message-strip-indication-colors-example.component.ts b/libs/docs/core/message-strip/examples/message-strip-indication-colors-example.component.ts new file mode 100644 index 00000000000..2123853b31b --- /dev/null +++ b/libs/docs/core/message-strip/examples/message-strip-indication-colors-example.component.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; +import { _messageStripIndicationColors } from '@fundamental-ngx/core/message-strip'; + +@Component({ + selector: 'fd-message-strip-indication-colors-example', + template: ` + + + This is the message strip with indication color "{{ indicationColor }}" + + `, + styles: [ + ` + :host { + display: flex; + flex-direction: column; + gap: 1rem; + } + ` + ] +}) +export class MessageStripIndicationColorsExampleComponent { + indicationColors = _messageStripIndicationColors; +} diff --git a/libs/docs/core/message-strip/message-strip-docs.component.html b/libs/docs/core/message-strip/message-strip-docs.component.html index 70adb930196..065eb02c914 100644 --- a/libs/docs/core/message-strip/message-strip-docs.component.html +++ b/libs/docs/core/message-strip/message-strip-docs.component.html @@ -1,9 +1,11 @@ Message Strip Types -A number of message strip types are available. They offer preset styling for different contextual situations. Can - be one of 'warning', 'success', 'information', 'error' or normal (default). + +

+ A number of message strip types are available. They offer preset styling for different contextual situations. + Can be one of 'warning', 'success', 'information', 'error' or normal (default). +

@@ -12,6 +14,22 @@ + + Custom Icons + + +

+ If you want to have a custom icon, that is also supported using the *fdMessageStripIcon + directive. +

+
+ + + + + + + Message Strips Without Icons @@ -40,6 +58,23 @@ + + Message Strips Indication Colors + + +

+ If the application needs a custom Message Strip, other than the semantic variations, then the colours from + Inverted Object Status/Tag control should be used. Use the input [indicationColor] with values + number from 1 to 10 for the first set, and 1b to 10b for the second set. +

+
+ + + + + + + Message Strip Alerts diff --git a/libs/docs/core/message-strip/message-strip-docs.component.ts b/libs/docs/core/message-strip/message-strip-docs.component.ts index 593f87104f3..c1f2630ff72 100644 --- a/libs/docs/core/message-strip/message-strip-docs.component.ts +++ b/libs/docs/core/message-strip/message-strip-docs.component.ts @@ -53,6 +53,22 @@ export class MessageStripDocsComponent { } ]; + messageStripCustomIconExample: ExampleFile[] = [ + { + language: 'ts', + fileName: 'message-strip-custom-icon-example', + code: getAssetFromModuleAssets('message-strip-custom-icon-example.component.ts') + } + ]; + + messageStripIndicationColorsExample: ExampleFile[] = [ + { + language: 'ts', + fileName: 'message-strip-indication-colors-example', + code: getAssetFromModuleAssets('message-strip-indication-colors-example.component.ts') + } + ]; + messageStripAlertExampleFiles: ExampleFile[] = [ { language: 'ts', diff --git a/libs/docs/core/message-strip/message-strip-docs.module.ts b/libs/docs/core/message-strip/message-strip-docs.module.ts index 7e0e681e5d6..575fc79aa25 100644 --- a/libs/docs/core/message-strip/message-strip-docs.module.ts +++ b/libs/docs/core/message-strip/message-strip-docs.module.ts @@ -8,6 +8,8 @@ import { MessageStripNoIconExampleComponent } from './examples/message-strip-noi import { MessageStripWidthExampleComponent } from './examples/message-strip-width-example.component'; import { MessageStripHeaderComponent } from './message-strip-header/message-strip-header.component'; import { MessageStripAlertExampleComponent } from './examples/message-strip-alert-example.component'; +import { MessageStripIndicationColorsExampleComponent } from './examples/message-strip-indication-colors-example.component'; +import { MessageStripCustomIconExampleComponent } from './examples/message-strip-custom-icon-example.component'; import { MessageStripAutoDismissExampleComponent } from './examples/message-strip-auto-dismiss-example.component'; const routes: Routes = [ @@ -31,6 +33,9 @@ const routes: Routes = [ MessageStripNoIconExampleComponent, MessageStripWidthExampleComponent, MessageStripAlertExampleComponent, + MessageStripIndicationColorsExampleComponent, + MessageStripCustomIconExampleComponent, + MessageStripAlertExampleComponent, MessageStripAutoDismissExampleComponent ], providers: [currentComponentProvider('message-strip')] diff --git a/libs/docs/core/schema/src/lib/slider.schema.ts b/libs/docs/core/schema/src/lib/slider.schema.ts index 15265810d05..ce4772eba58 100644 --- a/libs/docs/core/schema/src/lib/slider.schema.ts +++ b/libs/docs/core/schema/src/lib/slider.schema.ts @@ -30,6 +30,9 @@ export const sliderSchema: any = { }, disabled: { type: 'boolean' + }, + vertical: { + type: 'boolean' } } } diff --git a/libs/docs/core/slider/examples/index.ts b/libs/docs/core/slider/examples/index.ts index 96f8cd46faa..4f5bf7079e2 100644 --- a/libs/docs/core/slider/examples/index.ts +++ b/libs/docs/core/slider/examples/index.ts @@ -6,6 +6,7 @@ import { SliderRangeExampleComponent } from './range/slider-range-example.compon import { SliderTicksAndLabelsExampleComponent } from './ticks-and-labels/slider-ticks-and-labels-example.component'; import { SliderTooltipExampleComponent } from './tooltip/slider-tooltip-example.component'; import { SliderFormExampleComponent } from './form/slider-form-example.component'; +import { VerticalSliderExampleComponent } from './vertical/vertical-slider-example.component'; export const COMPONENTS = [ SliderBasicExampleComponent, @@ -15,5 +16,6 @@ export const COMPONENTS = [ SliderCustomValuesExampleComponent, SliderTooltipExampleComponent, SliderCozyExampleComponent, - SliderFormExampleComponent + SliderFormExampleComponent, + VerticalSliderExampleComponent ]; diff --git a/libs/docs/core/slider/examples/vertical/vertical-slider-example.component.html b/libs/docs/core/slider/examples/vertical/vertical-slider-example.component.html new file mode 100644 index 00000000000..a5ea568a955 --- /dev/null +++ b/libs/docs/core/slider/examples/vertical/vertical-slider-example.component.html @@ -0,0 +1,43 @@ +
+

+ To use Range Slider need to set [mode] as range (by default [mode] as + single) +

+
+ +
+ +

+ Min value: {{ value[0] }} +
+ Max value: {{ value[1] }} +

+
+
+ +
+

Range slided with Custom Values.

+
+ + +
+ +

+ Min value: {{ value2[0] | json }} +
+ Max value: {{ value2[1] | json }} +

+
diff --git a/libs/docs/core/slider/examples/vertical/vertical-slider-example.component.ts b/libs/docs/core/slider/examples/vertical/vertical-slider-example.component.ts new file mode 100644 index 00000000000..0eafc3c08d3 --- /dev/null +++ b/libs/docs/core/slider/examples/vertical/vertical-slider-example.component.ts @@ -0,0 +1,27 @@ +import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; +import { SliderCustomValue } from '@fundamental-ngx/core/slider'; + +@Component({ + selector: 'fd-vertical-slider-example', + templateUrl: './vertical-slider-example.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class VerticalSliderExampleComponent { + value = [20, 70]; + + customValues: SliderCustomValue[] = [ + { value: 1609452000000, label: 'Jan 1' }, + { value: 1609538400000, label: 'Jan 2' }, + { value: 1609624800000, label: 'Jan 3' }, + { value: 1609711200000, label: 'Jan 4' }, + { value: 1609797600000, label: 'Jan 5' }, + { value: 1609884000000, label: 'Jan 6' }, + { value: 1609970400000, label: 'Jan 7' }, + { value: 1610056800000, label: 'Jan 8' }, + { value: 1610143200000, label: 'Jan 9' }, + { value: 1610229600000, label: 'Jan 10' } + ]; + + value2 = [this.customValues[4], this.customValues[6]]; +} diff --git a/libs/docs/core/slider/slider-docs.component.html b/libs/docs/core/slider/slider-docs.component.html index da05ee0570f..3851e29b925 100644 --- a/libs/docs/core/slider/slider-docs.component.html +++ b/libs/docs/core/slider/slider-docs.component.html @@ -46,6 +46,12 @@
+Vertical slider + + + + + Playground Area

Value: {{ value | json }}

diff --git a/libs/docs/core/slider/slider-docs.component.ts b/libs/docs/core/slider/slider-docs.component.ts index b828014992f..ca2119b058d 100644 --- a/libs/docs/core/slider/slider-docs.component.ts +++ b/libs/docs/core/slider/slider-docs.component.ts @@ -38,7 +38,8 @@ export class SliderDocsComponent { showTicks: true, showTicksLabels: true, mode: 'single', - disabled: false + disabled: false, + vertical: false } }; diff --git a/libs/docs/core/toolbar/examples/toolbar-overflow-grouping-example.component.html b/libs/docs/core/toolbar/examples/toolbar-overflow-grouping-example.component.html index 73fb1e66c08..878c7d21d90 100644 --- a/libs/docs/core/toolbar/examples/toolbar-overflow-grouping-example.component.html +++ b/libs/docs/core/toolbar/examples/toolbar-overflow-grouping-example.component.html @@ -1,4 +1,4 @@ - + diff --git a/libs/docs/core/toolbar/examples/toolbar-overflow-priority-example.component.html b/libs/docs/core/toolbar/examples/toolbar-overflow-priority-example.component.html index 4958cee8f07..7a90412e66f 100644 --- a/libs/docs/core/toolbar/examples/toolbar-overflow-priority-example.component.html +++ b/libs/docs/core/toolbar/examples/toolbar-overflow-priority-example.component.html @@ -1,4 +1,4 @@ - + diff --git a/libs/docs/core/toolbar/examples/toolbar-separator-example.component.html b/libs/docs/core/toolbar/examples/toolbar-separator-example.component.html index 520e51159a8..b87bbae7070 100644 --- a/libs/docs/core/toolbar/examples/toolbar-separator-example.component.html +++ b/libs/docs/core/toolbar/examples/toolbar-separator-example.component.html @@ -1,4 +1,4 @@ - + diff --git a/libs/docs/core/toolbar/examples/toolbar-size-example.component.html b/libs/docs/core/toolbar/examples/toolbar-size-example.component.html index 585e67d50a8..a76094a7cc7 100644 --- a/libs/docs/core/toolbar/examples/toolbar-size-example.component.html +++ b/libs/docs/core/toolbar/examples/toolbar-size-example.component.html @@ -1,9 +1,9 @@ - +
- + diff --git a/libs/docs/platform/approval-flow/e2e/approval-flow.e2e-spec.ts b/libs/docs/platform/approval-flow/e2e/approval-flow.e2e-spec.ts index ee294a6d20b..0e124bfd1f6 100644 --- a/libs/docs/platform/approval-flow/e2e/approval-flow.e2e-spec.ts +++ b/libs/docs/platform/approval-flow/e2e/approval-flow.e2e-spec.ts @@ -272,6 +272,7 @@ describe('Approval flow', () => { await click(afDefaultExample + editExampleButton); await waitForElDisplayed(addNode); await click(addNode, 1); + await waitForElDisplayed(detailsDialogParallelSerialSelect); await click(detailsDialogParallelSerialSelect); await click(detailsDialogParallelSerialSelectOption); await click(detailsDialogUserTeamButton); @@ -295,6 +296,7 @@ describe('Approval flow', () => { (await browserIsFirefox()) ? await click(addNode, 2) : await click(addNode, 1); await click(detailsDialogParallelSerialSelect); await click(detailsDialogParallelSerialSelectOption, 1); + await waitForElDisplayed(detailsDialogUserTeamButton); await click(detailsDialogUserTeamButton); await pause(500); await waitForElDisplayed(detailsDialogTeamMemberCheckBox); @@ -308,7 +310,7 @@ describe('Approval flow', () => { await expect(approvalFlowNodeCountBefore).toBe(approvalFlowNodeCountAfter - 1); }); - it('should be able to add node in serial using top bar action menu', async () => { + xit('should be able to add node in serial using top bar action menu', async () => { await scrollIntoView(afDefaultExample); const approvalFlowNodeCountBefore = await getElementArrayLength(afDefaultExample + approvalFlowNode); await click(afDefaultExample + editExampleButton); @@ -317,6 +319,7 @@ describe('Approval flow', () => { await click(approvalFlowNodeCheckbox, 3); await waitForElDisplayed(topActionButtons); await click(topActionButtons); + await waitForElDisplayed(detailsDialogUserTeamButton); await click(detailsDialogUserTeamButton); await pause(500); await waitForElDisplayed(detailsDialogTeamMemberCheckBox); @@ -337,6 +340,7 @@ describe('Approval flow', () => { await click(approvalFlowNodeActionMenu, 3); await waitForElDisplayed(approvalFlowNodeActionMenuItem); await click(approvalFlowNodeActionMenuItem); + await waitForElDisplayed(detailsDialogUserTeamButton); await click(detailsDialogUserTeamButton); await pause(500); await waitForElDisplayed(detailsDialogTeamMemberCheckBox); @@ -360,6 +364,7 @@ describe('Approval flow', () => { await pause(500); await waitForElDisplayed(approverOptionListItem); await click(approverOptionListItem, 2); + await waitForElDisplayed(detailsDialogUserTeamButton); await click(detailsDialogUserTeamButton); await pause(500); await click(radioButton); @@ -392,6 +397,7 @@ describe('Approval flow', () => { await pause(500); await waitForElDisplayed(approverOptionListItem); await click(approverOptionListItem, 1); + await waitForElDisplayed(detailsDialogUserTeamButton); await click(detailsDialogUserTeamButton); await pause(500); await click(radioButton); @@ -413,7 +419,7 @@ describe('Approval flow', () => { } }); - it('should be able to remove node by button', async () => { + xit('should be able to remove node by button', async () => { await scrollIntoView(afDefaultExample); await waitForElDisplayed(afDefaultExample + approvalFlowNode); const approvalFlowNodeCountBefore = await getElementArrayLength(afDefaultExample + approvalFlowNode); @@ -459,6 +465,7 @@ describe('Approval flow', () => { await waitForElDisplayed(approvalFlowNodeActionMenuItem); await click(approvalFlowNodeActionMenuItem); await waitForElDisplayed(detailsDialog); + await waitForElDisplayed(detailsDialogUserTeamButton); await click(detailsDialogUserTeamButton); await pause(500); await waitForElDisplayed(dialogCheckbox); @@ -479,6 +486,7 @@ describe('Approval flow', () => { await waitForElDisplayed(approvalFlowNodeActionMenuItem); await click(approvalFlowNodeActionMenuItem, 1); await waitForElDisplayed(detailsDialog); + await waitForElDisplayed(detailsDialogUserTeamButton); await click(detailsDialogUserTeamButton); await pause(500); await waitForElDisplayed(dialogCheckbox); diff --git a/libs/docs/platform/checkbox-group/e2e/checkbox-group-page-content.ts b/libs/docs/platform/checkbox-group/e2e/checkbox-group-page-content.ts index e464ddb3177..addf471b6a2 100644 --- a/libs/docs/platform/checkbox-group/e2e/checkbox-group-page-content.ts +++ b/libs/docs/platform/checkbox-group/e2e/checkbox-group-page-content.ts @@ -15,7 +15,6 @@ export const errorTooltipMessage = 'Value is required.'; export const graduationErrorMessage = 'Graduation is mandatory'; export const engineeringErrorMessage = 'Engineering is mandatory'; export const workExpierenceErrorMessage = 'Minimum 5 years of work experience required'; -export const markingsStyle = 'flex'; -export const seasonsOutputLabel = 'Seasons: '; +export const markingsStyle = 'block'; export const currencies = ['AUD', 'INR', 'USD']; export const purchasedItemsArr = ['coffee', 'pen', 'chair']; diff --git a/libs/docs/platform/checkbox-group/e2e/checkbox-group.po.ts b/libs/docs/platform/checkbox-group/e2e/checkbox-group.po.ts index bac171f96d9..b6d8aa310fb 100644 --- a/libs/docs/platform/checkbox-group/e2e/checkbox-group.po.ts +++ b/libs/docs/platform/checkbox-group/e2e/checkbox-group.po.ts @@ -5,30 +5,21 @@ export class CheckboxGroupPO extends PlatformBaseComponentPo { stringValueCheckboxesArr = 'fdp-platform-checkbox-group-list input'; stringValueCheckboxLabelArr = 'fdp-platform-checkbox-group-list fd-checkbox label'; - stringValuecheckboxGroupLabelsArr = 'fdp-platform-checkbox-group-list .fd-form-label'; - stringValuecheckboxGroupsArr = 'fdp-platform-checkbox-group-list fd-form-group'; stringValueoutputLabelsArr = 'fdp-platform-checkbox-group-list > span'; - winterCheckbox = '.fd-checkbox__label[for=seasons0]'; + winterCheckbox = '.fd-checkbox__label[for=seasons0] span.fd-checkbox__checkmark'; objectValueCheckboxesArr = 'fdp-platform-checkbox-group-list-object input'; objectValueCheckboxLabelArr = 'fdp-platform-checkbox-group-list-object fd-checkbox label'; - objectValuecheckboxGroupLabelsArr = 'fdp-platform-checkbox-group-list-object .fd-form-label'; - objectValuecheckboxGroupsArr = 'fdp-platform-checkbox-group-list-object fd-form-group'; objectValueoutputLabelsArr = 'fdp-platform-checkbox-group-list-object > span'; projectedValueCheckboxesArr = 'fdp-platform-checkbox-group-content-checkbox input'; projectedValueCheckboxLabelArr = 'fdp-platform-checkbox-group-content-checkbox fd-checkbox label'; - projectedValuecheckboxGroupLabelsArr = 'fdp-platform-checkbox-group-content-checkbox .fd-form-label'; - projectedValuecheckboxGroupsArr = 'fdp-platform-checkbox-group-content-checkbox fd-form-group'; projectValueoutputLabelsArr = 'fdp-platform-checkbox-group-content-checkbox > span'; formValidationCheckboxesArr = 'fdp-platform-checkbox-group-examples input'; formValidationCheckboxLabelArr = 'fdp-platform-checkbox-group-examples fd-checkbox label'; - formValidationcheckboxGroupLabelsArr = 'fdp-platform-checkbox-group-examples .fd-form-label'; - formValidationcheckboxGroupsArr = 'fdp-platform-checkbox-group-examples fd-form-group'; formvalidationValueoutputLabelsArr = 'fdp-platform-checkbox-group-examples > span'; errorTooltip = '.fd-form-message span'; - sectiontitle = 'fdp-platform-checkbox-group-examples h3'; async open(): Promise { await super.open(this.url); diff --git a/libs/docs/platform/checkbox/e2e/checkbox-page-contents.ts b/libs/docs/platform/checkbox/e2e/checkbox-page-contents.ts index 0b16068560f..1265b8f32e8 100644 --- a/libs/docs/platform/checkbox/e2e/checkbox-page-contents.ts +++ b/libs/docs/platform/checkbox/e2e/checkbox-page-contents.ts @@ -2,4 +2,4 @@ export const disabledCheckboxTitle = 'It is a disabled checkbox'; export const a11yCheckboxAriaLabel = 'checkbox using aria-label'; export const a11yCheckboxAriaLabelledBy = 'checkboxLable'; export const checkboxErrorTooltip = 'Value is required'; -export const markingDisplayStyle = 'flex'; +export const markingDisplayStyle = 'block'; diff --git a/libs/docs/platform/checkbox/e2e/checkbox.po.ts b/libs/docs/platform/checkbox/e2e/checkbox.po.ts index de555ec4772..865138f0f04 100644 --- a/libs/docs/platform/checkbox/e2e/checkbox.po.ts +++ b/libs/docs/platform/checkbox/e2e/checkbox.po.ts @@ -4,20 +4,14 @@ export class CheckboxPO extends PlatformBaseComponentPo { url = '/checkbox'; binaryTempCheckbox = 'fdp-platform-binary-checkbox input'; - binaryTempCheckboxLable = 'fdp-platform-binary-checkbox label.fd-checkbox__label'; disabledBinaryCheckbox = 'fdp-platform-binary-checkbox input#disabled'; checkboxWithoutForm = 'fdp-platform-binary-checkbox-no-form input'; disabledCheckboxWithoutForm = 'fdp-platform-binary-checkbox-no-form fdp-checkbox:nth-of-type(5) input'; checkboxWithValue = 'fdp-platform-multiselect-checkbox input'; tristateCheckboxes = 'fdp-platform-tristate-checkbox input'; - tristateCheckboxParis = '.fd-checkbox__label[for=paris]'; - acceptAllCheckbox = 'fdp-platform-tristate-checkbox #acceptAll'; - termsAndConditionsCheckbox = 'fdp-platform-tristate-checkbox #termsAndConditions'; - marketingCheckbox = 'fdp-platform-tristate-checkbox #marketing'; - newsletterCheckbox = 'fdp-platform-tristate-checkbox #newsletter'; + tristateCheckboxParis = '.fd-checkbox__label[for=paris] span.fd-checkbox__checkmark'; errorCheckboxes = 'fdp-platform-checkbox-error-handling .fd-checkbox__label'; presenceCheckbox = 'fdp-platform-checkbox-error-handling #presence'; - errorExampleTitle = 'fdp-platform-checkbox-error-handling h3'; submitBtn = 'fdp-platform-checkbox-error-handling button'; errorTooltip = '[type="error"] span'; accessibilityCheckboxes = 'fdp-platform-checkbox-a11y input'; diff --git a/libs/docs/platform/message-popover/examples/default/message-popover-default-example.component.html b/libs/docs/platform/message-popover/examples/default/message-popover-default-example.component.html index 6bd0f9ee988..792a5f71471 100644 --- a/libs/docs/platform/message-popover/examples/default/message-popover-default-example.component.html +++ b/libs/docs/platform/message-popover/examples/default/message-popover-default-example.component.html @@ -6,6 +6,7 @@ @@ -17,6 +18,7 @@ type="email" email [(ngModel)]="templateFormValue.input2" + [errorTypes]="{ required: { heading: 'Required field empty', type: 'information' } }" [required]="true" name="input2" > diff --git a/libs/docs/platform/message-popover/examples/default/message-popover-default-example.component.ts b/libs/docs/platform/message-popover/examples/default/message-popover-default-example.component.ts index e35019ca138..f36ab558011 100644 --- a/libs/docs/platform/message-popover/examples/default/message-popover-default-example.component.ts +++ b/libs/docs/platform/message-popover/examples/default/message-popover-default-example.component.ts @@ -1,4 +1,4 @@ -import { Component, ChangeDetectionStrategy, ViewChild } from '@angular/core'; +import { Component, ChangeDetectionStrategy, ViewChild, ViewEncapsulation } from '@angular/core'; import { AbstractControl, AsyncValidatorFn, @@ -13,7 +13,8 @@ import { delay, Observable, of } from 'rxjs'; @Component({ selector: 'fdp-message-popover-default-example', templateUrl: './message-popover-default-example.component.html', - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None }) export class MessagePopoverDefaultExampleComponent { templateFormValue = { diff --git a/libs/docs/platform/message-popover/examples/form-generator/form-generator-component-example.component.ts b/libs/docs/platform/message-popover/examples/form-generator/form-generator-component-example.component.ts index 51611b698b6..3004805f5fa 100644 --- a/libs/docs/platform/message-popover/examples/form-generator/form-generator-component-example.component.ts +++ b/libs/docs/platform/message-popover/examples/form-generator/form-generator-component-example.component.ts @@ -1,4 +1,4 @@ -import { Component, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ViewChild, ViewEncapsulation } from '@angular/core'; import { Validators } from '@angular/forms'; import { DATE_TIME_FORMATS, @@ -24,6 +24,8 @@ export const dummyAwaitablePromise = (timeout = 200): Promise => @Component({ selector: 'fdp-message-popover-form-generator-example', templateUrl: './form-generator-component-example.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, providers: [ // Note that this is usually provided in the root of your application. // Due to the limit of this example we must provide it on this level. diff --git a/libs/docs/platform/schema/schemas/slider-schema.ts b/libs/docs/platform/schema/schemas/slider-schema.ts index baee51cc445..68e8be9459c 100644 --- a/libs/docs/platform/schema/schemas/slider-schema.ts +++ b/libs/docs/platform/schema/schemas/slider-schema.ts @@ -30,6 +30,9 @@ export const sliderSchema = { }, disabled: { type: 'boolean' + }, + vertical: { + type: 'boolean' } } } diff --git a/libs/docs/platform/slider/slider-docs.component.html b/libs/docs/platform/slider/slider-docs.component.html index 84fa83e7331..2aa691f02f7 100644 --- a/libs/docs/platform/slider/slider-docs.component.html +++ b/libs/docs/platform/slider/slider-docs.component.html @@ -61,6 +61,7 @@ [showTicksLabels]="data.properties.showTicksLabels" [mode]="data.properties.mode" [hideProgressBar]="data.properties.hideProgressBar" + [vertical]="data.properties.vertical" >

Value: {{ value | json }}

diff --git a/libs/docs/platform/slider/slider-docs.component.ts b/libs/docs/platform/slider/slider-docs.component.ts index 597b5919306..a5a20dc2141 100644 --- a/libs/docs/platform/slider/slider-docs.component.ts +++ b/libs/docs/platform/slider/slider-docs.component.ts @@ -37,7 +37,8 @@ export class PlatformSliderDocsComponent { showTicks: true, showTicksLabels: true, mode: 'single', - disabled: false + disabled: false, + vertical: false } }; diff --git a/libs/fn/src/lib/list/list-item/list-item.component.ts b/libs/fn/src/lib/list/list-item/list-item.component.ts new file mode 100644 index 00000000000..cc0497002d2 --- /dev/null +++ b/libs/fn/src/lib/list/list-item/list-item.component.ts @@ -0,0 +1,112 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ContentChild, + ElementRef, + HostBinding, + Inject, + Input, + Optional, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; +import { + FdkDisabledProvider, + FdkReadonlyProvider, + FocusableItemDirective, + SelectableItemToken, + SelectionService +} from '@fundamental-ngx/cdk/utils'; +import { + FN_LIST_ACTIONS, + FN_LIST_BYLINE, + FN_LIST_CHECKBOX, + FN_LIST_ICON, + FN_LIST_POSTFIX, + FN_LIST_PREFIX, + FN_LIST_TITLE +} from '../list.tokens'; +import { coerceBoolean, TemplateRefProviderToken } from '@fundamental-ngx/fn/utils'; +import { CheckboxContext } from '../list-item-checkbox.directive'; +import { ListComponent } from '../list/list.component'; +import { distinctUntilChanged, map, Observable } from 'rxjs'; +import { BooleanInput, coerceArray } from '@angular/cdk/coercion'; + +@Component({ + selector: 'fn-list-item, [fn-list-item]', + templateUrl: './list-item.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + '[class.fn-list__item]': 'true' + }, + providers: [FdkDisabledProvider, FdkReadonlyProvider], + hostDirectives: [FocusableItemDirective] +}) +export class ListItemComponent { + @Input() + @HostBinding('class.fn-list__item--info-bar') + @coerceBoolean + infoBar!: BooleanInput; + + @ContentChild(FN_LIST_CHECKBOX) + checkboxProvider?: TemplateRefProviderToken; + @ContentChild(FN_LIST_PREFIX) + prefixProvider?: TemplateRefProviderToken; + @ContentChild(FN_LIST_ICON) + iconProvider?: TemplateRefProviderToken; + @ContentChild(FN_LIST_TITLE) + titleProvider?: TemplateRefProviderToken; + @ContentChild(FN_LIST_BYLINE) + bylineProvider?: TemplateRefProviderToken; + @ContentChild(FN_LIST_ACTIONS) + actionsProvider?: TemplateRefProviderToken; + @ContentChild(FN_LIST_POSTFIX) + postfixProvider?: TemplateRefProviderToken; + + checkboxContext$!: Observable; + + readonly!: boolean; + disabled!: boolean; + + constructor( + private _cd: ChangeDetectorRef, + @Optional() private _selectionService: SelectionService, + @Optional() @Inject(SelectableItemToken) private _selectableItem: SelectableItemToken, + @Optional() @Inject(ListComponent) private _listComponent: ListComponent | null, + private _elementRef: ElementRef, + private _disabledProvider: FdkDisabledProvider, + private _readonlyProvider: FdkReadonlyProvider + ) { + if (this._selectionService && this._selectableItem) { + this.checkboxContext$ = this._selectionService.value$.pipe( + map((v) => coerceArray(v)), + map((value: any[]) => value.includes(this._selectableItem.value)), + distinctUntilChanged(), + map((selected) => ({ + $implicit: selected, + update: this.toggleItemSelection + })) + ); + } + } + + get byline(): boolean { + return !!this._listComponent?.byline; + } + + $templateRef = (template: any): TemplateRef => template; + + public elementRef(): ElementRef { + return this._elementRef; + } + + private toggleItemSelection = (isSelected: boolean): void => { + if (isSelected) { + this._selectionService?.selectItem(this._selectableItem); + } else { + this._selectionService?.deselectItem(this._selectableItem); + } + }; +} diff --git a/libs/platform/src/lib/form/checkbox-group/checkbox-group.component.ts b/libs/platform/src/lib/form/checkbox-group/checkbox-group.component.ts index 2b06c83968c..3ca53f79cc1 100644 --- a/libs/platform/src/lib/form/checkbox-group/checkbox-group.component.ts +++ b/libs/platform/src/lib/form/checkbox-group/checkbox-group.component.ts @@ -17,7 +17,7 @@ import { ViewChildren, ViewEncapsulation } from '@angular/core'; -import { ControlContainer, NgControl, NgForm } from '@angular/forms'; +import { ControlContainer, NgControl, NgForm, FormsModule } from '@angular/forms'; import { FD_FORM_FIELD, FD_FORM_FIELD_CONTROL } from '@fundamental-ngx/cdk/forms'; import { RangeSelector } from '@fundamental-ngx/cdk/utils'; import { SelectionModel } from '@angular/cdk/collections'; @@ -33,6 +33,8 @@ import { PlatformFormFieldControl } from '@fundamental-ngx/platform/shared'; import { CheckboxComponent } from '../checkbox/checkbox.component'; +import { NgIf, NgFor, NgTemplateOutlet } from '@angular/common'; +import { FormGroupModule } from '@fundamental-ngx/core/form'; /** * Checkbox group implementation based on the @@ -44,7 +46,9 @@ import { CheckboxComponent } from '../checkbox/checkbox.component'; templateUrl: './checkbox-group.component.html', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - providers: [{ provide: FD_FORM_FIELD_CONTROL, useExisting: forwardRef(() => CheckboxGroupComponent), multi: true }] + providers: [{ provide: FD_FORM_FIELD_CONTROL, useExisting: forwardRef(() => CheckboxGroupComponent), multi: true }], + standalone: true, + imports: [FormGroupModule, NgIf, NgFor, NgTemplateOutlet, CheckboxComponent, FormsModule] }) export class CheckboxGroupComponent extends InLineLayoutCollectionBaseInput { /** diff --git a/libs/platform/src/lib/form/checkbox-group/checkbox-group.module.ts b/libs/platform/src/lib/form/checkbox-group/checkbox-group.module.ts index 60177afe36a..f6858260595 100644 --- a/libs/platform/src/lib/form/checkbox-group/checkbox-group.module.ts +++ b/libs/platform/src/lib/form/checkbox-group/checkbox-group.module.ts @@ -1,15 +1,11 @@ import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; import { FormGroupModule } from '@fundamental-ngx/core/form'; import { ContentDensityModule } from '@fundamental-ngx/core/content-density'; import { CheckboxGroupComponent } from './checkbox-group.component'; -import { PlatformCheckboxModule } from '../checkbox/checkbox.module'; @NgModule({ - imports: [CommonModule, PlatformCheckboxModule, FormsModule, FormGroupModule, ContentDensityModule], - exports: [CheckboxGroupComponent, ContentDensityModule], - declarations: [CheckboxGroupComponent] + imports: [FormGroupModule, ContentDensityModule, CheckboxGroupComponent], + exports: [CheckboxGroupComponent, ContentDensityModule] }) export class PlatformCheckboxGroupModule {} diff --git a/libs/platform/src/lib/form/checkbox/checkbox.component.html b/libs/platform/src/lib/form/checkbox/checkbox.component.html index 9b1b163327e..e5bd2a84d3a 100644 --- a/libs/platform/src/lib/form/checkbox/checkbox.component.html +++ b/libs/platform/src/lib/form/checkbox/checkbox.component.html @@ -15,6 +15,7 @@ [ariaDescribedBy]="ariaDescribedBy" [ngModel]="value" [standalone]="standalone" + [displayOnly]="displayOnly" (ngModelChange)="onModelChange($event)" (focusChange)="_onFocusChanged($event)" > diff --git a/libs/platform/src/lib/form/checkbox/checkbox.component.ts b/libs/platform/src/lib/form/checkbox/checkbox.component.ts index 86e656b6758..e10e69faa16 100644 --- a/libs/platform/src/lib/form/checkbox/checkbox.component.ts +++ b/libs/platform/src/lib/form/checkbox/checkbox.component.ts @@ -17,11 +17,13 @@ import { ViewChild, ViewEncapsulation } from '@angular/core'; -import { ControlContainer, NgControl, NgForm } from '@angular/forms'; +import { ControlContainer, NgControl, NgForm, FormsModule } from '@angular/forms'; import { FD_FORM_FIELD, FD_FORM_FIELD_CONTROL } from '@fundamental-ngx/cdk/forms'; import { FdCheckboxValues, CheckboxComponent as FdCheckboxComponent } from '@fundamental-ngx/core/checkbox'; import { BaseInput, PlatformFormFieldControl, PlatformFormField } from '@fundamental-ngx/platform/shared'; +import { CheckboxComponent as CoreCheckboxComponent } from '@fundamental-ngx/core/checkbox'; +import { FormItemModule } from '@fundamental-ngx/core/form'; /** Change event object emitted by Platform Checkbox. */ export class PlatformCheckboxChange { @@ -41,7 +43,9 @@ let nextUniqueId = 0; templateUrl: './checkbox.component.html', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - providers: [{ provide: FD_FORM_FIELD_CONTROL, useExisting: forwardRef(() => CheckboxComponent), multi: true }] + providers: [{ provide: FD_FORM_FIELD_CONTROL, useExisting: forwardRef(() => CheckboxComponent), multi: true }], + standalone: true, + imports: [FormItemModule, CoreCheckboxComponent, FormsModule] }) export class CheckboxComponent extends BaseInput implements AfterViewInit { /** @@ -84,6 +88,10 @@ export class CheckboxComponent extends BaseInput implements AfterViewInit { @Input() standalone = false; + /** Whether the checkbox should be rendered in display-only mode. */ + @Input() + displayOnly = false; + /** * Emitting checked event for non-form checkbox */ diff --git a/libs/platform/src/lib/form/checkbox/checkbox.module.ts b/libs/platform/src/lib/form/checkbox/checkbox.module.ts index ac1dd07279a..64f85187157 100644 --- a/libs/platform/src/lib/form/checkbox/checkbox.module.ts +++ b/libs/platform/src/lib/form/checkbox/checkbox.module.ts @@ -1,16 +1,11 @@ -import { FormsModule } from '@angular/forms'; import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { CheckboxModule } from '@fundamental-ngx/core/checkbox'; import { PipeModule } from '@fundamental-ngx/cdk/utils'; import { CheckboxComponent } from './checkbox.component'; import { FormItemModule } from '@fundamental-ngx/core/form'; import { ContentDensityModule } from '@fundamental-ngx/core/content-density'; @NgModule({ - declarations: [CheckboxComponent], - imports: [CommonModule, FormsModule, CheckboxModule, PipeModule, FormItemModule, ContentDensityModule], + imports: [PipeModule, FormItemModule, ContentDensityModule, CheckboxComponent], exports: [CheckboxComponent, ContentDensityModule] }) export class PlatformCheckboxModule {} diff --git a/libs/platform/src/lib/message-popover/components/message-popover-form-wrapper/message-popover-form-wrapper.component.ts b/libs/platform/src/lib/message-popover/components/message-popover-form-wrapper/message-popover-form-wrapper.component.ts index 7a23168bc3c..986b70d1392 100644 --- a/libs/platform/src/lib/message-popover/components/message-popover-form-wrapper/message-popover-form-wrapper.component.ts +++ b/libs/platform/src/lib/message-popover/components/message-popover-form-wrapper/message-popover-form-wrapper.component.ts @@ -10,8 +10,8 @@ import { QueryList, ViewEncapsulation } from '@angular/core'; -import { AbstractControl, ControlContainer, FormGroup, FormGroupDirective, NgForm, NgModel } from '@angular/forms'; -import { FormStates } from '@fundamental-ngx/cdk/forms'; +import { AbstractControl, ControlContainer, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; +import { FD_FORM_FIELD_CONTROL, FormStates } from '@fundamental-ngx/cdk/forms'; import { Nullable } from '@fundamental-ngx/cdk/utils'; import { FormFieldErrorDirectiveContext, @@ -38,18 +38,16 @@ export type MessagePopoverForm = NgForm | FormGroupDirective; templateUrl: './message-popover-form-wrapper.component.html', exportAs: 'messagePopoverWrapper', changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None + encapsulation: ViewEncapsulation.None, + standalone: true }) export class MessagePopoverFormWrapperComponent implements MessagePopoverWrapper, AfterViewInit, OnDestroy { /** @hidden */ @ContentChildren(ControlContainer, { descendants: true }) private readonly _projectedForm: QueryList; - /** @hidden */ - @ContentChildren(NgModel, { descendants: true }) - private readonly _projectedFormItems!: QueryList; /** @hidden */ - @ContentChildren(PlatformFormFieldControl, { descendants: true }) + @ContentChildren(FD_FORM_FIELD_CONTROL, { descendants: true }) private readonly _projectedFormFieldControls!: QueryList; /** @hidden */ @@ -121,9 +119,7 @@ export class MessagePopoverFormWrapperComponent implements MessagePopoverWrapper if (this._ngForms.length > 0) { return; } - this._ngForms = this._projectedForm?.toArray(); - this._startListeningForErrors(); - this._projectedForm?.changes.pipe(takeUntilDestroyed(this._destroyRef)).subscribe(() => { + this._projectedForm?.changes.pipe(startWith(null), takeUntilDestroyed(this._destroyRef)).subscribe(() => { this._ngForms = this._projectedForm.toArray(); this._startListeningForErrors(); }); @@ -248,11 +244,16 @@ export class MessagePopoverFormWrapperComponent implements MessagePopoverWrapper .forEach((errorKey) => { const errorObj = control.errors![errorKey]; - const errorTextModel = this._getConfigErrorModel(errorKey); + const errorTextModel = this._getConfigErrorModel(errorKey, field.errorTypes); + const errorConfig = field?.errorTypes ? field?.errorTypes[errorKey] : null; + const errorState = + errorConfig && this._isAdvancedError(errorConfig) && errorConfig.type + ? errorConfig.type + : 'error'; const error: MessagePopoverEntry = { name: controlName, type: errorTextModel.type, - state: convertFormState('error'), + state: convertFormState(errorState), fieldName: field?.label ?? '', element: field?.elementRef, heading: { @@ -435,8 +436,12 @@ export class MessagePopoverFormWrapperComponent implements MessagePopoverWrapper } /** @hidden */ - private _getConfigErrorModel(errorKey: string): MessagePopoverErrorConfig { - const configError = this._config.errors[errorKey]; + private _getConfigErrorModel( + errorKey: string, + customErrorTypes?: MessagePopoverConfig['errors'] + ): MessagePopoverErrorConfig { + const configError = + customErrorTypes && customErrorTypes[errorKey] ? customErrorTypes[errorKey] : this._config.errors[errorKey]; const isPlainError = !this._isAdvancedError(configError); const errorType = isPlainError ? 'error' : configError.type; const headingMessage = isPlainError ? configError : configError.heading; diff --git a/libs/platform/src/lib/message-popover/components/message-view/message-view.component.ts b/libs/platform/src/lib/message-popover/components/message-view/message-view.component.ts index 8a0dd77b357..c703283de50 100644 --- a/libs/platform/src/lib/message-popover/components/message-view/message-view.component.ts +++ b/libs/platform/src/lib/message-popover/components/message-view/message-view.component.ts @@ -1,5 +1,5 @@ import { animate, keyframes, state, style, transition, trigger } from '@angular/animations'; -import { DOCUMENT } from '@angular/common'; +import { DOCUMENT, NgTemplateOutlet, NgFor, NgIf } from '@angular/common'; import { AfterViewInit, ChangeDetectionStrategy, @@ -18,6 +18,12 @@ import { Nullable, resizeObservable, TabbableElementService } from '@fundamental import { debounceTime } from 'rxjs'; import { MessagePopoverEntry, MessagePopoverErrorGroup } from '../../models/message-popover-entry.interface'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FdTranslatePipe } from '@fundamental-ngx/i18n'; +import { LinkComponent } from '@fundamental-ngx/core/link'; +import { ObjectStatusModule } from '@fundamental-ngx/core/object-status'; +import { ListModule } from '@fundamental-ngx/core/list'; +import { ScrollbarDirective } from '@fundamental-ngx/core/scrollbar'; +import { CdkScrollable } from '@angular/cdk/overlay'; @Component({ selector: 'fdp-message-view', @@ -85,6 +91,18 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; animate('0.1s .1s cubic-bezier(0, 0, 0.2, 1)') ]) ]) + ], + standalone: true, + imports: [ + NgTemplateOutlet, + CdkScrollable, + ScrollbarDirective, + ListModule, + NgFor, + NgIf, + ObjectStatusModule, + LinkComponent, + FdTranslatePipe ] }) export class MessageViewComponent implements AfterViewInit { diff --git a/libs/platform/src/lib/message-popover/directives/message-popover-form-item.directive.ts b/libs/platform/src/lib/message-popover/directives/message-popover-form-item.directive.ts index 3438e8498bf..405ce1f8de4 100644 --- a/libs/platform/src/lib/message-popover/directives/message-popover-form-item.directive.ts +++ b/libs/platform/src/lib/message-popover/directives/message-popover-form-item.directive.ts @@ -1,14 +1,20 @@ import { Directive, ElementRef, Input, Optional } from '@angular/core'; import { NgControl } from '@angular/forms'; +import { MessagePopoverConfig } from '../default-config'; @Directive({ - selector: '[fdpMessagePopoverFormItem]' + selector: '[fdpMessagePopoverFormItem]', + standalone: true }) export class MessagePopoverFormItemDirective { /** Form item name. */ @Input('fdpMessagePopoverFormItem') label: string; + /** Error type definition. */ + @Input() + errorTypes: MessagePopoverConfig['errors']; + /** @hidden */ - constructor(public elementRef: ElementRef, @Optional() public control: NgControl) {} + constructor(public readonly elementRef: ElementRef, @Optional() public readonly control: NgControl) {} } diff --git a/libs/platform/src/lib/message-popover/message-popover.component.html b/libs/platform/src/lib/message-popover/message-popover.component.html index d94d3cace7b..8ab75237bf7 100644 --- a/libs/platform/src/lib/message-popover/message-popover.component.html +++ b/libs/platform/src/lib/message-popover/message-popover.component.html @@ -38,7 +38,7 @@ +
diff --git a/libs/platform/src/lib/slider/jest.config.ts b/libs/platform/src/lib/slider/jest.config.ts new file mode 100644 index 00000000000..b768606ca3f --- /dev/null +++ b/libs/platform/src/lib/slider/jest.config.ts @@ -0,0 +1,9 @@ +import baseConfig from '../../../../../jest.config.base'; + +export default { + ...baseConfig, + displayName: 'platform-slider', + preset: '../../../../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts', '../../../../../jest-extended-matchers.ts'], + coverageDirectory: '../../../../../dist/coverage/platform-slider' +}; diff --git a/libs/platform/src/lib/slider/karma.conf.js b/libs/platform/src/lib/slider/karma.conf.js deleted file mode 100644 index 3bf095adbc3..00000000000 --- a/libs/platform/src/lib/slider/karma.conf.js +++ /dev/null @@ -1,17 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -const { join } = require('path'); -const getBaseKarmaConfig = require('../../../../../karma.conf'); - -module.exports = function (config) { - const baseConfig = getBaseKarmaConfig(); - config.set({ - ...baseConfig, - coverageIstanbulReporter: { - ...baseConfig.coverageIstanbulReporter, - dir: join(__dirname, '../../../../../coverage/libs/platform/slider') - }, - browsers: ['ChromeHeadless'] - }); -}; diff --git a/libs/platform/src/lib/slider/project.json b/libs/platform/src/lib/slider/project.json index 14789023c12..3c5ce0f67c7 100644 --- a/libs/platform/src/lib/slider/project.json +++ b/libs/platform/src/lib/slider/project.json @@ -19,19 +19,25 @@ } } }, - "test": { - "executor": "@angular-devkit/build-angular:karma", - "options": { - "main": "libs/platform/src/lib/slider/test.ts", - "tsConfig": "libs/platform/src/lib/slider/tsconfig.spec.json", - "karmaConfig": "libs/platform/src/lib/slider/karma.conf.js" - } - }, "lint": { "executor": "@nx/linter:eslint", "options": { "lintFilePatterns": ["libs/platform/src/lib/slider/**/*.ts", "libs/platform/src/lib/slider/**/*.html"] } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/platform/src/lib/slider/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } } }, "tags": ["type:lib", "scope:fdp"], diff --git a/libs/platform/src/lib/slider/slider.component.html b/libs/platform/src/lib/slider/slider.component.html index 5baffc5ef68..0df62d2988e 100644 --- a/libs/platform/src/lib/slider/slider.component.html +++ b/libs/platform/src/lib/slider/slider.component.html @@ -16,5 +16,6 @@ [tooltipMode]="tooltipMode" [hideProgressBar]="hideProgressBar" [ngModel]="value" + [vertical]="vertical" (ngModelChange)="_onModelChange($event)" > diff --git a/libs/platform/src/lib/slider/slider.component.spec.ts b/libs/platform/src/lib/slider/slider.component.spec.ts index f7ad36fec9c..3000374019b 100644 --- a/libs/platform/src/lib/slider/slider.component.spec.ts +++ b/libs/platform/src/lib/slider/slider.component.spec.ts @@ -3,7 +3,7 @@ import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { SliderCustomValue } from '@fundamental-ngx/core/slider'; +import { SliderComponent, SliderCustomValue } from '@fundamental-ngx/core/slider'; import { FdpFormGroupModule } from '@fundamental-ngx/platform/form'; import { PlatformSliderModule } from './slider.module'; @@ -22,6 +22,7 @@ import { PlatformSliderModule } from './slider.module'; tooltipMode="readonly" name="value1" formControlName="value1" + [vertical]="vertical" > @@ -65,7 +66,9 @@ import { PlatformSliderModule } from './slider.module'; `, - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [PlatformSliderModule, ReactiveFormsModule, FdpFormGroupModule] }) class TestSliderComponent { customValues: SliderCustomValue[] = [ @@ -113,6 +116,8 @@ class TestSliderComponent { get value6(): number { return this.customForm.controls['value6'].value!; } + + vertical = false; } describe('PlatformSliderComponent', () => { @@ -122,8 +127,7 @@ describe('PlatformSliderComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - declarations: [TestSliderComponent], - imports: [PlatformSliderModule, ReactiveFormsModule, FdpFormGroupModule] + imports: [PlatformSliderModule, ReactiveFormsModule, FdpFormGroupModule, TestSliderComponent] }).compileComponents(); }); @@ -133,7 +137,7 @@ describe('PlatformSliderComponent', () => { await whenStable(fixture); - bodyClientWidth = fixture.nativeElement.ownerDocument.body.clientWidth; + bodyClientWidth = 100; }); it('should create', () => { @@ -191,6 +195,19 @@ describe('PlatformSliderComponent', () => { }); it('should display 11 ticks marks and 11 Labels', () => { + const sliderComponent = fixture.debugElement.query(By.directive(SliderComponent)).componentInstance; + jest.spyOn(sliderComponent.trackEl.nativeElement as any, 'getBoundingClientRect').mockImplementation(() => ({ + height: 2, + width: bodyClientWidth, + x: 0, + y: 0, + bottom: 0, + left: 0, + right: 0, + top: 0 + })); + (sliderComponent as any)._onResize(); + fixture.detectChanges(); const ticksMarks = fixture.debugElement.queryAll(By.css('.example-1 .fd-slider__tick')); const labels = fixture.debugElement.queryAll(By.css('.example-1 .fd-slider__label')); @@ -199,6 +216,19 @@ describe('PlatformSliderComponent', () => { }); it('should display 11 ticks marks and 6 Labels', () => { + const sliders = fixture.debugElement.queryAll(By.directive(SliderComponent)).map((el) => el.componentInstance); + jest.spyOn(sliders[1].trackEl.nativeElement as any, 'getBoundingClientRect').mockImplementation(() => ({ + height: 2, + width: bodyClientWidth, + x: 0, + y: 0, + bottom: 0, + left: 0, + right: 0, + top: 0 + })); + (sliders[1] as any)._onResize(); + fixture.detectChanges(); const ticksMarks = fixture.debugElement.queryAll(By.css('.example-2 .fd-slider__tick')); const labels = fixture.debugElement.queryAll(By.css('.example-2 .fd-slider__label')); @@ -221,6 +251,14 @@ describe('PlatformSliderComponent', () => { expect(handles.length).toEqual(2); }); + it('should consume vertical state', () => { + const slider = fixture.debugElement.query(By.directive(SliderComponent)); + expect(slider.nativeElement.classList).not.toContain('fd-slider--vertical'); + component.vertical = true; + fixture.detectChanges(); + expect(slider.nativeElement.classList).toContain('fd-slider--vertical'); + }); + // TODO investigate and fix - ticket #4893 xit('range slider second handle should have the ability to be less than the first handle', async () => { const sliderWidth = fixture.debugElement.query(By.css('.example-4 .fd-slider__inner')).nativeElement diff --git a/libs/platform/src/lib/slider/slider.component.ts b/libs/platform/src/lib/slider/slider.component.ts index dd15037c49e..8a5ec01d2d6 100644 --- a/libs/platform/src/lib/slider/slider.component.ts +++ b/libs/platform/src/lib/slider/slider.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/member-ordering */ import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -13,10 +14,10 @@ import { SkipSelf, ViewEncapsulation } from '@angular/core'; -import { ControlContainer, NgControl, NgForm } from '@angular/forms'; +import { ControlContainer, NgControl, NgForm, FormsModule } from '@angular/forms'; import { FD_FORM_FIELD, FD_FORM_FIELD_CONTROL } from '@fundamental-ngx/cdk/forms'; -import { SliderTickMark } from '@fundamental-ngx/core/slider'; +import { SliderTickMark, SliderComponent as SliderComponent_1 } from '@fundamental-ngx/core/slider'; import { BaseInput, PlatformFormField, PlatformFormFieldControl } from '@fundamental-ngx/platform/shared'; export type SliderCustomValue = Omit; @@ -36,7 +37,9 @@ export class SliderChangeEvent { templateUrl: './slider.component.html', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - providers: [{ provide: FD_FORM_FIELD_CONTROL, useExisting: SliderComponent, multi: true }] + providers: [{ provide: FD_FORM_FIELD_CONTROL, useExisting: SliderComponent, multi: true }], + standalone: true, + imports: [SliderComponent_1, FormsModule] }) export class SliderComponent extends BaseInput { /** User's custom classes */ @@ -90,6 +93,10 @@ export class SliderComponent extends BaseInput { /** Hides display of colored progress bar. */ @Input() hideProgressBar = false; + + /** Whether the slider should be rendered vertically. */ + @Input() + vertical = false; /** * Event fired when the state of the slider changes. * *$event* can be used to retrieve the new state of the slider. diff --git a/libs/platform/src/lib/slider/slider.module.ts b/libs/platform/src/lib/slider/slider.module.ts index 401bbfe48cb..e8c988214d7 100644 --- a/libs/platform/src/lib/slider/slider.module.ts +++ b/libs/platform/src/lib/slider/slider.module.ts @@ -1,15 +1,10 @@ import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; - -import { SliderModule } from '@fundamental-ngx/core/slider'; import { ContentDensityModule } from '@fundamental-ngx/core/content-density'; import { SliderComponent } from './slider.component'; @NgModule({ - declarations: [SliderComponent], - imports: [CommonModule, SliderModule, FormsModule, ContentDensityModule], - exports: [SliderComponent, ContentDensityModule] + imports: [ContentDensityModule, SliderComponent], + exports: [SliderComponent] }) export class PlatformSliderModule {} diff --git a/libs/platform/src/lib/slider/src/test-setup.ts b/libs/platform/src/lib/slider/src/test-setup.ts new file mode 100644 index 00000000000..1100b3e8a6e --- /dev/null +++ b/libs/platform/src/lib/slider/src/test-setup.ts @@ -0,0 +1 @@ +import 'jest-preset-angular/setup-jest'; diff --git a/libs/platform/src/lib/slider/test.ts b/libs/platform/src/lib/slider/test.ts deleted file mode 100644 index 62bbfc6f4cd..00000000000 --- a/libs/platform/src/lib/slider/test.ts +++ /dev/null @@ -1,13 +0,0 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'core-js/es/reflect'; -import 'zone.js'; - -import 'zone.js/testing'; -import { getTestBed } from '@angular/core/testing'; -import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { - teardown: { destroyAfterEach: false } -}); diff --git a/libs/platform/src/lib/slider/tsconfig.spec.json b/libs/platform/src/lib/slider/tsconfig.spec.json index 9aa03ce2ad8..de11b03de10 100644 --- a/libs/platform/src/lib/slider/tsconfig.spec.json +++ b/libs/platform/src/lib/slider/tsconfig.spec.json @@ -1,11 +1,11 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "../../out-tsc/spec", - "types": ["jasmine"], - "target": "ES2022", - "useDefineForClassFields": false + "outDir": "../../../../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"] }, - "files": ["./test.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts"] + "files": ["src/test-setup.ts"], + "include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"] } diff --git a/package.json b/package.json index 9d1f6a727b6..6120fb739ff 100644 --- a/package.json +++ b/package.json @@ -47,9 +47,10 @@ "@angular/platform-browser": "16.1.0", "@angular/platform-browser-dynamic": "16.1.0", "@angular/router": "16.1.0", - "@fundamental-styles/cx": "0.28.3", + "@fundamental-styles/cx": "0.30.0-rc.35", + "@fundamental-styles/fn": "0.25.1-rc.16", "@nx/angular": "16.5.5", - "@sap-theming/theming-base-content": "11.6.1", + "@sap-theming/theming-base-content": "11.6.4", "@stackblitz/sdk": "1.8.2", "@swc/helpers": "0.5.1", "compare-versions": "6.0.0", @@ -58,7 +59,7 @@ "fast-deep-equal": "3.1.3", "focus-trap": "7.1.0", "focus-visible": "5.2.0", - "fundamental-styles": "0.28.3", + "fundamental-styles": "0.30.2-rc.6", "highlight.js": "11.7.0", "intl": "1.2.5", "karma-viewport": "1.0.9", diff --git a/yarn.lock b/yarn.lock index c1e886cc358..5dd9530d627 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2232,10 +2232,17 @@ dependencies: "@sap-theming/theming-base-content" "^11.4.0" -"@fundamental-styles/cx@0.28.3": - version "0.28.3" - resolved "https://registry.yarnpkg.com/@fundamental-styles/cx/-/cx-0.28.3.tgz#34bd4364865ae8136221579eb3cc1aacf3aa84b5" - integrity sha512-yT/BfN04r8ZF9crG9QJS+PfPvhYmzy+YtTjmqLPZ1ssbGGm909ytwL4mdZJ+0+ZMzkgZJphtA1U027B0FcBkww== +"@fundamental-styles/cx@0.30.0-rc.35": + version "0.30.0-rc.35" + resolved "https://registry.yarnpkg.com/@fundamental-styles/cx/-/cx-0.30.0-rc.35.tgz#ba746cf0cb7ddb916749c4dee882df8cfeeffc70" + integrity sha512-YCU42FIgSxkhQljCWMn0DoSHEqLYWxUijsq6p6vUu3/orkz4xks1Gs+LWGzyscMOEKs+CWgl6j8i2Ft/7FYgMw== + +"@fundamental-styles/fn@0.25.1-rc.16": + version "0.25.1-rc.16" + resolved "https://registry.yarnpkg.com/@fundamental-styles/fn/-/fn-0.25.1-rc.16.tgz#f79a805aee7cfedc93c8b7cedb161d17ef1f8d00" + integrity sha512-fKtnIw/X6SO9UUlnCxeYFApP8QnTp2ydRTYynvKIb31KR+XD1gkutyqaikzeESj92+m6drQyiJMoknnZ1vdl2g== + dependencies: + fundamental-styles "0.25.1-rc.16" "@hapi/hoek@^9.0.0": version "9.3.0" @@ -3230,10 +3237,10 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@sap-theming/theming-base-content@11.6.1": - version "11.6.1" - resolved "https://registry.yarnpkg.com/@sap-theming/theming-base-content/-/theming-base-content-11.6.1.tgz#1368380b196d5445c7698c7a1a2725366cc1f9e3" - integrity sha512-07e1P2sOEl/CadBP3iejy/IpcHBWzojCNfly6WURM3HZeZLVvfGmLXVv0HQeJYfE8t+wRhDMJhnJvX5fjVH3Nw== +"@sap-theming/theming-base-content@11.6.4": + version "11.6.4" + resolved "https://registry.yarnpkg.com/@sap-theming/theming-base-content/-/theming-base-content-11.6.4.tgz#828b314c5036b054d4bae20243b382e1eb57c7cf" + integrity sha512-ub3W9qRO4Kt1nrXJBvTqUbQjpAhcQNGcIh1CTopVStpGGhEP1a2VPeXxJQPL37FCzIFeN898OBiQgCgQNpVqYQ== "@sap-theming/theming-base-content@^11.4.0": version "11.6.3" @@ -9520,10 +9527,15 @@ functions-have-names@^1.2.2, functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== -fundamental-styles@0.28.3: - version "0.28.3" - resolved "https://registry.yarnpkg.com/fundamental-styles/-/fundamental-styles-0.28.3.tgz#d0044b0ebeba43369982fc8912a50f3d06238160" - integrity sha512-FUhMJXqvQZACTm8PN+6s5RQtnBn6fU5zA+0Y4ZAP92fszVU1ZbuGhmHvOtxs41dcmfUV4UjJ/hW98coKtCY6KA== +fundamental-styles@0.25.1-rc.16: + version "0.25.1-rc.16" + resolved "https://registry.yarnpkg.com/fundamental-styles/-/fundamental-styles-0.25.1-rc.16.tgz#8560a5e1411ac3dfbf6d593ba4cd7b1f3124f3d5" + integrity sha512-/ETPbzv98pMtqT5nNODkamxDBJsDPJ4KG/f5XEAvExvdx0+IvESuZQES6n1F3He1o1zMGLJj4HVqp4UJFiRGsQ== + +fundamental-styles@0.30.2-rc.6: + version "0.30.2-rc.6" + resolved "https://registry.yarnpkg.com/fundamental-styles/-/fundamental-styles-0.30.2-rc.6.tgz#43b5e314eff0c16a0721cf0122ba6fbdb8746dc1" + integrity sha512-vKWZFlijWNNvju5v5Y93sVWGxe3uGicBptbfD+4r0lwInKEU0tQnQx87NfjtfdkPkqzDhmewMWTHI5ujNWXmOw== gauge@^4.0.3: version "4.0.4"