From f7b4413788a850221da43dba5e1e25495d4ed09b Mon Sep 17 00:00:00 2001 From: Badisi Date: Mon, 10 Jun 2024 10:39:17 +0200 Subject: [PATCH] feat(numeric-stepper): first commit --- .github/workflows/ci_test_numeric-stepper.yml | 24 ++ projects/numeric-stepper/ng-package.json | 9 +- projects/numeric-stepper/package.json | 77 +++-- .../src/_numeric-stepper-theme.scss | 55 ++++ .../src/example/example.component.spec.ts | 22 -- .../src/example/example.component.ts | 11 - .../src/example/example.service.spec.ts | 16 - .../src/example/example.service.ts | 6 - projects/numeric-stepper/src/index.ts | 7 +- .../src/numeric-stepper.component.html | 3 + .../src/numeric-stepper.component.scss | 189 +++++++++++ .../src/numeric-stepper.component.ts | 309 ++++++++++++++++++ projects/numeric-stepper/tsconfig.lib.json | 2 +- 13 files changed, 634 insertions(+), 96 deletions(-) create mode 100644 .github/workflows/ci_test_numeric-stepper.yml create mode 100644 projects/numeric-stepper/src/_numeric-stepper-theme.scss delete mode 100644 projects/numeric-stepper/src/example/example.component.spec.ts delete mode 100644 projects/numeric-stepper/src/example/example.component.ts delete mode 100644 projects/numeric-stepper/src/example/example.service.spec.ts delete mode 100644 projects/numeric-stepper/src/example/example.service.ts create mode 100644 projects/numeric-stepper/src/numeric-stepper.component.html create mode 100644 projects/numeric-stepper/src/numeric-stepper.component.scss create mode 100644 projects/numeric-stepper/src/numeric-stepper.component.ts diff --git a/.github/workflows/ci_test_numeric-stepper.yml b/.github/workflows/ci_test_numeric-stepper.yml new file mode 100644 index 00000000..66c18022 --- /dev/null +++ b/.github/workflows/ci_test_numeric-stepper.yml @@ -0,0 +1,24 @@ +name: Test numeric-stepper + +on: + workflow_dispatch: + push: + branches: + - '**' + tags-ignore: + - '**' + paths: + - '.github/workflows/ci_test_numeric-stepper.yml' + - 'projects/numeric-stepper/**' + +concurrency: + group: ci-test-numeric-stepper-group-${{ github.ref }} + cancel-in-progress: true + +jobs: + ci_test_numeric-stepper: + uses: dsi-hug/action/.github/workflows/action.yml@v1 + with: + working-directory: projects/numeric-stepper + runs-on: '["ubuntu-latest", "macos-latest", "windows-latest"]' + node-versions: '[18, 20]' diff --git a/projects/numeric-stepper/ng-package.json b/projects/numeric-stepper/ng-package.json index 162feaf4..1e0dbd2b 100644 --- a/projects/numeric-stepper/ng-package.json +++ b/projects/numeric-stepper/ng-package.json @@ -1,7 +1,14 @@ { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/numeric-stepper", + "assets": [ + { + "input": "src/", + "glob": "_numeric-stepper-theme.scss", + "output": "." + } + ], "lib": { "entryFile": "src/index.ts" } -} \ No newline at end of file +} diff --git a/projects/numeric-stepper/package.json b/projects/numeric-stepper/package.json index 39c47029..80216a0f 100644 --- a/projects/numeric-stepper/package.json +++ b/projects/numeric-stepper/package.json @@ -1,36 +1,47 @@ { - "name": "@hug/ngx-numeric-stepper", - "version": "1.0.0", - "description": "", - "homepage": "https://github.com/dsi-hug/ngx-components", - "license": "GPL-3.0-only", - "author": "HUG - Hôpitaux Universitaires Genève", - "contributors": [ - "badisi (https://github.com/badisi)", - "vapkse (https://github.com/vapkse)" - ], - "repository": { - "type": "git", - "url": "https://github.com/dsi-hug/ngx-components.git" - }, - "keywords": [ - "angular", - "material", - "material design", - "components" - ], - "sideEffects": false, - "scripts": { - "lint": "eslint . --fix", - "test": "ng test numeric-stepper", - "test:ci": "ng test numeric-stepper --watch=false --browsers=ChromeHeadless", - "build": "ng build numeric-stepper -c production" - }, - "peerDependencies": { - "@angular/common": "^14.3.0", - "@angular/core": "^14.3.0" - }, - "dependencies": { - "tslib": "^2.6.2" + "name": "@hug/ngx-numeric-stepper", + "version": "1.0.0", + "description": "", + "homepage": "https://github.com/dsi-hug/ngx-components", + "license": "GPL-3.0-only", + "author": "HUG - Hôpitaux Universitaires Genève", + "contributors": [ + "badisi (https://github.com/badisi)", + "vapkse (https://github.com/vapkse)" + ], + "repository": { + "type": "git", + "url": "https://github.com/dsi-hug/ngx-components.git" + }, + "keywords": [ + "angular", + "material", + "material design", + "components" + ], + "sideEffects": false, + "exports": { + ".": { + "sass": "./_numeric-stepper-theme.scss" } + }, + "scripts": { + "lint": "eslint . --fix", + "test": "ng test numeric-stepper", + "test:ci": "ng test numeric-stepper --watch=false --browsers=ChromeHeadless", + "build": "ng build numeric-stepper -c=production", + "release": "nx release -p=@hug/ngx-numeric-stepper", + "release:dry-run": "nx release -p=@hug/ngx-numeric-stepper --dry-run" + }, + "peerDependencies": { + "@angular/common": ">= 14", + "@angular/core": ">= 14", + "@angular/cdk": ">= 14", + "@angular/material": ">= 14", + "rxjs": ">= 7.0.0", + "@hug/ngx-core": "1.1.0" + }, + "dependencies": { + "tslib": "^2.6.2" + } } diff --git a/projects/numeric-stepper/src/_numeric-stepper-theme.scss b/projects/numeric-stepper/src/_numeric-stepper-theme.scss new file mode 100644 index 00000000..ac30af64 --- /dev/null +++ b/projects/numeric-stepper/src/_numeric-stepper-theme.scss @@ -0,0 +1,55 @@ +@use "@angular/material" as mat; + +@mixin theme($theme) { + $primary: map-get($theme, primary); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + + [numeric-stepper-form-field] { + numeric-stepper { + .arrow { + color: mat.get-color-from-palette($primary); + + &[disabled] { + color: rgba(#000, 0.3); + } + } + + .shadow { + background-color: mat.get-color-from-palette($background, card); + } + } + + &:not(.disabled) { + &.mat-form-field-invalid { + numeric-stepper { + .arrow:not([disabled]) { + color: mat.get-color-from-palette($warn); + } + + .shadow { + box-shadow: 0px 2px 4px rgba(mat.get-color-from-palette($warn), 0.3); + } + } + + .mat-form-field-flex { + background-color: mat.get-color-from-palette($background); + } + } + + &:hover { + .mat-form-field-flex { + background-color: transparent; + } + } + } + + &.mat-form-field-appearance-fill { + numeric-stepper { + .shadow::after { + background-color: rgba(0, 0, 0, 0.04); + } + } + } + } +} diff --git a/projects/numeric-stepper/src/example/example.component.spec.ts b/projects/numeric-stepper/src/example/example.component.spec.ts deleted file mode 100644 index 61022488..00000000 --- a/projects/numeric-stepper/src/example/example.component.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ExampleComponent } from './example.component'; - -describe('ExampleComponent', () => { - let component: ExampleComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ExampleComponent] - }).compileComponents(); - - fixture = TestBed.createComponent(ExampleComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/numeric-stepper/src/example/example.component.ts b/projects/numeric-stepper/src/example/example.component.ts deleted file mode 100644 index d6dc2085..00000000 --- a/projects/numeric-stepper/src/example/example.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; - -@Component({ - selector: 'deja-example', - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [], - template: '

example works!

', - styles: [] -}) -export class ExampleComponent { } diff --git a/projects/numeric-stepper/src/example/example.service.spec.ts b/projects/numeric-stepper/src/example/example.service.spec.ts deleted file mode 100644 index 33a65090..00000000 --- a/projects/numeric-stepper/src/example/example.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { ExampleService } from './example.service'; - -describe('ExampleService', () => { - let service: ExampleService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(ExampleService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/projects/numeric-stepper/src/example/example.service.ts b/projects/numeric-stepper/src/example/example.service.ts deleted file mode 100644 index 278d5bcf..00000000 --- a/projects/numeric-stepper/src/example/example.service.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root' -}) -export class ExampleService { } diff --git a/projects/numeric-stepper/src/index.ts b/projects/numeric-stepper/src/index.ts index 020b9f72..039a73a1 100644 --- a/projects/numeric-stepper/src/index.ts +++ b/projects/numeric-stepper/src/index.ts @@ -1,6 +1 @@ -/* - * Public API Surface of lib - */ - -export * from './example/example.service'; -export * from './example/example.component'; +export * from './numeric-stepper.component'; diff --git a/projects/numeric-stepper/src/numeric-stepper.component.html b/projects/numeric-stepper/src/numeric-stepper.component.html new file mode 100644 index 00000000..e17c8bb0 --- /dev/null +++ b/projects/numeric-stepper/src/numeric-stepper.component.html @@ -0,0 +1,3 @@ +{{ (!arrowIcons && "add") || (layout==="vertical" && "keyboard_arrow_up") || "keyboard_arrow_right" }} +{{ (!arrowIcons && "remove") || (layout==="vertical" && "keyboard_arrow_down") || "keyboard_arrow_left" }} +
diff --git a/projects/numeric-stepper/src/numeric-stepper.component.scss b/projects/numeric-stepper/src/numeric-stepper.component.scss new file mode 100644 index 00000000..dbb032f5 --- /dev/null +++ b/projects/numeric-stepper/src/numeric-stepper.component.scss @@ -0,0 +1,189 @@ +[numeric-stepper-container] .mat-form-field, +[numeric-stepper-form-field] { + .mat-form-field-infix { + display: inline-flex; + justify-content: center; + align-items: center; + } + + &[numeric-stepper-form-field="vertical"] { + + &:not(.disabled) { + + &:hover { + .mat-form-field-label-wrapper { + opacity: 0 !important; + } + + &.mat-form-field-invalid { + .mat-form-field-subscript-wrapper { + visibility: hidden; + } + } + } + } + } + + &[numeric-stepper-form-field="horizontal-inlay"] { + &:not(.disabled) { + input:not([inputAutosize]) { + width: calc(100% - 64px); + } + + &:hover { + &:not([float-label="always"]) { + .mat-form-field-label.mat-empty { + padding-left: 23px + } + } + + &[inputAutosizeFormField] { + .mat-form-field-infix { + padding: 1em 23px; + } + + &.mat-form-field-appearance-outline { + .mat-form-field-infix { + padding: 1em 18px; + } + } + } + + &:not([inputAutosizeFormField]) { + + &.mat-form-field-appearance-fill { + input { + margin: 0 18px; + } + } + } + } + } + } + + .noselect { + -webkit-touch-callout: none; + /* iOS Safari */ + -webkit-user-select: none; + /* Chrome/Safari/Opera */ + -khtml-user-select: none; + /* Konqueror */ + -moz-user-select: none; + /* Firefox */ + -ms-user-select: none; + /* Internet Explorer/Edge */ + user-select: none; + /* Non-prefixed version, currently + not supported by any browser */ + } + + * { + transition: opacity 250ms ease-out; + } + + input::-webkit-inner-spin-button, + input::-webkit-outer-spin-button { + -webkit-appearance: none !important; + margin: 0 !important; + -moz-appearance: textfield !important; + } + + .mat-input-element { + text-align: center; + } + + &:not(.disabled) { + + &:hover { + + .arrow, + .shadow { + opacity: 1; + } + + .mat-form-field-outline, + .mat-form-field-outline-thick, + .mat-form-field-underline { + opacity: 0 !important; + } + + .mat-form-field-label-wrapper, + input { + z-index: 10003; + } + + input { + position: relative; + } + } + + &:hover, + &[hover] { + + .arrow, + .shadow { + visibility: visible; + } + } + } + + numeric-stepper { + position: relative; + z-index: 10002; + + .arrow, + .shadow { + position: absolute; + visibility: hidden; + opacity: 0; + } + + .arrow { + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + box-sizing: border-box; + z-index: 10004; + height: 32px; + width: 32px; + + &[disabled=true] { + cursor: default; + } + } + + .shadow { + border-radius: 4px; + box-shadow: 0px 2px 4px rgba(#000, 0.3); + } + + .increment { + border-radius: 4px 4px 0 0; + } + + .decrement { + border-radius: 0 0 4px 4px; + } + + &[layout="horizontal"], + &[layout="horizontal-inlay"] { + .increment { + border-radius: 0 4px 4px 0; + } + + .decrement { + border-radius: 4px 0 0 4px; + } + } + + .shadow::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + } +} diff --git a/projects/numeric-stepper/src/numeric-stepper.component.ts b/projects/numeric-stepper/src/numeric-stepper.component.ts new file mode 100644 index 00000000..05b75dbf --- /dev/null +++ b/projects/numeric-stepper/src/numeric-stepper.component.ts @@ -0,0 +1,309 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { CommonModule } from '@angular/common'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostBinding, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatFormFieldControl, MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { Destroy, filterMap, KeyCodes } from '@hug/ngx-core'; +import { combineLatestWith, debounceTime, delay, filter, fromEvent, map, mergeWith, ReplaySubject, shareReplay, startWith, Subject, switchMap, takeUntil, tap, timer, withLatestFrom } from 'rxjs'; + +export type NumericStepperLayout = 'vertical' | 'horizontal' | 'horizontal-inlay'; + +// TODO sdil refactor rxjs flows +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: 'numeric-stepper', + styleUrls: ['./numeric-stepper.component.scss'], + templateUrl: './numeric-stepper.component.html', + standalone: true, + imports: [ + CommonModule, + FormsModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + ReactiveFormsModule + ], + encapsulation: ViewEncapsulation.None +}) +export class NumericStepperComponent extends Destroy implements OnInit { + private static TYPE_ERROR = 'Input element on the same mat-form-field must be type="number". With other input type, use increment or decrement events and implement your proper functions to change the value.'; + private static STEP_FN_ERROR = 'Input element on the same mat-form-field must implement stepDown/stepUp functions.'; + private static INPUT_ERROR = 'To use the automatic binding, you must specify the input field with a matInput reference. [input]="matInputRef"'; + + @HostBinding('attr.layout') @Input() + public layout: NumericStepperLayout = 'vertical'; + + @Output() public readonly increment = new EventEmitter(); + @Output() public readonly decrement = new EventEmitter(); + + @Input() public input?: MatFormFieldControl; + + @Input() + public set arrowIcons(value: BooleanInput) { + this._arrowIcons = coerceBooleanProperty(value); + } + + public get arrowIcons(): BooleanInput { + return this._arrowIcons; + } + + @Input() + public set showOnInit(value: BooleanInput) { + this._showOnInit = coerceBooleanProperty(value); + } + + public get showOnInit(): BooleanInput { + return this._showOnInit; + } + + @HostBinding('attr.hover') + protected hover = null as boolean | null; + + public leftUp?: number = undefined; + public leftDown?: number = undefined; + public topUp?: number = undefined; + public topDown?: number = undefined; + public leftShadow?: number = undefined; + public topShadow?: number = undefined; + public widthShadow?: number = undefined; + public heightShadow?: number = undefined; + + public disableUp = false; + public disableDown = false; + public clickArrow$ = new Subject(); + public show$ = new ReplaySubject(1); + + private validateArrows$ = new Subject(); + private _arrowIcons = false; + private _showOnInit = false; + private arrowSize = 32; + private parentAppearance?: string; + + public constructor( + private elementRef: ElementRef, + private changeDetectorRef: ChangeDetectorRef + ) { + super(); + } + + public ngOnInit(): void { + const calcPositions = (inputElement: HTMLInputElement | undefined, formFieldElement: HTMLElement | undefined, containerElement: HTMLElement | undefined): void => { + const containerBounds = containerElement?.getBoundingClientRect(); + const formFieldBounds = formFieldElement?.getBoundingClientRect() ?? {} as DOMRect; + const inputBounds = inputElement?.getBoundingClientRect() ?? formFieldBounds; + + const bounds = this.elementRef.nativeElement.getBoundingClientRect(); + + this.validateArrows$.next(); + + // Ensure delayed hover in case of the mouse leave accidentally + formFieldElement?.setAttribute('hover', ''); + + if (this.layout === 'horizontal') { + const height = containerBounds?.height || formFieldBounds.height; + this.heightShadow = Math.min(48, height) + 2; + this.topShadow = (containerBounds?.top ?? inputBounds.top + (inputBounds.height - this.heightShadow) / 2 - 5) - bounds.top; + this.leftDown = this.leftShadow = formFieldBounds.left - bounds.left - 28; + this.leftUp = formFieldBounds.right - bounds.left; + this.widthShadow = this.leftUp - this.leftDown + 28; + + if (this.parentAppearance === 'LEGACY' || this.parentAppearance === 'STANDARD') { + this.heightShadow -= 6; + } else if (this.parentAppearance === 'FILL') { + this.heightShadow -= 2; + } + + this.topUp = this.topDown = inputBounds.top + (inputBounds.height - this.arrowSize) / 2 - bounds.top; + + } else if (this.layout === 'horizontal-inlay') { + this.heightShadow = Math.min(48, containerBounds?.height || formFieldBounds.height) + 4; + this.topShadow = containerBounds?.top ?? (inputBounds.top + (inputBounds.height - this.heightShadow) / 2 - 5) - bounds.top; + this.leftDown = this.leftShadow = formFieldBounds.left - bounds.left; + this.leftUp = formFieldBounds.right - bounds.left - 28; + this.widthShadow = this.leftUp - this.leftDown + 28; + + if (this.parentAppearance === 'LEGACY' || this.parentAppearance === 'STANDARD') { + const addedPadding = 6; + this.widthShadow += addedPadding * 2; + this.leftDown -= addedPadding; + this.leftUp += addedPadding; + this.heightShadow -= addedPadding; + this.leftShadow -= addedPadding; + } else if (this.parentAppearance === 'FILL') { + this.heightShadow -= 2; + } + + this.topUp = this.topDown = inputBounds.top + (inputBounds.height - this.arrowSize) / 2 - bounds.top; + + } else { + this.heightShadow = 90; + this.topShadow = inputBounds.top - bounds.top + (inputBounds.height - this.heightShadow) / 2; + this.leftShadow = (containerBounds?.left ?? formFieldBounds.left) - bounds.left; + this.topUp = this.topShadow; + this.topDown = this.topShadow + this.heightShadow - this.arrowSize; + this.widthShadow = containerBounds?.width || formFieldBounds.width; + this.leftUp = this.leftDown = formFieldBounds.left + (formFieldBounds.width - this.arrowSize) / 2 - bounds.left; + } + + this.changeDetectorRef.markForCheck(); + }; + + const linkedElements$ = timer(100).pipe( + map(() => { + // Find form field + let parentElement = this.elementRef.nativeElement.parentElement; + let formFieldElement: HTMLElement | undefined; + let containerElement: HTMLElement | undefined; + let inputElement: HTMLInputElement | undefined; + + // eslint-disable-next-line no-loops/no-loops + while (parentElement) { + if (parentElement.tagName === 'MAT-FORM-FIELD' || parentElement.hasAttribute('numeric-stepper-form-field')) { + formFieldElement = parentElement; + } + if (parentElement.hasAttribute('numeric-stepper-container')) { + containerElement = parentElement; + } + if (containerElement && formFieldElement) { + break; + } + parentElement = parentElement.parentElement; + } + + if (formFieldElement) { + formFieldElement.setAttribute('numeric-stepper-form-field', this.layout); + this.parentAppearance = formFieldElement.getAttribute('appearance')?.toUpperCase(); + } + + if (!formFieldElement) { + console.error('numeric-stepper work only inside a mat-form-field or a [numeric-stepper-form-field] element'); + } else { + inputElement = formFieldElement.getElementsByTagName('INPUT')?.[0] as HTMLInputElement || null; + + if (!inputElement) { + console.error('numeric-stepper work only inside a mat-form-field or a [numeric-stepper-form-field] element containing an input element'); + } + } + + return [inputElement, formFieldElement, containerElement] as const; + }), + filter(([inputElement, formFieldElement]) => !!formFieldElement && !!inputElement), + shareReplay({ bufferSize: 1, refCount: false }) + ); + + const step = (inputElement: HTMLInputElement, event: 'increment' | 'decrement', fn: 'stepUp' | 'stepDown'): void => { + if (this[event].observed) { + this[event].emit(); + } else { + if (inputElement?.type !== 'number') { + throw new Error(NumericStepperComponent.TYPE_ERROR); + } + + if (!inputElement[fn]) { + throw new Error(NumericStepperComponent.STEP_FN_ERROR); + } + + if (!this.input?.ngControl?.control) { + throw new Error(NumericStepperComponent.INPUT_ERROR); + } + + inputElement[fn](); + this.input.ngControl.control.setValue(+inputElement.value); + } + this.validateArrows$.next(); + }; + + const step$ = this.clickArrow$.pipe( + debounceTime(10), + withLatestFrom(linkedElements$), + tap(([isUp, [inputElement]]) => { + if (isUp && !this.disableUp && inputElement) { + step(inputElement, 'increment', 'stepUp'); + } + if (!isUp && !this.disableDown && inputElement) { + step(inputElement, 'decrement', 'stepDown'); + } + }), + shareReplay({ bufferSize: 1, refCount: false }) + ); + + const valueChange$ = linkedElements$.pipe( + filterMap(([inputElement]) => inputElement), + switchMap(inputElement => fromEvent(inputElement, 'input').pipe( + mergeWith(step$, fromEvent(inputElement, 'paste'), fromEvent(inputElement, 'keypress')) + )), + debounceTime(50), + startWith(undefined) + ); + + linkedElements$.pipe( + switchMap(([inputElement, formFieldElement, containerElement]) => { + const element = containerElement ?? formFieldElement; + if (!element) { + return []; + } + + return fromEvent(element, 'mouseenter').pipe( + switchMap(() => valueChange$), + mergeWith(this.show$.pipe( + delay(200) + )), + tap(() => calcPositions(inputElement, formFieldElement, containerElement)), + switchMap(() => fromEvent(element, 'mouseleave')), + delay(400), + tap(() => { + formFieldElement?.removeAttribute('hover'); + }) + ); + }), + takeUntil(this.destroyed$) + ).subscribe(); + + linkedElements$.pipe( + filterMap(([_, formFieldElement]) => formFieldElement), + switchMap(formFieldElement => fromEvent(formFieldElement, 'keydown')), + filter(event => { + const keyCode = event.code as KeyCodes; + if (keyCode === 'ArrowUp' || keyCode === 'ArrowDown') { + this.clickArrow$.next(keyCode === 'ArrowUp'); + return true; + } + return false; + }), + takeUntil(this.destroyed$) + ).subscribe(event => { + event.preventDefault(); + return false; + }); + + linkedElements$.pipe( + combineLatestWith(this.validateArrows$), + filterMap(([[inputElement]]) => inputElement), + debounceTime(1), + takeUntil(this.destroyed$) + ).subscribe(inputElement => { + if (inputElement.disabled) { + this.disableDown = true; + this.disableUp = true; + } else { + const min = inputElement.min; + this.disableDown = min !== '' && !isNaN(+min) && +inputElement.value <= +min; + + const max = inputElement.max; + this.disableUp = max !== '' && !isNaN(+max) && +inputElement.value >= +max; + } + + this.changeDetectorRef.markForCheck(); + }); + + step$.pipe( + takeUntil(this.destroyed$) + ).subscribe(); + + if (this.showOnInit) { + this.show$.next(); + } + } +} diff --git a/projects/numeric-stepper/tsconfig.lib.json b/projects/numeric-stepper/tsconfig.lib.json index 8f6c5298..599bb9f4 100644 --- a/projects/numeric-stepper/tsconfig.lib.json +++ b/projects/numeric-stepper/tsconfig.lib.json @@ -1,6 +1,6 @@ /* To learn more about this file see: https://angular.io/config/tsconfig. */ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "../../out-tsc/lib", "declaration": true,