-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Continue scrolling in custom scrollbars even when the cursor enters o…
…r goes past the boundary of an iframe (#41) This commit adds `SciMouseDispatcher` to dispatch 'mousemove' and 'mouseup' events between the application window and another window. Communication is based on `postMessage` and `onmessage` to safely propagate events cross-origin. fixes #41
- Loading branch information
1 parent
3e91321
commit 4527d2a
Showing
16 changed files
with
413 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* Copyright (c) 2018 Swiss Federal Railways | ||
* | ||
* This program and the accompanying materials are made | ||
* available under the terms of the Eclipse Public License 2.0 | ||
* which is available at https://www.eclipse.org/legal/epl-2.0/ | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
|
||
// Karma configuration file, see link for more information | ||
// https://karma-runner.github.io/1.0/config/configuration-file.html | ||
|
||
module.exports = function (config) { | ||
config.set({ | ||
basePath: '', | ||
frameworks: ['jasmine', '@angular-devkit/build-angular'], | ||
plugins: [ | ||
require('karma-jasmine'), | ||
require('karma-chrome-launcher'), | ||
require('karma-jasmine-html-reporter'), | ||
require('karma-coverage-istanbul-reporter'), | ||
require('@angular-devkit/build-angular/plugins/karma') | ||
], | ||
client: { | ||
clearContext: false // leave Jasmine Spec Runner output visible in browser | ||
}, | ||
coverageIstanbulReporter: { | ||
dir: require('path').join(__dirname, '../../../coverage'), | ||
reports: ['html', 'lcovonly'], | ||
fixWebpackSourcePaths: true | ||
}, | ||
reporters: ['progress', 'kjhtml'], | ||
port: 9876, | ||
colors: true, | ||
logLevel: config.LOG_INFO, | ||
autoWatch: true, | ||
browsers: ['Chrome'], | ||
singleRun: false | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", | ||
"dest": "../../../dist/scion/mouse-dispatcher", | ||
"lib": { | ||
"entryFile": "src/public_api.ts" | ||
}, | ||
"whitelistedNonPeerDependencies": ["@scion"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"name": "@scion/mouse-dispatcher", | ||
"version": "0.0.0-beta.11", | ||
"description": "Dispatches mouse events between the application window and another cross-origin window.", | ||
"license": "EPL-2.0", | ||
"homepage": "https://github.com/SchweizerischeBundesbahnen/scion-workbench", | ||
"bugs": { | ||
"url": "https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues" | ||
}, | ||
"author": { | ||
"name": "SCION Workbench contributors", | ||
"url": "https://github.com/SchweizerischeBundesbahnen/scion-workbench" | ||
}, | ||
"dependencies": { | ||
}, | ||
"peerDependencies": { | ||
"rxjs": "^6.0.0" | ||
}, | ||
"keywords": [ | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/SchweizerischeBundesbahnen/scion-workbench.git" | ||
} | ||
} |
139 changes: 139 additions & 0 deletions
139
projects/scion/mouse-dispatcher/src/lib/mouse-dispatcher.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
/* | ||
* Copyright (c) 2018 Swiss Federal Railways | ||
* | ||
* This program and the accompanying materials are made | ||
* available under the terms of the Eclipse Public License 2.0 | ||
* which is available at https://www.eclipse.org/legal/epl-2.0/ | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
|
||
import { fromEvent, merge, Subject } from 'rxjs'; | ||
import { filter, takeUntil } from 'rxjs/operators'; | ||
|
||
/** | ||
* Indicates that the primary mouse button is pressed (usually left). | ||
*/ | ||
const PRIMARY_MOUSE_BUTTON = 1; | ||
|
||
/** | ||
* Installs dispatching of the mouse events 'mousemove' (if pressed the primary mouse button) and 'mouseup' | ||
* between the application window and the given target window. | ||
* | ||
* Communication is based on `postMessage` and `onmessage` to safely propagate events cross-origin. | ||
* | ||
* Events are dispatched as synthetic events via document event dispatcher of the target window. | ||
* A 'mousemove' event with the primary mouse button pressed is dispatched as 'sci-mousemove' event | ||
* and a 'mouseup' event as 'sci-mouseup' event. Properties dispatched are `screenX` and `screenY`. | ||
* | ||
* Mouse event dispatching is fundamental if using custom scrollbars in combination with iframes. It provides | ||
* continued delivery of mouse events even when the cursor goes past the boundary of the iframe boundary. | ||
* | ||
* @param targetWindow | ||
* A reference to the window to dispatch mouse events | ||
* @param targetOrigin | ||
* Specifies what the origin of `targetWindow` must be for the events to be, | ||
* either as the literal string "*" (indicating no preference) or as a URI. | ||
* @return handle to uninstall mouse dispatching | ||
*/ | ||
export function installMouseDispatcher(targetWindow: Window, targetOrigin: string): SciMouseDispatcher { | ||
const destroy$ = new Subject<void>(); | ||
|
||
// Dispatch native mouse events to the target window | ||
merge( | ||
fromEvent<MouseEvent>(document, 'mousemove').pipe(filter(event => event.buttons === PRIMARY_MOUSE_BUTTON)), | ||
fromEvent<MouseEvent>(document, 'mouseup'), | ||
) | ||
.pipe(takeUntil(destroy$)) | ||
.subscribe((event: MouseEvent) => { | ||
targetWindow.postMessage({ | ||
type: `sci-${event.type}`, | ||
screenX: event.screenX, | ||
screenY: event.screenY, | ||
}, targetOrigin); | ||
}); | ||
|
||
// Dispatch synthetic mouse events to the target window (unless emitted itself) | ||
merge( | ||
fromEvent<SciMouseEvent>(document, 'sci-mousemove'), | ||
fromEvent<SciMouseEvent>(document, 'sci-mouseup') | ||
) | ||
.pipe( | ||
filter((event: SciMouseEvent) => event.source !== targetWindow), | ||
takeUntil(destroy$), | ||
) | ||
.subscribe((event: SciMouseEvent) => { | ||
targetWindow.postMessage({ | ||
type: event.type, | ||
screenX: event.screenX, | ||
screenY: event.screenY, | ||
}, targetOrigin); | ||
}); | ||
|
||
|
||
// Dispatch synthetic mouse events received from the target window to this document's event bus | ||
fromEvent<MessageEvent>(window, 'message') | ||
.pipe(takeUntil(destroy$)) | ||
.subscribe((messageEvent: MessageEvent) => { | ||
if (messageEvent.source !== targetWindow) { | ||
return; | ||
} | ||
|
||
if (targetOrigin !== '*' && messageEvent.origin !== targetOrigin) { | ||
throw Error(`[OriginError] Message of illegal origin received [expected=${targetOrigin}, actual=${messageEvent.origin}]`); | ||
} | ||
|
||
const mouseEvent = parseSyntheticMouseEvent(messageEvent); | ||
mouseEvent && document.dispatchEvent(mouseEvent); | ||
}); | ||
|
||
return { | ||
dispose: (): void => destroy$.next() | ||
}; | ||
} | ||
|
||
function parseSyntheticMouseEvent(messageEvent: MessageEvent): Event & SciMouseEvent | null { | ||
const event: SciMouseEvent = messageEvent.data; | ||
if (isNullOrUndefined(event) || typeof event !== 'object') { | ||
return null; | ||
} | ||
if (isNullOrUndefined(event.type) || !event.type.startsWith('sci-mouse')) { | ||
return null; | ||
} | ||
if (isNullOrUndefined(event.screenX)) { | ||
return null; | ||
} | ||
if (isNullOrUndefined(event.screenY)) { | ||
return null; | ||
} | ||
|
||
const syntheticMouseEvent: any = new Event(event.type); | ||
syntheticMouseEvent.screenX = event.screenX; | ||
syntheticMouseEvent.screenY = event.screenY; | ||
syntheticMouseEvent.source = messageEvent.source; | ||
return syntheticMouseEvent; | ||
} | ||
|
||
function isNullOrUndefined(value: any): boolean { | ||
return value === null || value === undefined; | ||
} | ||
|
||
/** | ||
* Synthetic mouse event dispatched from another window. | ||
*/ | ||
export interface SciMouseEvent { | ||
type: 'sci-mousemove' | 'sci-mouseup'; | ||
screenX: number; | ||
screenY: number; | ||
source?: Window; | ||
} | ||
|
||
/** | ||
* Dispatches mouse events between the application window and another cross-origin window. | ||
*/ | ||
export interface SciMouseDispatcher { | ||
/** | ||
* Invoke to uninstall mouse dispatching. | ||
*/ | ||
dispose(): void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* | ||
* Copyright (c) 2018 Swiss Federal Railways | ||
* | ||
* This program and the accompanying materials are made | ||
* available under the terms of the Eclipse Public License 2.0 | ||
* which is available at https://www.eclipse.org/legal/epl-2.0/ | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
|
||
/** | ||
* Entry point for all public APIs of this package. | ||
*/ | ||
export * from './lib/mouse-dispatcher'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
* Copyright (c) 2018 Swiss Federal Railways | ||
* | ||
* This program and the accompanying materials are made | ||
* available under the terms of the Eclipse Public License 2.0 | ||
* which is available at https://www.eclipse.org/legal/epl-2.0/ | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
|
||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files | ||
|
||
import 'core-js/es7/reflect'; | ||
import 'zone.js/dist/zone'; | ||
import 'zone.js/dist/zone-testing'; | ||
import { getTestBed } from '@angular/core/testing'; | ||
import { | ||
BrowserDynamicTestingModule, | ||
platformBrowserDynamicTesting | ||
} from '@angular/platform-browser-dynamic/testing'; | ||
|
||
declare const require: any; | ||
|
||
// First, initialize the Angular testing environment. | ||
getTestBed().initTestEnvironment( | ||
BrowserDynamicTestingModule, | ||
platformBrowserDynamicTesting() | ||
); | ||
// Then we find all the tests. | ||
const context = require.context('./', true, /\.spec\.ts$/); | ||
// And load the modules. | ||
context.keys().map(context); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"extends": "../../../tsconfig.json", | ||
"compilerOptions": { | ||
"outDir": "../../../out-tsc/lib", | ||
"target": "es2015", | ||
"module": "es2015", | ||
"moduleResolution": "node", | ||
"declaration": true, | ||
"sourceMap": true, | ||
"inlineSources": true, | ||
"emitDecoratorMetadata": true, | ||
"experimentalDecorators": true, | ||
"importHelpers": true, | ||
"types": [], | ||
"lib": [ | ||
"dom", | ||
"es2018" | ||
] | ||
}, | ||
"angularCompilerOptions": { | ||
"annotateForClosureCompiler": true, | ||
"skipTemplateCodegen": true, | ||
"strictMetadataEmit": true, | ||
"fullTemplateTypeCheck": true, | ||
"strictInjectionParameters": true, | ||
"enableResourceInlining": true | ||
}, | ||
"exclude": [ | ||
"src/test.ts", | ||
"**/*.spec.ts" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"extends": "../../../tsconfig.json", | ||
"compilerOptions": { | ||
"outDir": "../../../out-tsc/spec", | ||
"types": [ | ||
"jasmine", | ||
"node" | ||
] | ||
}, | ||
"files": [ | ||
"src/test.ts" | ||
], | ||
"include": [ | ||
"**/*.spec.ts", | ||
"**/*.d.ts" | ||
] | ||
} |
Oops, something went wrong.