Skip to content

Feat/stepper #49

Merged
merged 19 commits into from
Oct 9, 2018
Merged
1 change: 1 addition & 0 deletions src/cdk/keycodes/keycodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const SEMICOLON = 186; // Firefox (Gecko) fires 59 for SEMICOLON
export const EQUALS = 187; // Firefox (Gecko) fires 61 for EQUALS
export const COMMA = 188;
export const DASH = 189; // Firefox (Gecko) fires 173 for DASH/MINUS
export const PERIOD = 190;
export const SLASH = 191;
export const APOSTROPHE = 192;
export const TILDE = 192;
Expand Down
12 changes: 9 additions & 3 deletions src/cdk/testing/dispatch-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,30 @@ export function dispatchEvent(node: Node | Window, event: Event): Event {
}

/** Shorthand to dispatch a fake event on a specified node. */
// tslint:disable-next-line:no-reserved-keywords
export function dispatchFakeEvent(node: Node | Window, type: string, canBubble?: boolean): Event {
return dispatchEvent(node, createFakeEvent(type, canBubble));
}

/** Shorthand to dispatch a keyboard event with a specified key code. */
export function dispatchKeyboardEvent(node: Node, type: string, keyCode: number, target?: Element):
// tslint:disable-next-line:no-reserved-keywords
export function dispatchKeyboardEvent(node: Node, type: string, keyCode: number, target?: Element,
shiftKey = false, ctrlKey = false, altKey = false):
KeyboardEvent {
return dispatchEvent(node, createKeyboardEvent(type, keyCode, target)) as KeyboardEvent;
const event = createKeyboardEvent(type, keyCode, target, undefined, shiftKey, ctrlKey, altKey);

return dispatchEvent(node, event) as KeyboardEvent;
}

/** Shorthand to dispatch a mouse event on the specified coordinates. */
// tslint:disable-next-line
// tslint:disable-next-line:no-reserved-keywords
export function dispatchMouseEvent(node: Node, type: string, x = 0, y = 0,
event = createMouseEvent(type, x, y)): MouseEvent {
return dispatchEvent(node, event) as MouseEvent;
}

/** Shorthand to dispatch a touch event on the specified coordinates. */
// tslint:disable-next-line:no-reserved-keywords
export function dispatchTouchEvent(node: Node, type: string, x = 0, y = 0) {
return dispatchEvent(node, createTouchEvent(type, x, y));
}
24 changes: 14 additions & 10 deletions src/cdk/testing/event-objects.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** Creates a browser MouseEvent with the specified options. */
// tslint:disable-next-line
// tslint:disable-next-line:no-reserved-keywords
export function createMouseEvent(type: string, x = 0, y = 0) {
const event = document.createEvent('MouseEvent');

Expand All @@ -23,27 +23,28 @@ export function createMouseEvent(type: string, x = 0, y = 0) {
}

/** Creates a browser TouchEvent with the specified pointer coordinates. */
// tslint:disable-next-line
// tslint:disable-next-line:no-reserved-keywords
export function createTouchEvent(type: string, pageX = 0, pageY = 0) {
// In favor of creating events that work for most of the browsers, the event is created
// as a basic UI Event. The necessary details for the event will be set manually.
const event = document.createEvent('UIEvent');
const touchDetails = {pageX, pageY};
const touchDetails = { pageX, pageY };

event.initUIEvent(type, true, true, window, 0);

// Most of the browsers don't have a "initTouchEvent" method that can be used to define
// the touch details.
Object.defineProperties(event, {
touches: {value: [touchDetails]}
touches: { value: [touchDetails] }
});

return event;
}

/** Dispatches a keydown event from an element. */
// tslint:disable-next-line
export function createKeyboardEvent(type: string, keyCode: number, target?: Element, key?: string) {
// tslint:disable-next-line:no-reserved-keywords
export function createKeyboardEvent(type: string, keyCode: number, target?: Element, key?: string,
shiftKey = false, ctrlKey = false, altKey = false) {
const event = document.createEvent('KeyboardEvent') as any;
// Firefox does not support `initKeyboardEvent`, but supports `initKeyEvent`.
const initEventFn = (event.initKeyEvent || event.initKeyboardEvent).bind(event);
Expand All @@ -54,9 +55,12 @@ export function createKeyboardEvent(type: string, keyCode: number, target?: Elem
// Webkit Browsers don't set the keyCode when calling the init function.
// See related bug https://bugs.webkit.org/show_bug.cgi?id=16735
Object.defineProperties(event, {
keyCode: {get: () => keyCode},
key: {get: () => key},
target: {get: () => target}
keyCode: { get: () => keyCode },
key: { get: () => key },
target: { get: () => target },
shiftKey: { get: () => shiftKey },
ctrlKey: { get: () => ctrlKey },
altKey: { get: () => altKey }
});

// IE won't set `defaultPrevented` on synthetic events so we need to do it manually.
Expand All @@ -70,7 +74,7 @@ export function createKeyboardEvent(type: string, keyCode: number, target?: Elem
}

/** Creates a fake event object with any desired event type. */
// tslint:disable-next-line
// tslint:disable-next-line:no-reserved-keywords
export function createFakeEvent(type: string, canBubble = false, cancelable = true) {
const event = document.createEvent('Event');
event.initEvent(type, canBubble, cancelable);
Expand Down
2 changes: 2 additions & 0 deletions src/lib-dev/input/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { McInputModule } from '../../lib/input/';
})
export class InputDemoComponent {
value: string = '';
numberValue: number | null = null;
}


Expand All @@ -36,6 +37,7 @@ export class InputDemoComponent {
})
export class InputDemoModule {}

// tslint:disable:no-console
platformBrowserDynamic()
.bootstrapModule(InputDemoModule)
.catch((error) => console.error(error));
Expand Down
56 changes: 56 additions & 0 deletions src/lib-dev/input/template.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,62 @@
<div class="container">

Number Value: {{numberValue}}
<br>

<header>Without placeholder:</header>
<mc-form-field>
<input mcInput [(ngModel)]="numberValue" type="number">
<mc-stepper></mc-stepper>
</mc-form-field>

<br><br>

<header>With placeholder:</header>
<mc-form-field>
<input mcInput [(ngModel)]="numberValue" type="number" placeholder="Number Value">
<mc-stepper></mc-stepper>
</mc-form-field>

<br><br>

<header>Min = -5 Max = 7 Step = 0.5 Big step = 1.5 </header>
<mc-form-field>
<input mcInput [(ngModel)]="numberValue" type="number" placeholder="Number MaxMin Step"
min="-5" max="7" step="0.5" big-step="1.5">
<mc-stepper></mc-stepper>
</mc-form-field>

<br><br>

<header>Min = -5 </header>
<mc-form-field>
<input mcInput [(ngModel)]="numberValue" type="number" placeholder="Min only" min="-5">
<mc-stepper></mc-stepper>
</mc-form-field>

<br><br>

<header>Step = 0.5 </header>
<mc-form-field>
<input mcInput [(ngModel)]="numberValue" type="number" placeholder="Step only" step="0.5">
<mc-stepper></mc-stepper>
</mc-form-field>

<br><br>

<header>Without placeholder:</header>
<mc-form-field>
<input mcInput [(ngModel)]="numberValue" type="number" disabled>
<mc-stepper></mc-stepper>
</mc-form-field>

<br><br>


Value: {{value}}
<br>

<header>Without placeholder:</header>
<mc-form-field>
<input mcInput [(ngModel)]="value">
</mc-form-field>
Expand Down
49 changes: 49 additions & 0 deletions src/lib/core/utils/__test__/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
// tslint:disable:no-magic-numbers
import { stepDown, stepUp } from '@ptsecurity/mosaic/core';

import { toBoolean } from '../utils';


describe('[Core]::utils', () => {
it('should work for null values', () => {
expect(toBoolean(null)).toBe(false);
Expand All @@ -13,3 +17,48 @@ describe('[Core]::utils', () => {
expect(toBoolean('blablabla')).toBe(true);
});
});

describe('[Core]::stepperUtils', () => {
it('stepUp common', () => {
expect(stepUp(10, 100, -100, 1)).toBe(11);
});

it('stepDown common', () => {
expect(stepDown(10, 100, -100, 1)).toBe(9);
});


it('stepUp empty', () => {
expect(stepUp(null, 100, -100, 1)).toBe(-99);
});

it('stepDown empty', () => {
expect(stepDown(null, 100, -100, 1)).toBe(99);
});


it('stepUp over step', () => {
expect(stepUp(99, 100, -100, 5)).toBe(100);
});

it('stepDown over step', () => {
expect(stepDown(-99, 100, -100, 5)).toBe(-100);
});


it('stepUp no max', () => {
expect(stepUp(99, Infinity, -100, 5)).toBe(104);
});

it('stepDown no min', () => {
expect(stepDown(-99, 100, -Infinity, 5)).toBe(-104);
});

it('stepUp no min', () => {
expect(stepUp(null, 100, -Infinity, 5)).toBe(null);
});

it('stepDown no max', () => {
expect(stepDown(null, Infinity, -100, 5)).toBe(null);
});
});
1 change: 1 addition & 0 deletions src/lib/core/utils/public-api.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './utils';
export * from './stepperUtils';
59 changes: 59 additions & 0 deletions src/lib/core/utils/stepperUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

function sanitizeNumber(value: number): number | null {
return !isFinite(value) || isNaN(value)
? null
: value;
}

function getPrecision(value: number): number {
const arr = value.toString().split('.');

return arr.length === 1
? 1
// tslint:disable-next-line:no-magic-numbers
: Math.pow(10, arr[1].length);
}

function add(value1: number, value2: number) {
const precision = Math.max(getPrecision(value1), getPrecision(value2));

const res = (value1 * precision + value2 * precision) / precision;

return sanitizeNumber(res);
}

export const stepUp = (value: number | null,
max: number,
min: number,
step: number
): number | null => {
let res;

if (value === null) {
res = add(min, step);

return res === null ? null : Math.min(res, max);
}

res = add(value, step);

return res === null ? null : Math.max(Math.min(res, max), min);
};

export const stepDown = (value: number | null,
max: number,
min: number,
step: number
): number | null => {
let res;

if (value === null) {
res = add(max, -step);

return res === null ? null : Math.max(res, min);
}

res = add(value, -step);

return res === null ? null : Math.min(Math.max(res, min), max);
};
28 changes: 28 additions & 0 deletions src/lib/form-field/form-field-number-control.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Observable } from 'rxjs';


/** An interface which allows a control to work inside of a `MсFormField`. */
export abstract class McFormFieldNumberControl<T> {
/** The value of the control. */
value: T | null;

/**
* Stream that emits whenever the state of the control changes such that the parent `MсFormField`
* needs to run change detection.
*/
readonly stateChanges: Observable<void>;

/** the number step */
step: number;

/** the number big step */
bigStep: number;

/** Whether the control is focused. */
readonly focused: boolean;

/** Handles step up and down */
abstract stepUp(step: number): void;

abstract stepDown(step: number): void;
}
2 changes: 2 additions & 0 deletions src/lib/form-field/form-field.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
(click)="clearValue($event)">
<ng-content select="mc-cleaner"></ng-content>
</div>

<ng-content *ngIf="canShowStepper" select="mc-stepper"></ng-content>
</div>

<div class="mc-form-field__hint" *ngIf="hasHint">
Expand Down
7 changes: 5 additions & 2 deletions src/lib/form-field/form-field.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { McCleaner } from './cleaner';
import { McFormField, McFormFieldWithoutBorders } from './form-field';
import { McHint } from './hint';
import { McPrefix } from './prefix';
import { McStepper } from './stepper';
import { McSuffix } from './suffix';


Expand All @@ -17,7 +18,8 @@ import { McSuffix } from './suffix';
McHint,
McPrefix,
McSuffix,
McCleaner
McCleaner,
McStepper
],
imports: [CommonModule, McIconModule],
exports: [
Expand All @@ -26,7 +28,8 @@ import { McSuffix } from './suffix';
McHint,
McPrefix,
McSuffix,
McCleaner
McCleaner,
McStepper
]
})
export class McFormFieldModule {
Expand Down
Loading