gecko-dev/toolkit/content/widgets/popup.xml

385 строки
13 KiB
XML

<?xml version="1.0"?>
<!-- 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/. -->
<!-- This files relies on these specific Chrome/XBL globals -->
<!-- globals PopupBoxObject -->
<bindings id="popupBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="popup">
<content>
<xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
smoothscroll="false">
<children/>
</xul:arrowscrollbox>
</content>
<implementation>
<field name="scrollBox" readonly="true">
document.getAnonymousElementByAttribute(this, "class", "popup-internal-box");
</field>
</implementation>
<handlers>
<handler event="popupshowing" phase="target">
<![CDATA[
var array = [];
var width = 0;
for (var menuitem = this.firstElementChild; menuitem; menuitem = menuitem.nextElementSibling) {
if (menuitem.localName == "menuitem" && menuitem.hasAttribute("acceltext")) {
var accel = document.getAnonymousElementByAttribute(menuitem, "anonid", "accel");
if (accel && accel.boxObject) {
array.push(accel);
if (accel.boxObject.width > width)
width = accel.boxObject.width;
}
}
}
for (var i = 0; i < array.length; i++)
array[i].width = width;
]]>
</handler>
</handlers>
</binding>
<binding id="panel">
<implementation>
<field name="_prevFocus">0</field>
</implementation>
<handlers>
<handler event="popupshowing"><![CDATA[
// Capture the previous focus before has a chance to get set inside the panel
try {
this._prevFocus = Cu
.getWeakReference(document.commandDispatcher.focusedElement);
if (this._prevFocus.get())
return;
} catch (ex) { }
this._prevFocus = Cu.getWeakReference(document.activeElement);
]]></handler>
<handler event="popupshown"><![CDATA[
// Fire event for accessibility APIs
var alertEvent = document.createEvent("Events");
alertEvent.initEvent("AlertActive", true, true);
this.dispatchEvent(alertEvent);
]]></handler>
<handler event="popuphiding"><![CDATA[
try {
this._currentFocus = document.commandDispatcher.focusedElement;
} catch (e) {
this._currentFocus = document.activeElement;
}
]]></handler>
<handler event="popuphidden"><![CDATA[
function doFocus() {
// Focus was set on an element inside this panel,
// so we need to move it back to where it was previously
try {
let fm = Cc["@mozilla.org/focus-manager;1"]
.getService(Ci.nsIFocusManager);
fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
} catch (e) {
prevFocus.focus();
}
}
var currentFocus = this._currentFocus;
var prevFocus = this._prevFocus ? this._prevFocus.get() : null;
this._currentFocus = null;
this._prevFocus = null;
// Avoid changing focus if focus changed while we hide the popup
// (This can happen e.g. if the popup is hiding as a result of a
// click/keypress that focused something)
let nowFocus;
try {
nowFocus = document.commandDispatcher.focusedElement;
} catch (e) {
nowFocus = document.activeElement;
}
if (nowFocus && nowFocus != currentFocus)
return;
if (prevFocus && this.getAttribute("norestorefocus") != "true") {
// Try to restore focus
try {
if (document.commandDispatcher.focusedWindow != window)
return; // Focus has already been set to a window outside of this panel
} catch (ex) {}
if (!currentFocus) {
doFocus();
return;
}
while (currentFocus) {
if (currentFocus == this) {
doFocus();
return;
}
currentFocus = currentFocus.parentNode;
}
}
]]></handler>
</handlers>
</binding>
<binding id="arrowpanel" extends="chrome://global/content/bindings/popup.xml#panel">
<resources>
<!-- Fixes an issue with the "test_arrowpanel.xul" animation on Mac, see bug 1470880. -->
<stylesheet src="data:text/css,"/>
</resources>
<content flip="both" side="top" position="bottomcenter topleft" consumeoutsideclicks="false">
<xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
xbl:inherits="side,panelopen">
<xul:box anonid="arrowbox" class="panel-arrowbox">
<xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
</xul:box>
<xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
<children/>
</xul:box>
</xul:vbox>
</content>
<implementation>
<field name="_fadeTimer">null</field>
<method name="adjustArrowPosition">
<body>
<![CDATA[
var anchor = this.anchorNode;
if (!anchor) {
return;
}
var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
var position = this.alignmentPosition;
var offset = this.alignmentOffset;
this.setAttribute("arrowposition", position);
if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
container.orient = "horizontal";
arrowbox.orient = "vertical";
if (position.indexOf("_after") > 0) {
arrowbox.pack = "end";
} else {
arrowbox.pack = "start";
}
arrowbox.style.transform = "translate(0, " + -offset + "px)";
// The assigned side stays the same regardless of direction.
var isRTL = (window.getComputedStyle(this).direction == "rtl");
if (position.indexOf("start_") == 0) {
container.dir = "reverse";
this.setAttribute("side", isRTL ? "left" : "right");
} else {
container.dir = "";
this.setAttribute("side", isRTL ? "right" : "left");
}
} else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
container.orient = "";
arrowbox.orient = "";
if (position.indexOf("_end") > 0) {
arrowbox.pack = "end";
} else {
arrowbox.pack = "start";
}
arrowbox.style.transform = "translate(" + -offset + "px, 0)";
if (position.indexOf("before_") == 0) {
container.dir = "reverse";
this.setAttribute("side", "bottom");
} else {
container.dir = "";
this.setAttribute("side", "top");
}
}
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="popupshowing" phase="target">
<![CDATA[
var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
arrow.hidden = this.anchorNode == null;
document.getAnonymousElementByAttribute(this, "anonid", "arrowbox")
.style.removeProperty("transform");
if (this.getAttribute("animate") != "false") {
this.setAttribute("animate", "open");
// the animating attribute prevents user interaction during transition
// it is removed when popupshown fires
this.setAttribute("animating", "true");
}
// set fading
var fade = this.getAttribute("fade");
var fadeDelay = 0;
if (fade == "fast") {
fadeDelay = 1;
} else if (fade == "slow") {
fadeDelay = 4000;
} else {
return;
}
this._fadeTimer = setTimeout(() => this.hidePopup(true), fadeDelay, this);
]]>
</handler>
<handler event="popuphiding" phase="target">
let animate = (this.getAttribute("animate") != "false");
if (this._fadeTimer) {
clearTimeout(this._fadeTimer);
if (animate) {
this.setAttribute("animate", "fade");
}
} else if (animate) {
this.setAttribute("animate", "cancel");
}
</handler>
<handler event="popupshown" phase="target">
this.removeAttribute("animating");
this.setAttribute("panelopen", "true");
</handler>
<handler event="popuphidden" phase="target">
this.removeAttribute("panelopen");
if (this.getAttribute("animate") != "false") {
this.removeAttribute("animate");
}
</handler>
<handler event="popuppositioned" phase="target">
this.adjustArrowPosition();
</handler>
</handlers>
</binding>
<binding id="popup-scrollbars" extends="chrome://global/content/bindings/popup.xml#popup">
<content>
<xul:scrollbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;">
<children/>
</xul:scrollbox>
</content>
<implementation>
<field name="AUTOSCROLL_INTERVAL">25</field>
<field name="NOT_DRAGGING">0</field>
<field name="DRAG_OVER_BUTTON">-1</field>
<field name="DRAG_OVER_POPUP">1</field>
<field name="_draggingState">this.NOT_DRAGGING</field>
<field name="_scrollTimer">0</field>
<method name="enableDragScrolling">
<!-- when overItem is true, drag started over menuitem; when false, drag
started while the popup was opening.
-->
<parameter name="overItem"/>
<body>
<![CDATA[
if (!this._draggingState) {
this.setCaptureAlways();
this._draggingState = overItem ? this.DRAG_OVER_POPUP : this.DRAG_OVER_BUTTON;
}
]]>
</body>
</method>
<method name="_clearScrollTimer">
<body>
<![CDATA[
if (this._scrollTimer) {
this.ownerGlobal.clearInterval(this._scrollTimer);
this._scrollTimer = 0;
}
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="popupshown">
// Enable drag scrolling even when the mouse wasn't used. The mousemove
// handler will remove it if the mouse isn't down.
this.enableDragScrolling(false);
</handler>
<handler event="popuphidden">
<![CDATA[
this._draggingState = this.NOT_DRAGGING;
this._clearScrollTimer();
this.releaseCapture();
]]>
</handler>
<handler event="mousedown" button="0">
<![CDATA[
if (this.state == "open" &&
(event.target.localName == "menuitem" ||
event.target.localName == "menu" ||
event.target.localName == "menucaption")) {
this.enableDragScrolling(true);
}
]]>
</handler>
<handler event="mouseup" button="0">
<![CDATA[
this._draggingState = this.NOT_DRAGGING;
this._clearScrollTimer();
]]>
</handler>
<handler event="mousemove">
<![CDATA[
if (!this._draggingState) {
return;
}
this._clearScrollTimer();
// If the user released the mouse before the popup opens, we will
// still be capturing, so check that the button is still pressed. If
// not, release the capture and do nothing else. This also handles if
// the dropdown was opened via the keyboard.
if (!(event.buttons & 1)) {
this._draggingState = this.NOT_DRAGGING;
this.releaseCapture();
return;
}
// If dragging outside the top or bottom edge of the popup, but within
// the popup area horizontally, scroll the list in that direction. The
// _draggingState flag is used to ensure that scrolling does not start
// until the mouse has moved over the popup first, preventing scrolling
// while over the dropdown button.
let popupRect = this.getOuterScreenRect();
if (event.screenX >= popupRect.left && event.screenX <= popupRect.right) {
if (this._draggingState == this.DRAG_OVER_BUTTON) {
if (event.screenY > popupRect.top && event.screenY < popupRect.bottom) {
this._draggingState = this.DRAG_OVER_POPUP;
}
}
if (this._draggingState == this.DRAG_OVER_POPUP &&
(event.screenY <= popupRect.top || event.screenY >= popupRect.bottom)) {
let scrollAmount = event.screenY <= popupRect.top ? -1 : 1;
this.scrollBox.scrollByIndex(scrollAmount);
let win = this.ownerGlobal;
this._scrollTimer = win.setInterval(() => {
this.scrollBox.scrollByIndex(scrollAmount);
}, this.AUTOSCROLL_INTERVAL);
}
}
]]>
</handler>
</handlers>
</binding>
</bindings>