Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const enum NumberInputAppearance {
Underline = 'underline',
Border = 'border'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@import 'font';
@import 'color-palette';

.number-input {
@include body-2-regular($gray-9);
width: inherit;
height: inherit;
background: white;
text-align: center;
}

.border {
border: 1px solid $gray-2;
border-radius: 6px;
}

.underline {
border: none;
border-bottom: 1px solid $gray-2;
}

input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}

/* Firefox */
input[type='number'] {
-moz-appearance: textfield;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { fakeAsync } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { createHostFactory } from '@ngneat/spectator/jest';
import { NumberInputAppearance } from './number-input-appearance';
import { NumberInputComponent } from './number-input.component';

describe('Number Input Component', () => {
const hostFactory = createHostFactory({
component: NumberInputComponent,
imports: [FormsModule],
shallow: true
});

test('should apply disabled attribute when disabled', fakeAsync(() => {
const spectator = hostFactory(`
<ht-number-input [disabled]="true">
</ht-number-input>`);

spectator.tick();
expect(spectator.query('input')!.getAttributeNames().includes('disabled')).toBe(true);
}));

test('should apply border style correctly', fakeAsync(() => {
const spectator = hostFactory(
`
<ht-number-input [appearance]="appearance">
</ht-number-input>`,
{
hostProps: {
appearance: NumberInputAppearance.Border
}
}
);

spectator.tick();
expect(spectator.query('input')?.classList).toContain('border');
}));

test('should apply underline style correctly', fakeAsync(() => {
const spectator = hostFactory(
`
<ht-number-input [appearance]="appearance">
</ht-number-input>`,
{
hostProps: {
appearance: NumberInputAppearance.Underline
}
}
);

spectator.tick();
expect(spectator.query('input')?.classList).toContain('underline');
}));

test('should emit correct values on value change', fakeAsync(() => {
const spectator = hostFactory(`
<ht-number-input minValue="1" maxValue="10">
</ht-number-input>`);

spectator.tick();
spyOn(spectator.component.valueChange, 'emit');
spectator.triggerEventHandler('input', 'ngModelChange', 7);
expect(spectator.component.valueChange.emit).toHaveBeenCalledWith(7);

spectator.triggerEventHandler('input', 'ngModelChange', 15);
expect(spectator.component.valueChange.emit).toHaveBeenCalledWith(10);

spectator.triggerEventHandler('input', 'ngModelChange', -1);
expect(spectator.component.valueChange.emit).toHaveBeenCalledWith(1);
}));
});
58 changes: 58 additions & 0 deletions projects/components/src/number-input/number-input.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { NumberCoercer } from '@hypertrace/common';
import { NumberInputAppearance } from './number-input-appearance';

@Component({
selector: 'ht-number-input',
styleUrls: ['./number-input.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<input
type="number"
class="number-input"
[ngClass]="this.appearance"
[disabled]="this.disabled"
[ngModel]="this.value"
(ngModelChange)="this.onValueChange($event)"
/>
`
})
export class NumberInputComponent {
@Input()
public value?: number;

@Input()
public appearance: NumberInputAppearance = NumberInputAppearance.Border;

@Input()
public disabled: boolean = false;

@Input()
public minValue?: number;

@Input()
public maxValue?: number;

@Output()
public readonly valueChange: EventEmitter<number> = new EventEmitter();

private readonly numberCoercer: NumberCoercer = new NumberCoercer();

private enforceMinMaxAndEmit(): void {
if (this.value !== undefined && this.maxValue !== undefined && this.value > this.maxValue) {
this.value = this.maxValue;
}

if (this.value !== undefined && this.minValue !== undefined && this.value < this.minValue) {
this.value = this.minValue;
}

this.valueChange.emit(this.numberCoercer.coerce(this.value));
}

public onValueChange(value?: number): void {
this.value = value;

this.enforceMinMaxAndEmit();
}
}
11 changes: 11 additions & 0 deletions projects/components/src/number-input/number-input.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NumberInputComponent } from './number-input.component';

@NgModule({
imports: [CommonModule, FormsModule],
declarations: [NumberInputComponent],
exports: [NumberInputComponent]
})
export class NumberInputModule {}
5 changes: 5 additions & 0 deletions projects/components/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ export { InputAppearance } from './input/input-appearance';
export * from './input/input.component';
export * from './input/input.module';

// Number Input
export { NumberInputAppearance } from './number-input/number-input-appearance';
export * from './number-input/number-input.component';
export * from './number-input/number-input.module';

// Json Tree
export { JsonViewerComponent } from './viewer/json-viewer/json-viewer.component';
export { JsonViewerModule } from './viewer/json-viewer/json-viewer.module';
Expand Down