chore(*): use stricter types (#82)
This commit is contained in:
Родитель
8d83baa845
Коммит
00c97691ee
|
@ -35,12 +35,13 @@ import { FocusService } from '../../../../src';
|
|||
`],
|
||||
})
|
||||
export class BenchmarkPageComponent {
|
||||
public tiles: string[];
|
||||
public tiles: string[] = [];
|
||||
public iterations = 500;
|
||||
public results: string[] = [];
|
||||
public testElems = 100;
|
||||
|
||||
@ViewChildren('tiles') public tilesElemRef: QueryList<ElementRef>;
|
||||
@ViewChildren('tiles')
|
||||
public tilesElemRef!: QueryList<ElementRef>;
|
||||
|
||||
constructor(
|
||||
private focusService: FocusService,
|
||||
|
@ -66,7 +67,6 @@ export class BenchmarkPageComponent {
|
|||
}
|
||||
|
||||
private initTestElems(count: number) {
|
||||
this.tiles = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
this.tiles.push('Tile: ' + i);
|
||||
}
|
||||
|
|
|
@ -6,17 +6,14 @@ import {
|
|||
EventEmitter,
|
||||
NgModule,
|
||||
OnDestroy,
|
||||
Output
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Observable } from 'rxjs';
|
||||
import { BenchmarkPageComponent } from './benchmark-page.component';
|
||||
|
||||
import 'rxjs/add/observable/interval';
|
||||
import 'rxjs/add/operator/take';
|
||||
|
||||
import { ArcModule, Direction, FocusService, InputService, RegistryService } from '../../../../src';
|
||||
import { Page1Component } from './page1.component';
|
||||
import { Page2Component } from './page2.component';
|
||||
|
|
|
@ -5,18 +5,20 @@ const defaultDuration = 500; //ms
|
|||
@Injectable()
|
||||
export class ScrollService {
|
||||
public scrollCompleted = new EventEmitter();
|
||||
private scrollContainer: HTMLElement;
|
||||
private startTime: number;
|
||||
private duration: number;
|
||||
private startOffset: number;
|
||||
private endOffset: number;
|
||||
private raf: number;
|
||||
private scrollContainer?: HTMLElement | Window;
|
||||
private startTime?: number;
|
||||
private duration?: number;
|
||||
private startOffset?: number;
|
||||
private endOffset?: number;
|
||||
private raf?: number;
|
||||
|
||||
public smoothScroll(scrollContainer: HTMLElement, endOffset: number, duration?: number) {
|
||||
cancelAnimationFrame(this.raf);
|
||||
public smoothScroll(scrollContainer: HTMLElement | Window = window, endOffset: number, duration?: number) {
|
||||
if (this.raf) {
|
||||
cancelAnimationFrame(this.raf);
|
||||
}
|
||||
this.duration = duration || defaultDuration;
|
||||
this.scrollContainer = scrollContainer || <any>window;
|
||||
this.startOffset = scrollContainer.scrollTop || window.pageYOffset;
|
||||
this.scrollContainer = scrollContainer;
|
||||
this.startOffset = scrollContainer instanceof Window ? scrollContainer.scrollY : scrollContainer.scrollTop;
|
||||
this.endOffset = endOffset < 0 ? 0 : endOffset;
|
||||
this.startTime = performance.now();
|
||||
|
||||
|
@ -24,11 +26,14 @@ export class ScrollService {
|
|||
}
|
||||
|
||||
private step(currentTime: number) {
|
||||
if (!this.startTime || !this.scrollContainer || !this.duration) {
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
const elapsed = currentTime - this.startTime;
|
||||
if (this.scrollContainer !== <any>window) {
|
||||
this.scrollContainer.scrollTop = this.getPositionAt(elapsed);
|
||||
} else {
|
||||
if (this.scrollContainer instanceof Window) {
|
||||
window.scroll(0, this.getPositionAt(elapsed));
|
||||
} else {
|
||||
this.scrollContainer.scrollTop = this.getPositionAt(elapsed);
|
||||
}
|
||||
|
||||
if (elapsed < this.duration) {
|
||||
|
@ -36,10 +41,15 @@ export class ScrollService {
|
|||
} else {
|
||||
this.scrollCompleted.next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private getPositionAt(elapsedTime: number) {
|
||||
if (elapsedTime > this.duration) { return this.endOffset; }
|
||||
private getPositionAt(elapsedTime: number): number {
|
||||
if (!this.duration || !this.startOffset || !this.endOffset) {
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
if (elapsedTime > this.duration) {
|
||||
return this.endOffset;
|
||||
}
|
||||
return this.startOffset + (this.endOffset - this.startOffset) * this.easeOutCubic(elapsedTime / this.duration);
|
||||
}
|
||||
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
52
package.json
52
package.json
|
@ -37,39 +37,39 @@
|
|||
"@angular/platform-browser": "~4.1.0",
|
||||
"@angular/platform-browser-dynamic": "~4.1.0",
|
||||
"@angular/router": "~4.1.0",
|
||||
"@types/jasmine": "^2.5.35",
|
||||
"@types/node": "^8.0.16",
|
||||
"@types/winrt-uwp": "0.0.16",
|
||||
"awesome-typescript-loader": "^3.0.3",
|
||||
"clean-webpack-plugin": "^0.1.10",
|
||||
"codelyzer": "^3.1.2",
|
||||
"core-js": "^2.4.1",
|
||||
"@types/jasmine": "^2.8.6",
|
||||
"@types/node": "^8.9.4",
|
||||
"@types/winrt-uwp": "0.0.19",
|
||||
"awesome-typescript-loader": "^3.4.1",
|
||||
"clean-webpack-plugin": "^0.1.18",
|
||||
"codelyzer": "^3.2.2",
|
||||
"core-js": "^2.5.3",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-clean": "^0.3.2",
|
||||
"gulp-clean": "^0.4.0",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-typescript": "^3.2.1",
|
||||
"html-webpack-plugin": "^2.22.0",
|
||||
"jasmine-core": "^2.5.2",
|
||||
"karma": "^1.1.1",
|
||||
"gulp-typescript": "^4.0.1",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"jasmine-core": "^2.99.1",
|
||||
"karma": "^1.7.1",
|
||||
"karma-browserstack-launcher": "^1.0.1",
|
||||
"karma-chrome-launcher": "^2.0.0",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-firefox-launcher": "^1.0.0",
|
||||
"karma-jasmine": "^1.0.2",
|
||||
"karma-mocha-reporter": "^2.2.0",
|
||||
"karma-sauce-launcher": "^1.0.0",
|
||||
"karma-firefox-launcher": "^1.1.0",
|
||||
"karma-jasmine": "^1.1.1",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-sauce-launcher": "^1.2.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"merge2": "^1.0.2",
|
||||
"npm-run-all": "^4.0.0",
|
||||
"rimraf": "^2.5.1",
|
||||
"run-sequence": "^1.2.2",
|
||||
"systemjs": "^0.20.17",
|
||||
"merge2": "^1.2.1",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"rimraf": "^2.6.2",
|
||||
"run-sequence": "^2.2.1",
|
||||
"systemjs": "^0.20.19",
|
||||
"ts-helpers": "^1.1.1",
|
||||
"tslint": "^5.9.1",
|
||||
"tslint-microsoft-contrib": "5.0.1",
|
||||
"typescript": "^2.4.2",
|
||||
"webpack": "^2.2.1",
|
||||
"webpack-dev-server": "^2.6.1",
|
||||
"zone.js": "^0.8.14"
|
||||
"tslint-microsoft-contrib": "^5.0.3",
|
||||
"typescript": "^2.7.2",
|
||||
"webpack": "^3.11.0",
|
||||
"webpack-dev-server": "^2.11.1",
|
||||
"zone.js": "^0.8.20"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,7 @@ import {
|
|||
OnInit,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import 'rxjs/add/observable/never';
|
||||
import 'rxjs/add/operator/startWith';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { Direction, IArcEvent, IArcHandler } from './model';
|
||||
import { RegistryService } from './registry.service';
|
||||
|
@ -65,7 +62,7 @@ export class ArcDirective implements OnInit, OnDestroy, IArcHandler {
|
|||
}
|
||||
|
||||
@Input('arc-focus-inside')
|
||||
public arcFocusInside: boolean;
|
||||
public arcFocusInside: boolean = false;
|
||||
|
||||
// Directional/event shortcuts: =============================================
|
||||
|
||||
|
@ -96,16 +93,16 @@ export class ArcDirective implements OnInit, OnDestroy, IArcHandler {
|
|||
}
|
||||
|
||||
@Input('arc-focus-left')
|
||||
public arcFocusLeft: HTMLElement | string;
|
||||
public arcFocusLeft?: HTMLElement | string;
|
||||
|
||||
@Input('arc-focus-right')
|
||||
public arcFocusRight: HTMLElement | string;
|
||||
public arcFocusRight?: HTMLElement | string;
|
||||
|
||||
@Input('arc-focus-up')
|
||||
public arcFocusUp: HTMLElement | string;
|
||||
public arcFocusUp?: HTMLElement | string;
|
||||
|
||||
@Input('arc-focus-down')
|
||||
public arcFocusDown: HTMLElement | string;
|
||||
public arcFocusDown?: HTMLElement | string;
|
||||
|
||||
private handlers: ((ev: IArcEvent) => void)[] = [];
|
||||
private innerExcludeThis = false;
|
||||
|
|
29
src/event.ts
29
src/event.ts
|
@ -1,21 +1,30 @@
|
|||
import { Direction, IArcEvent, IArcHandler } from './model';
|
||||
|
||||
export class ArcEvent implements IArcEvent {
|
||||
public readonly directive: IArcHandler;
|
||||
public next: HTMLElement;
|
||||
|
||||
public readonly directive?: IArcHandler;
|
||||
public readonly event: Direction;
|
||||
public readonly target: HTMLElement;
|
||||
public readonly target: HTMLElement | null;
|
||||
public next: HTMLElement | null;
|
||||
|
||||
public defaultPrevented = false;
|
||||
public propagationStopped = false;
|
||||
|
||||
constructor(opts: {
|
||||
directive?: IArcHandler,
|
||||
next: HTMLElement | null,
|
||||
event: Direction,
|
||||
target: HTMLElement | null,
|
||||
}) { Object.assign(this, opts); }
|
||||
constructor({
|
||||
event,
|
||||
target,
|
||||
next,
|
||||
directive,
|
||||
}: {
|
||||
directive?: IArcHandler;
|
||||
next: HTMLElement | null;
|
||||
event: Direction;
|
||||
target: HTMLElement | null;
|
||||
}) {
|
||||
this.directive = directive;
|
||||
this.event = event;
|
||||
this.target = target;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
public stopPropagation() {
|
||||
this.propagationStopped = true;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import 'rxjs/add/operator/filter';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { ArcEvent } from './event';
|
||||
import { FocusByRegistry } from './focus-strategies/focus-by-registry';
|
||||
|
@ -219,10 +217,10 @@ function isDirectional(ev: Direction) {
|
|||
function quad(start: number, end: number, progress: number): number {
|
||||
const diff = end - start;
|
||||
if (progress < 0.5) {
|
||||
return diff * (2 * progress * progress) + start;
|
||||
return diff * (2 * progress ** 2) + start;
|
||||
} else {
|
||||
const displaced = progress - 1;
|
||||
return diff * ((-2 * displaced * displaced) + 1) + start;
|
||||
return diff * ((-2 * displaced ** 2) + 1) + start;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,15 +243,15 @@ export class FocusService {
|
|||
*/
|
||||
public scrollSpeed: number | null = 1000;
|
||||
// Focus root, the service operates below here.
|
||||
private root: HTMLElement;
|
||||
private root: HTMLElement | null = null;
|
||||
public focusRoot: HTMLElement = defaultFocusRoot;
|
||||
// The previous rectange that the user had selected.
|
||||
private historyRect = defaultRect;
|
||||
// Subscription to focus update events.
|
||||
private registrySubscription: Subscription;
|
||||
private registrySubscription?: Subscription;
|
||||
|
||||
// The currently selected element.
|
||||
public selected: HTMLElement | null;
|
||||
public selected: HTMLElement | null = null;
|
||||
// The client bounding rect when we first selected the element, cached
|
||||
// so that we can reuse it if the element gets detached.
|
||||
private referenceRect: ClientRect = defaultRect;
|
||||
|
@ -352,6 +350,9 @@ export class FocusService {
|
|||
* e.g. when intercepting and transfering focus
|
||||
*/
|
||||
public selectNodeWithoutEvent(next: HTMLElement, scrollSpeed: number | null = this.scrollSpeed) {
|
||||
if (!this.root) {
|
||||
throw new Error('root not set');
|
||||
}
|
||||
if (this.selected === next) {
|
||||
return;
|
||||
}
|
||||
|
@ -369,6 +370,9 @@ export class FocusService {
|
|||
}
|
||||
|
||||
private triggerOnFocusHandlers(next: HTMLElement) {
|
||||
if (!this.root) {
|
||||
throw new Error('root not set');
|
||||
}
|
||||
const isAttached = this.selected !== null && this.root.contains(this.selected);
|
||||
if (!isAttached) {
|
||||
let elem: HTMLElement | null = next;
|
||||
|
@ -421,6 +425,9 @@ export class FocusService {
|
|||
* Frees resources associated with the service.
|
||||
*/
|
||||
public teardown() {
|
||||
if (!this.registrySubscription) {
|
||||
return;
|
||||
}
|
||||
this.registrySubscription.unsubscribe();
|
||||
}
|
||||
|
||||
|
@ -684,11 +691,10 @@ export class FocusService {
|
|||
* Reset the focus if arcade-machine wanders out of root
|
||||
*/
|
||||
private setDefaultFocus(scrollSpeed: number | null = this.scrollSpeed) {
|
||||
const { selected } = this;
|
||||
const focusableElems = this.focusRoot.querySelectorAll('[tabIndex]');
|
||||
for (let i = 0; i < focusableElems.length; i += 1) {
|
||||
const potentialElement = <HTMLElement>focusableElems[i];
|
||||
if (selected === potentialElement || !this.isFocusable(potentialElement)) {
|
||||
if (this.selected === potentialElement || !this.isFocusable(potentialElement)) {
|
||||
continue;
|
||||
}
|
||||
const potentialRect = roundRect(potentialElement.getBoundingClientRect());
|
||||
|
@ -835,11 +841,11 @@ export class FocusService {
|
|||
}
|
||||
|
||||
private updateHistoryRect(direction: Direction, result: {
|
||||
element: HTMLElement,
|
||||
rect: ClientRect,
|
||||
referenceRect: ClientRect,
|
||||
element: HTMLElement;
|
||||
rect: ClientRect;
|
||||
referenceRect: ClientRect;
|
||||
}) {
|
||||
const newHistoryRect: IMutableClientRect = Object.assign({}, defaultRect);
|
||||
const newHistoryRect: IMutableClientRect = {...defaultRect};
|
||||
// It's possible to get into a situation where the target element has
|
||||
// no overlap with the reference edge.
|
||||
//
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// tslint:disable-next-line:import-destructuring-spacing
|
||||
import { } from 'jasmine';
|
||||
// tslint:disable-next-line no-implicit-dependencies
|
||||
import 'jasmine';
|
||||
import { FocusService } from './focus.service';
|
||||
import { InputService } from './input.service';
|
||||
import { Direction } from './model';
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import { EventEmitter, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Observable, Subject, Subscription } from 'rxjs';
|
||||
import { keys } from 'uwp-keycodes';
|
||||
|
||||
import 'rxjs/add/observable/fromEvent';
|
||||
import 'rxjs/add/observable/merge';
|
||||
|
||||
import { ArcEvent } from './event';
|
||||
import { FocusService } from './focus.service';
|
||||
import { Direction } from './model';
|
||||
|
@ -45,7 +40,7 @@ class DirectionalDebouncer {
|
|||
/**
|
||||
* fn is a bound function that can be called to check if the key is held.
|
||||
*/
|
||||
public fn: (time: number) => boolean;
|
||||
public fn?: (time: number) => boolean;
|
||||
|
||||
/**
|
||||
* Initial debounce after a joystick is pressed before beginning shorter
|
||||
|
@ -317,9 +312,9 @@ export class InputService {
|
|||
* here, but this is mostly for testing purposes.
|
||||
*/
|
||||
public keyboardSrc = new Subject<{
|
||||
defaultPrevented: boolean,
|
||||
keyCode: number,
|
||||
preventDefault: () => void,
|
||||
defaultPrevented: boolean;
|
||||
keyCode: number;
|
||||
preventDefault: () => void;
|
||||
}>();
|
||||
|
||||
private gamepads: { [key: string]: IGamepadWrapper } = {};
|
||||
|
@ -343,8 +338,8 @@ export class InputService {
|
|||
// We want the gamepad to provide gamepad VK keyboard events rather than moving a
|
||||
// mouse like cursor. The gamepad will provide such keyboard events and provide
|
||||
// input to the DOM
|
||||
(<any>navigator).gamepadInputEmulation = 'keyboard';
|
||||
} else if (typeof navigator.getGamepads === 'function') {
|
||||
navigator.gamepadInputEmulation = 'keyboard';
|
||||
} else if ('getGamepads' in navigator) {
|
||||
// Poll connected gamepads and use that for input if keyboard emulation isn't available
|
||||
this.watchForGamepad();
|
||||
}
|
||||
|
|
12
src/model.ts
12
src/model.ts
|
@ -47,10 +47,10 @@ export interface IArcEvent {
|
|||
// unless the element is cancelled. This *is* settable and you can use it
|
||||
// to modify the focus target. This will be set to `null` on non-directional
|
||||
// navigation or if we can't find a subsequent element to select.
|
||||
next?: HTMLElement;
|
||||
next: HTMLElement | null;
|
||||
|
||||
readonly event: Direction;
|
||||
readonly target: HTMLElement;
|
||||
readonly target: HTMLElement | null;
|
||||
readonly defaultPrevented: boolean;
|
||||
|
||||
stopPropagation(): void;
|
||||
|
@ -92,10 +92,10 @@ export interface IArcHandler {
|
|||
*/
|
||||
onFocus?(el: HTMLElement | null): void;
|
||||
|
||||
arcFocusLeft: HTMLElement | string;
|
||||
arcFocusRight: HTMLElement | string;
|
||||
arcFocusUp: HTMLElement | string;
|
||||
arcFocusDown: HTMLElement | string;
|
||||
arcFocusLeft?: HTMLElement | string;
|
||||
arcFocusRight?: HTMLElement | string;
|
||||
arcFocusUp?: HTMLElement | string;
|
||||
arcFocusDown?: HTMLElement | string;
|
||||
|
||||
/**
|
||||
* If focused, the element transfers focus to its children if true
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { IArcHandler } from './model';
|
||||
|
||||
|
|
|
@ -5,19 +5,18 @@
|
|||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"strict": true,
|
||||
"noStrictGenericChecks": true,
|
||||
"module": "commonjs",
|
||||
"sourceMap": true,
|
||||
"target": "es5",
|
||||
"pretty": true,
|
||||
"declaration": true,
|
||||
"skipLibCheck": true,
|
||||
"noStrictGenericChecks": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
|
|
10
tslint.json
10
tslint.json
|
@ -27,9 +27,7 @@
|
|||
// Basic
|
||||
"import-blacklist": [
|
||||
true,
|
||||
"lodash",
|
||||
"rxjs/Rx",
|
||||
"rxjs"
|
||||
"rxjs/Rx"
|
||||
],
|
||||
"no-multiline-string": false,
|
||||
"arrow-parens": [true, "ban-single-arg-parens"],
|
||||
|
@ -108,6 +106,10 @@
|
|||
"no-string-literal": false,
|
||||
"no-string-throw": true,
|
||||
"no-empty-line-after-opening-brace": true,
|
||||
"no-function-expression": true
|
||||
"no-function-expression": true,
|
||||
"no-implicit-dependencies": [true, "dev"],
|
||||
"no-parameter-reassignment": false,
|
||||
"binary-expression-operand-order": false,
|
||||
"no-unnecessary-class": false
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче