blob: 8e27c7435d66e09b6d32a1d1c821757a20ec34c5 [file] [log] [blame]
/**
* @license
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {property} from '@polymer/decorators';
import {PolymerElement} from '@polymer/polymer';
import {check, Constructor} from '../../utils/common-util';
import {getAppContext} from '../../services/app-context';
import {
Shortcut,
ShortcutSection,
SPECIAL_SHORTCUT,
} from '../../services/shortcuts/shortcuts-config';
import {
SectionView,
ShortcutListener,
} from '../../services/shortcuts/shortcuts-service';
export {
Shortcut,
ShortcutSection,
SPECIAL_SHORTCUT,
ShortcutListener,
SectionView,
};
export const KeyboardShortcutMixin = <T extends Constructor<PolymerElement>>(
superClass: T
) => {
/**
* @polymer
* @mixinClass
*/
class Mixin extends superClass {
// This enables `Shortcut` to be used in the html template.
Shortcut = Shortcut;
// This enables `ShortcutSection` to be used in the html template.
ShortcutSection = ShortcutSection;
private readonly shortcuts = getAppContext().shortcutsService;
/** Used to disable shortcuts when the element is not visible. */
private observer?: IntersectionObserver;
/**
* Enabling shortcuts only when the element is visible (see `observer`
* above) is a great feature, but often what you want is for the *page* to
* be visible, not the specific child element that registers keyboard
* shortcuts. An example is the FileList in the ChangeView. So we allow
* a broader observer target to be specified here, and fall back to
* `this` as the default.
*/
@property({type: Object})
observerTarget: Element = this;
/** Are shortcuts currently enabled? True only when element is visible. */
private bindingsEnabled = false;
override connectedCallback() {
super.connectedCallback();
this.createVisibilityObserver();
this.enableBindings();
}
override disconnectedCallback() {
this.destroyVisibilityObserver();
this.disableBindings();
super.disconnectedCallback();
}
/**
* Creates an intersection observer that enables bindings when the
* element is visible and disables them when the element is hidden.
*/
private createVisibilityObserver() {
if (!this.hasKeyboardShortcuts()) return;
if (this.observer) return;
this.observer = new IntersectionObserver(entries => {
check(entries.length === 1, 'Expected one observer entry.');
const isVisible = entries[0].isIntersecting;
if (isVisible) {
this.enableBindings();
} else {
this.disableBindings();
}
});
this.observer.observe(this.observerTarget);
}
private destroyVisibilityObserver() {
if (this.observer) this.observer.unobserve(this.observerTarget);
}
/**
* Enables all the shortcuts returned by keyboardShortcuts().
* This is a private method being called when the element becomes
* connected or visible.
*/
private enableBindings() {
if (!this.hasKeyboardShortcuts()) return;
if (this.bindingsEnabled) return;
this.bindingsEnabled = true;
this.shortcuts.attachHost(this, this.keyboardShortcuts());
}
/**
* Disables all the shortcuts returned by keyboardShortcuts().
* This is a private method being called when the element becomes
* disconnected or invisible.
*/
private disableBindings() {
if (!this.bindingsEnabled) return;
this.bindingsEnabled = false;
this.shortcuts.detachHost(this);
}
private hasKeyboardShortcuts() {
return this.keyboardShortcuts().length > 0;
}
keyboardShortcuts(): ShortcutListener[] {
return [];
}
}
return Mixin as T & Constructor<KeyboardShortcutMixinInterface>;
};
/** The interface corresponding to KeyboardShortcutMixin */
export interface KeyboardShortcutMixinInterface {
keyboardShortcuts(): ShortcutListener[];
}