fune/toolkit/content/widgets/tabbox.js

263 lines
6.9 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
class MozTabbox extends MozXULElement {
constructor() {
super();
this._handleMetaAltArrows = /Mac/.test(navigator.platform);
this.disconnectedCallback = this.disconnectedCallback.bind(this);
}
connectedCallback() {
Services.els.addSystemEventListener(document, "keydown", this, false);
window.addEventListener("unload", this.disconnectedCallback, { once: true });
}
disconnectedCallback() {
window.removeEventListener("unload", this.disconnectedCallback);
Services.els.removeSystemEventListener(document, "keydown", this, false);
}
set handleCtrlTab(val) {
this.setAttribute("handleCtrlTab", val);
return val;
}
get handleCtrlTab() {
return (this.getAttribute("handleCtrlTab") != "false");
}
/**
* _tabs and _tabpanels are deprecated, they exist only for
* backwards compatibility.
*/
get _tabs() {
return this.tabs;
}
get _tabpanels() {
return this.tabpanels;
}
get tabs() {
if (this.hasAttribute("tabcontainer")) {
return document.getElementById(this.getAttribute("tabcontainer"));
}
return this.getElementsByTagNameNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"tabs").item(0);
}
get tabpanels() {
return this.getElementsByTagNameNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"tabpanels").item(0);
}
set selectedIndex(val) {
let tabs = this.tabs;
if (tabs)
tabs.selectedIndex = val;
this.setAttribute("selectedIndex", val);
return val;
}
get selectedIndex() {
let tabs = this.tabs;
return tabs ? tabs.selectedIndex : -1;
}
set selectedTab(val) {
if (val) {
let tabs = this.tabs;
if (tabs)
tabs.selectedItem = val;
}
return val;
}
get selectedTab() {
let tabs = this.tabs;
return tabs && tabs.selectedItem;
}
set selectedPanel(val) {
if (val) {
let tabpanels = this.tabpanels;
if (tabpanels)
tabpanels.selectedPanel = val;
}
return val;
}
get selectedPanel() {
let tabpanels = this.tabpanels;
return tabpanels && tabpanels.selectedPanel;
}
handleEvent(event) {
if (!event.isTrusted) {
// Don't let untrusted events mess with tabs.
return;
}
// Don't check if the event was already consumed because tab
// navigation should always work for better user experience.
switch (event.keyCode) {
case event.DOM_VK_TAB:
if (event.ctrlKey && !event.altKey && !event.metaKey)
if (this.tabs && this.handleCtrlTab) {
this.tabs.advanceSelectedTab(event.shiftKey ? -1 : 1, true);
event.preventDefault();
}
break;
case event.DOM_VK_PAGE_UP:
if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey &&
this.tabs) {
this.tabs.advanceSelectedTab(-1, true);
event.preventDefault();
}
break;
case event.DOM_VK_PAGE_DOWN:
if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey &&
this.tabs) {
this.tabs.advanceSelectedTab(1, true);
event.preventDefault();
}
break;
case event.DOM_VK_LEFT:
if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
if (this.tabs && this._handleMetaAltArrows) {
let offset = window.getComputedStyle(this)
.direction == "ltr" ? -1 : 1;
this.tabs.advanceSelectedTab(offset, true);
event.preventDefault();
}
break;
case event.DOM_VK_RIGHT:
if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
if (this.tabs && this._handleMetaAltArrows) {
let offset = window.getComputedStyle(this)
.direction == "ltr" ? 1 : -1;
this.tabs.advanceSelectedTab(offset, true);
event.preventDefault();
}
break;
}
}
}
customElements.define("tabbox", MozTabbox);
class MozTabpanels extends MozXULElement {
connectedCallback() {
if (this.delayConnectedCallback()) {
return;
}
this._tabbox = null;
this._selectedPanel = this.children.item(this.selectedIndex);
}
get tabbox() {
// Memoize the result rather than replacing this getter, so that
// it can be reset if the parent changes.
if (this._tabbox) {
return this._tabbox;
}
let parent = this.parentNode;
while (parent) {
if (parent.localName == "tabbox") {
break;
}
parent = parent.parentNode;
}
return this._tabbox = parent;
}
set selectedIndex(val) {
if (val < 0 || val >= this.children.length)
return val;
let panel = this._selectedPanel;
this._selectedPanel = this.children[val];
if (this.getAttribute("async") != "true") {
this.setAttribute("selectedIndex", val);
}
if (this._selectedPanel != panel) {
let event = document.createEvent("Events");
event.initEvent("select", true, true);
this.dispatchEvent(event);
}
return val;
}
get selectedIndex() {
let indexStr = this.getAttribute("selectedIndex");
return indexStr ? parseInt(indexStr) : -1;
}
set selectedPanel(val) {
let selectedIndex = -1;
for (let panel = val; panel != null; panel = panel.previousElementSibling)
++selectedIndex;
this.selectedIndex = selectedIndex;
return val;
}
get selectedPanel() {
return this._selectedPanel;
}
/**
* nsIDOMXULRelatedElement
*/
getRelatedElement(aTabPanelElm) {
if (!aTabPanelElm)
return null;
let tabboxElm = this.tabbox;
if (!tabboxElm)
return null;
let tabsElm = tabboxElm.tabs;
if (!tabsElm)
return null;
// Return tab element having 'linkedpanel' attribute equal to the id
// of the tab panel or the same index as the tab panel element.
let tabpanelIdx = Array.indexOf(this.children, aTabPanelElm);
if (tabpanelIdx == -1)
return null;
let tabElms = tabsElm.children;
let tabElmFromIndex = tabElms[tabpanelIdx];
let tabpanelId = aTabPanelElm.id;
if (tabpanelId) {
for (let idx = 0; idx < tabElms.length; idx++) {
let tabElm = tabElms[idx];
if (tabElm.linkedPanel == tabpanelId)
return tabElm;
}
}
return tabElmFromIndex;
}
}
MozXULElement.implementCustomInterface(MozTabpanels, [Ci.nsIDOMXULRelatedElement]);
customElements.define("tabpanels", MozTabpanels);
}