This commit is contained in:
Simon Schick 2018-02-23 20:11:06 +01:00 коммит произвёл Connor Peet
Родитель 8d83baa845
Коммит 00c97691ee
14 изменённых файлов: 1961 добавлений и 877 удалений

Просмотреть файл

@ -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);
}

2603
package-lock.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;

Просмотреть файл

@ -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();
}

Просмотреть файл

@ -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"

Просмотреть файл

@ -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
}
}