Skip to content

Commit

Permalink
refactor!: remove precision, decimal inputs from InputNumber in…
Browse files Browse the repository at this point in the history
… favour of the `NumberFormat` directive (#6997)

Co-authored-by: taiga-family-bot <[email protected]>
  • Loading branch information
vladimirpotekhin and taiga-family-bot authored Mar 21, 2024
1 parent bef16df commit 23f1f71
Show file tree
Hide file tree
Showing 61 changed files with 519 additions and 252 deletions.
18 changes: 5 additions & 13 deletions projects/addon-commerce/pipes/amount/amount.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import {inject, Pipe} from '@angular/core';
import type {TuiCurrencyVariants} from '@taiga-ui/addon-commerce/types';
import {tuiFormatCurrency, tuiFormatSignSymbol} from '@taiga-ui/addon-commerce/utils';
import {CHAR_NO_BREAK_SPACE} from '@taiga-ui/cdk';
import type {TuiDecimal, TuiHorizontalDirection} from '@taiga-ui/core';
import type {TuiHorizontalDirection} from '@taiga-ui/core';
import {TUI_NUMBER_FORMAT, tuiFormatNumber} from '@taiga-ui/core';
import type {Observable} from 'rxjs';
import {map} from 'rxjs';

import {TUI_AMOUNT_OPTIONS} from './amount.options';

const DEFAULT_DECIMAL_LIMIT = 2;
const DEFAULT_PRECISION = 2;

@Pipe({
name: 'tuiAmount',
Expand All @@ -31,13 +31,9 @@ export class TuiAmountPipe implements PipeTransform {
const currencySymbol = tuiFormatCurrency(currency);
const formatted = tuiFormatNumber(Math.abs(value), {
...format,
decimalLimit: this.getDecimalLimit(
value,
Number.isNaN(format.decimalLimit)
? DEFAULT_DECIMAL_LIMIT
: format.decimalLimit,
format?.decimal || 'not-zero',
),
precision: Number.isNaN(format.precision)
? DEFAULT_PRECISION
: format.precision,
});
const space =
currencySymbol?.length > 1 || currencyAlign === 'right'
Expand All @@ -50,8 +46,4 @@ export class TuiAmountPipe implements PipeTransform {
}),
);
}

private getDecimalLimit(value: number, limit: number, decimal: TuiDecimal): number {
return decimal === 'always' || (decimal === 'not-zero' && value % 1) ? limit : 0;
}
}
5 changes: 2 additions & 3 deletions projects/core/constants/default-number-format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import {CHAR_NO_BREAK_SPACE} from '@taiga-ui/cdk';
import type {TuiNumberFormatSettings} from '@taiga-ui/core/interfaces';

export const TUI_DEFAULT_NUMBER_FORMAT: TuiNumberFormatSettings = {
decimalLimit: NaN,
precision: NaN,
decimalSeparator: ',',
thousandSeparator: CHAR_NO_BREAK_SPACE,
zeroPadding: true,
rounding: 'truncate',
decimal: 'not-zero',
decimalMode: 'pad',
};
10 changes: 3 additions & 7 deletions projects/core/interfaces/number-format-settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {TuiRounding} from '@taiga-ui/cdk';
import type {TuiDecimal, TuiDecimalSymbol} from '@taiga-ui/core/types';
import type {TuiDecimalMode, TuiDecimalSymbol} from '@taiga-ui/core/types';

/**
* Formatting configuration for displayed numbers
Expand All @@ -9,7 +9,7 @@ export interface TuiNumberFormatSettings {
* Number of digits of decimal part.
* @note Use `Infinity` to keep untouched.
*/
readonly decimalLimit: number;
readonly precision: number;
/**
* Separator between the integer and the decimal part.
* @example 0,42 (',' by default)
Expand All @@ -24,12 +24,8 @@ export interface TuiNumberFormatSettings {
* @example 360 000 (' ' by default)
*/
readonly thousandSeparator: string;
/**
* Enable zeros at the end of decimal part.
*/
readonly zeroPadding: boolean;
/**
* Decimal part display mode. ('not-zero' by default)
*/
readonly decimal: TuiDecimal;
readonly decimalMode: TuiDecimalMode;
}
4 changes: 2 additions & 2 deletions projects/core/pipes/format-number/format-number.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ export class TuiFormatNumberPipe implements PipeTransform {
map(format =>
tuiFormatNumber(value, {
...format,
decimalLimit: Number.isNaN(format.decimalLimit)
precision: Number.isNaN(format.precision)
? Infinity
: format.decimalLimit,
: format.precision,
...settings,
}),
),
Expand Down
1 change: 1 addition & 0 deletions projects/core/types/decimal-mode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type TuiDecimalMode = 'always' | 'not-zero' | 'pad';
1 change: 0 additions & 1 deletion projects/core/types/decimal.ts

This file was deleted.

2 changes: 1 addition & 1 deletion projects/core/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export * from './brightness';
export * from './calendar-view';
export * from './data-list-role';
export * from './decimal';
export * from './decimal-mode';
export * from './decimal-symbol';
export * from './direction';
export * from './dropdown-align';
Expand Down
18 changes: 10 additions & 8 deletions projects/core/utils/format/format-number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,25 @@ export function tuiFormatNumber(
value: number,
settings: Partial<TuiNumberFormatSettings> = {},
): string {
const {decimalLimit, decimalSeparator, thousandSeparator, zeroPadding, rounding} = {
const {precision, decimalSeparator, thousandSeparator, decimalMode, rounding} = {
...TUI_DEFAULT_NUMBER_FORMAT,
decimalLimit: Infinity,
decimalMode: 'always',
precision: Infinity,
...settings,
};

const rounded = Number.isFinite(decimalLimit)
? tuiRoundWith({value, precision: decimalLimit, method: rounding})
const rounded = Number.isFinite(precision)
? tuiRoundWith({value, precision, method: rounding})
: value;
const integerPartString = String(Math.floor(Math.abs(rounded)));

let fractionPartPadded = tuiGetFractionPartPadded(rounded, decimalLimit);
let fractionPartPadded = tuiGetFractionPartPadded(rounded, precision);
const hasFraction = Number(fractionPartPadded) > 0;

if (Number.isFinite(decimalLimit)) {
if (zeroPadding) {
if (Number.isFinite(precision)) {
if (decimalMode === 'always' || (hasFraction && decimalMode === 'pad')) {
const zeroPaddingSize: number = Math.max(
decimalLimit - fractionPartPadded.length,
precision - fractionPartPadded.length,
0,
);
const zeroPartString = '0'.repeat(zeroPaddingSize);
Expand Down
53 changes: 32 additions & 21 deletions projects/core/utils/format/test/format-number.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,27 @@ describe('Number formatting', () => {
});

it('finishes the fractional part with zeros to a given value', () => {
expect(tuiFormatNumber(123, {decimalLimit: 2})).toBe('123,00');
expect(tuiFormatNumber(123, {precision: 2})).toBe('123,00');
});

it('discards the extra fractional part', () => {
expect(tuiFormatNumber(1.234, {decimalLimit: 2})).toBe('1,23');
expect(tuiFormatNumber(1.234, {precision: 2})).toBe('1,23');
});

it('discards the fractional part altogether', () => {
expect(tuiFormatNumber(5.678, {decimalLimit: 0})).toBe('5');
expect(tuiFormatNumber(5.678, {precision: 0})).toBe('5');
});

it('accepts custom fractional separator', () => {
expect(tuiFormatNumber(123.45, {decimalLimit: 2, decimalSeparator: '.'})).toBe(
expect(tuiFormatNumber(123.45, {precision: 2, decimalSeparator: '.'})).toBe(
'123.45',
);
});

it('accepts custom thousands separator', () => {
expect(
tuiFormatNumber(12345.67, {
decimalLimit: 2,
precision: 2,
decimalSeparator: ',',
thousandSeparator: '.',
}),
Expand All @@ -64,68 +64,79 @@ describe('Number formatting', () => {
it('do not add zeros when disable zero padding flag', () => {
expect(
tuiFormatNumber(12345.6, {
decimalLimit: 2,
precision: 2,
decimalSeparator: ',',
thousandSeparator: '.',
zeroPadding: false,
decimalMode: 'not-zero',
}),
).toBe('12.345,6');
});

it('add zeros with default behavior', () => {
expect(
tuiFormatNumber(12345.6, {
decimalLimit: 2,
precision: 2,
decimalSeparator: ',',
thousandSeparator: '.',
}),
).toBe('12.345,60');
});

it('value with exponent and fractional part with and decimal bigger than precision', () => {
expect(tuiFormatNumber(1.23e-8, {decimalLimit: 12})).toBe('0,000000012300');
expect(tuiFormatNumber(1.23e-8, {precision: 12})).toBe('0,000000012300');
});

it('deletes trailing zeros when zero padding flag is disabled', () => {
it('deletes trailing zeros when decimal mode is not-zero', () => {
expect(
tuiFormatNumber(12345.6078, {
decimalLimit: 2,
precision: 2,
decimalSeparator: ',',
thousandSeparator: '.',
zeroPadding: false,
decimalMode: 'not-zero',
}),
).toBe('12.345,6');
});

it('displays without decimal separator when zero padding flag is disabled and there is no significant digits', () => {
it('displays without decimal separator when decimal mode is pad and there is no significant digits', () => {
expect(
tuiFormatNumber(12345.006, {
decimalLimit: 2,
precision: 2,
decimalSeparator: ',',
thousandSeparator: '.',
zeroPadding: false,
decimalMode: 'pad',
}),
).toBe('12.345');
});

it('does not delete significant zeros in decimal part when padding flag is disabled', () => {
it('does not delete significant zeros in decimal part when when decimal mode is "pad"', () => {
expect(
tuiFormatNumber(0.01, {
decimalLimit: 2,
precision: 2,
decimalSeparator: ',',
thousandSeparator: '.',
zeroPadding: false,
decimalMode: 'pad',
}),
).toBe('0,01');
});

it('deletes trailing zeros only in decimal part when zero padding flag is disabled', () => {
it('deletes trailing zeros only in decimal part when decimal mode is "pad"', () => {
expect(
tuiFormatNumber(0.001, {
decimalLimit: 2,
precision: 2,
decimalSeparator: ',',
thousandSeparator: '.',
zeroPadding: false,
decimalMode: 'pad',
}),
).toBe('0');
});

it('deletes trailing zeros only in decimal part when decimal mode is "not-zero"', () => {
expect(
tuiFormatNumber(0.001, {
precision: 2,
decimalSeparator: ',',
thousandSeparator: '.',
decimalMode: 'not-zero',
}),
).toBe('0');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ test.describe('InputNumber', () => {
});

test.describe('Caret navigation', () => {
test.describe('if user tries to erase padded decimal zeroes (decimal="always"), mask triggers caret navigation', () => {
test.describe('if user tries to erase padded decimal zeroes (decimalMode="always"), mask triggers caret navigation', () => {
test.beforeEach(async ({page}) => {
await tuiGoto(
page,
'/components/input-number/API?decimal=always&precision=2',
'/components/input-number/API?decimalMode=always&precision=2',
);

example = new TuiDocumentationApiPagePO(page).apiPageExample;
Expand Down Expand Up @@ -134,7 +134,7 @@ test.describe('InputNumber', () => {

test.describe('if user tries to erase thousand separator, mask triggers caret navigation', () => {
test.beforeEach(async ({page}) => {
await tuiGoto(page, '/components/input-number/API?decimal=not-zero');
await tuiGoto(page, '/components/input-number/API?decimalMode=not-zero');

example = new TuiDocumentationApiPagePO(page).apiPageExample;
input = example.getByTestId('tui-primitive-textfield__native-input');
Expand Down Expand Up @@ -319,58 +319,79 @@ test.describe('InputNumber', () => {
input = example.getByTestId('tui-primitive-textfield__native-input');
});

test('Value 42 (decimal=not-zero) => 42', async ({page}) => {
test('Value 42 (decimalMode=not-zero) => 42', async ({page}) => {
await tuiGoto(
page,
'components/input-number/API?precision=2&decimal=not-zero',
'components/input-number/API?precision=2&decimalMode=not-zero',
);

await input.fill('42');
await expect(input).toHaveValue('42');
await expect(input).toHaveJSProperty('selectionStart', 2);
await expect(input).toHaveJSProperty('selectionEnd', 2);
await expect(example).toHaveScreenshot('26-input-number.png');
});

test('Value 42,1 (decimal=not-zero) => 42,10', async ({page}) => {
test('Value 42,1 (decimalMode=not-zero) => 42,1', async ({page}) => {
await tuiGoto(
page,
'/components/input-number/API?precision=2&decimal=not-zero',
'/components/input-number/API?precision=2&decimalMode=not-zero',
);

await input.fill('42,1');
await expect(input).toHaveValue('42,1');
await expect(input).toHaveJSProperty('selectionStart', 4);
await expect(input).toHaveJSProperty('selectionEnd', 4);
await input.blur();
await expect(input).toHaveValue('42,1');
await expect(example).toHaveScreenshot('27-input-number.png');
});

test('Value 42,00 (decimal=not-zero) => 42', async ({page}) => {
test('Value 42,1 (decimalMode=pad) => 42,10', async ({page}) => {
await tuiGoto(
page,
'/components/input-number/API?precision=2&decimal=not-zero',
'/components/input-number/API?precision=2&decimalMode=pad',
);

await input.fill('42,1');
await expect(input).toHaveValue('42,1');
await expect(input).toHaveJSProperty('selectionStart', 4);
await expect(input).toHaveJSProperty('selectionEnd', 4);
await input.blur();
await expect(input).toHaveValue('42,10');
await expect(example).toHaveScreenshot('28-input-number.png');
});

test('Value 42,00 (decimalMode=not-zero) => 42', async ({page}) => {
await tuiGoto(
page,
'/components/input-number/API?precision=2&decimalMode=not-zero',
);

await input.fill('42,00');
await expect(input).toHaveJSProperty('selectionStart', 5);
await expect(input).toHaveJSProperty('selectionEnd', 5);
await expect(example).toHaveScreenshot('28-input-number.png');
await expect(example).toHaveScreenshot('29-input-number.png');
});

test('Value 42 (decimal=never) => 42', async ({page}) => {
await tuiGoto(page, '/components/input-number/API?precision=2&decimal=never');
test('Value 42,1 (precision=0) => 42', async ({page}) => {
await tuiGoto(page, '/components/input-number/API?precision=0');

await input.fill('42');
await expect(example).toHaveScreenshot('29-input-number.png');
await input.fill('42,1');
await expect(input).toHaveValue('42');
await expect(example).toHaveScreenshot('30-input-number.png');
});

test('Value 42 (decimal=always) => 42', async ({page}) => {
test('Value 42 (decimalMode=always) => 42.00', async ({page}) => {
await tuiGoto(
page,
'/components/input-number/API?precision=2&decimal=always',
'/components/input-number/API?precision=2&decimalMode=always',
);
await input.fill('42');
await expect(input).toHaveValue('42,00');
await expect(input).toHaveJSProperty('selectionStart', 2);
await expect(input).toHaveJSProperty('selectionEnd', 2);
await expect(example).toHaveScreenshot('30-input-number.png');
await expect(example).toHaveScreenshot('31-input-number.png');
});

test("text field does not contain any digit (only prefix + postfix) => clear text field's value on blur", async ({
Expand All @@ -385,7 +406,7 @@ test.describe('InputNumber', () => {
await expect(input).toHaveValue('$ kg');
await expect(input).toHaveJSProperty('selectionStart', 1);
await expect(input).toHaveJSProperty('selectionEnd', 1);
await expect(example).toHaveScreenshot('31-input-number.png');
await expect(example).toHaveScreenshot('32-input-number.png');
});
});
});
Loading

0 comments on commit 23f1f71

Please sign in to comment.