// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. define([ 'exports', '../Core/_Global', '../Core/_Base', '../Core/_BaseUtils', '../Core/_WinRT', '../Promise', '../Scheduler' ], function elementUtilities(exports, _Global, _Base, _BaseUtils, _WinRT, Promise, Scheduler) { "use strict"; // not supported in WebWorker if (!_Global.document) { return; } var _zoomToDuration = 167; // Firefox's implementation of getComputedStyle returns null when called within // an iframe that is display:none. This is a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=548397 // _getComputedStyle is a helper which is guaranteed to return an object whose keys // map to strings. var defaultComputedStyle = null; function getDefaultComputedStyle() { if (!defaultComputedStyle) { defaultComputedStyle = {}; Object.keys(_Global.CSS2Properties.prototype).forEach(function (cssProperty) { defaultComputedStyle[cssProperty] = ""; }); } return defaultComputedStyle; } function _getComputedStyle(element, pseudoElement) { // jscs:disable disallowDirectGetComputedStyle return _Global.getComputedStyle(element, pseudoElement) || getDefaultComputedStyle(); // jscs:enable disallowDirectGetComputedStyle } function removeEmpties(arr) { var len = arr.length; for (var i = len - 1; i >= 0; i--) { if (!arr[i]) { arr.splice(i, 1); len--; } } return len; } function getClassName(e) { var name = e.className || ""; if (typeof (name) === "string") { return name; } else { return name.baseVal || ""; } } function setClassName(e, value) { // SVG elements (which use e.className.baseVal) are never undefined, // so this logic makes the comparison a bit more compact. // var name = e.className || ""; if (typeof (name) === "string") { e.className = value; } else { e.className.baseVal = value; } return e; } function addClass(e, name) { /// /// /// Adds the specified class(es) to the specified element. Multiple classes can be added using space delimited names. /// /// /// The element to which to add the class. /// /// /// The name of the class to add, multiple classes can be added using space delimited names /// /// /// The element. /// /// if (e.classList) { // Fastpath: adding a single class, no need to string split the argument if (name.indexOf(" ") < 0) { e.classList.add(name); } else { var namesToAdd = name.split(" "); removeEmpties(namesToAdd); for (var i = 0, len = namesToAdd.length; i < len; i++) { e.classList.add(namesToAdd[i]); } } return e; } else { var className = getClassName(e); var names = className.split(" "); var l = removeEmpties(names); var toAdd; // we have a fast path for the common case of a single name in the class name // if (name.indexOf(" ") >= 0) { var namesToAdd = name.split(" "); removeEmpties(namesToAdd); for (var i = 0; i < l; i++) { var found = namesToAdd.indexOf(names[i]); if (found >= 0) { namesToAdd.splice(found, 1); } } if (namesToAdd.length > 0) { toAdd = namesToAdd.join(" "); } } else { var saw = false; for (var i = 0; i < l; i++) { if (names[i] === name) { saw = true; break; } } if (!saw) { toAdd = name; } } if (toAdd) { if (l > 0 && names[0].length > 0) { setClassName(e, className + " " + toAdd); } else { setClassName(e, toAdd); } } return e; } } function removeClass(e, name) { /// /// /// Removes the specified class from the specified element. /// /// /// The element from which to remove the class. /// /// /// The name of the class to remove. /// /// /// The element. /// /// if (e.classList) { // Fastpath: Nothing to remove if (e.classList.length === 0) { return e; } var namesToRemove = name.split(" "); removeEmpties(namesToRemove); for (var i = 0, len = namesToRemove.length; i < len; i++) { e.classList.remove(namesToRemove[i]); } return e; } else { var original = getClassName(e); var namesToRemove; var namesToRemoveLen; if (name.indexOf(" ") >= 0) { namesToRemove = name.split(" "); namesToRemoveLen = removeEmpties(namesToRemove); } else { // early out for the case where you ask to remove a single // name and that name isn't found. // if (original.indexOf(name) < 0) { return e; } namesToRemove = [name]; namesToRemoveLen = 1; } var removed; var names = original.split(" "); var namesLen = removeEmpties(names); for (var i = namesLen - 1; i >= 0; i--) { if (namesToRemove.indexOf(names[i]) >= 0) { names.splice(i, 1); removed = true; } } if (removed) { setClassName(e, names.join(" ")); } return e; } } function toggleClass(e, name) { /// /// /// Toggles (adds or removes) the specified class on the specified element. /// If the class is present, it is removed; if it is absent, it is added. /// /// /// The element on which to toggle the class. /// /// /// The name of the class to toggle. /// /// /// The element. /// /// if (e.classList) { e.classList.toggle(name); return e; } else { var className = getClassName(e); var names = className.trim().split(" "); var l = names.length; var found = false; for (var i = 0; i < l; i++) { if (names[i] === name) { found = true; } } if (!found) { if (l > 0 && names[0].length > 0) { setClassName(e, className + " " + name); } else { setClassName(e, className + name); } } else { setClassName(e, names.reduce(function (r, e) { if (e === name) { return r; } else if (r && r.length > 0) { return r + " " + e; } else { return e; } }, "")); } return e; } } // Only set the attribute if its value has changed function setAttribute(element, attribute, value) { if (element.getAttribute(attribute) !== "" + value) { element.setAttribute(attribute, value); } } function _clamp(value, lowerBound, upperBound, defaultValue) { var n = Math.max(lowerBound, Math.min(upperBound, +value)); return n === 0 ? 0 : n || Math.max(lowerBound, Math.min(upperBound, defaultValue)); } var _pixelsRE = /^-?\d+\.?\d*(px)?$/i; var _numberRE = /^-?\d+/i; function convertToPixels(element, value) { /// /// /// Converts a CSS positioning string for the specified element to pixels. /// /// /// The element. /// /// /// The CSS positioning string. /// /// /// The number of pixels. /// /// if (!_pixelsRE.test(value) && _numberRE.test(value)) { var previousValue = element.style.left; element.style.left = value; value = element.style.pixelLeft; element.style.left = previousValue; return value; } else { return Math.round(parseFloat(value)) || 0; } } function getDimension(element, property) { return convertToPixels(element, _getComputedStyle(element, null)[property]); } function _convertToPrecisePixels(value) { return parseFloat(value) || 0; } function _getPreciseDimension(element, property) { return _convertToPrecisePixels(_getComputedStyle(element, null)[property]); } function _getPreciseMargins(element) { var style = _getComputedStyle(element); return { top: _convertToPrecisePixels(style.marginTop), right: _convertToPrecisePixels(style.marginRight), bottom: _convertToPrecisePixels(style.marginBottom), left: _convertToPrecisePixels(style.marginLeft), }; } var _MSGestureEvent = _Global.MSGestureEvent || { MSGESTURE_FLAG_BEGIN: 1, MSGESTURE_FLAG_CANCEL: 4, MSGESTURE_FLAG_END: 2, MSGESTURE_FLAG_INERTIA: 8, MSGESTURE_FLAG_NONE: 0 }; var _MSManipulationEvent = _Global.MSManipulationEvent || { MS_MANIPULATION_STATE_ACTIVE: 1, MS_MANIPULATION_STATE_CANCELLED: 6, MS_MANIPULATION_STATE_COMMITTED: 7, MS_MANIPULATION_STATE_DRAGGING: 5, MS_MANIPULATION_STATE_INERTIA: 2, MS_MANIPULATION_STATE_PRESELECT: 3, MS_MANIPULATION_STATE_SELECTING: 4, MS_MANIPULATION_STATE_STOPPED: 0 }; var _MSPointerEvent = _Global.MSPointerEvent || { MSPOINTER_TYPE_TOUCH: "touch", MSPOINTER_TYPE_PEN: "pen", MSPOINTER_TYPE_MOUSE: "mouse", }; // Helpers for managing element._eventsMap for custom events // function addListenerToEventMap(element, type, listener, useCapture, data) { var eventNameLowercase = type.toLowerCase(); if (!element._eventsMap) { element._eventsMap = {}; } if (!element._eventsMap[eventNameLowercase]) { element._eventsMap[eventNameLowercase] = []; } element._eventsMap[eventNameLowercase].push({ listener: listener, useCapture: useCapture, data: data }); } function removeListenerFromEventMap(element, type, listener, useCapture) { var eventNameLowercase = type.toLowerCase(); var mappedEvents = element._eventsMap && element._eventsMap[eventNameLowercase]; if (mappedEvents) { for (var i = mappedEvents.length - 1; i >= 0; i--) { var mapping = mappedEvents[i]; if (mapping.listener === listener && (!!useCapture === !!mapping.useCapture)) { mappedEvents.splice(i, 1); return mapping; } } } return null; } function lookupListeners(element, type) { var eventNameLowercase = type.toLowerCase(); return element._eventsMap && element._eventsMap[eventNameLowercase] && element._eventsMap[eventNameLowercase].slice(0) || []; } // Custom focusin/focusout events // Generally, use these instead of using the browser's blur/focus/focusout/focusin events directly. // However, this doesn't support the window object. If you need to listen to focus events on the window, // use the browser's events directly. // // In order to send our custom focusin/focusout events synchronously on every browser, we feature detect // for native "focusin" and "focusout" since every browser that supports them will fire them synchronously. // Every browser in our support matrix, except for IE, also fires focus/blur synchronously, we fall back to // those events in browsers such as Firefox that do not have native support for focusin/focusout. function bubbleEvent(element, type, eventObject) { while (element) { var handlers = lookupListeners(element, type); for (var i = 0, len = handlers.length; i < len; i++) { handlers[i].listener.call(element, eventObject); } element = element.parentNode; } } function prepareFocusEvent(eventObject) { // If an iframe is involved, then relatedTarget should be null. if (eventObject.relatedTarget && eventObject.relatedTarget.tagName === "IFRAME" || eventObject.target && eventObject.target.tagName === "IFRAME") { eventObject.relatedTarget = null; } return eventObject; } var nativeSupportForFocusIn = "onfocusin" in _Global.document.documentElement; var activeElement = null; _Global.addEventListener(nativeSupportForFocusIn ? "focusout" : "blur", function (eventObject) { // Fires focusout when focus move to another window or into an iframe. if (eventObject.target === _Global) { var previousActiveElement = activeElement; if (previousActiveElement) { bubbleEvent(previousActiveElement, "focusout", prepareFocusEvent({ type: "focusout", target: previousActiveElement, relatedTarget: null })); } activeElement = null; } }); _Global.document.documentElement.addEventListener(nativeSupportForFocusIn ? "focusin" : "focus", function (eventObject) { var previousActiveElement = activeElement; activeElement = eventObject.target; if (previousActiveElement) { bubbleEvent(previousActiveElement, "focusout", prepareFocusEvent({ type: "focusout", target: previousActiveElement, relatedTarget: activeElement })); } if (activeElement) { bubbleEvent(activeElement, "focusin", prepareFocusEvent({ type: "focusin", target: activeElement, relatedTarget: previousActiveElement })); } }, true); function registerBubbleListener(element, type, listener, useCapture) { if (useCapture) { throw "This custom WinJS event only supports bubbling"; } addListenerToEventMap(element, type, listener, useCapture); } // Custom pointer events // // Sets the properties in *overrideProperties* on the object. Delegates all other // property accesses to *eventObject*. // // The purpose of PointerEventProxy is that it allows us to customize properties on // an eventObject despite those properties being unwritable and unconfigurable. var PointerEventProxy = function (eventObject, overrideProperties) { overrideProperties = overrideProperties || {}; this.__eventObject = eventObject; var that = this; Object.keys(overrideProperties).forEach(function (propertyName) { Object.defineProperty(that, propertyName, { value: overrideProperties[propertyName] }); }); }; // Define PointerEventProxy properties which should be delegated to the original eventObject. [ "altKey", "AT_TARGET", "bubbles", "BUBBLING_PHASE", "button", "buttons", "cancelable", "cancelBubble", "CAPTURING_PHASE", "clientX", "clientY", "ctrlKey", "currentTarget", "defaultPrevented", "detail", "eventPhase", "fromElement", "getModifierState", "height", "hwTimestamp", "initEvent", "initMouseEvent", "initPointerEvent", "initUIEvent", "isPrimary", "isTrusted", "layerX", "layerY", "metaKey", "offsetX", "offsetY", "pageX", "pageY", "pointerId", "pointerType", "pressure", "preventDefault", "relatedTarget", "rotation", "screenX", "screenY", "shiftKey", "srcElement", "stopImmediatePropagation", "stopPropagation", "target", "tiltX", "tiltY", "timeStamp", "toElement", "type", "view", "which", "width", "x", "y", "_normalizedType", "_fakedBySemanticZoom" ].forEach(function (propertyName) { Object.defineProperty(PointerEventProxy.prototype, propertyName, { get: function () { var value = this.__eventObject[propertyName]; return typeof value === "function" ? value.bind(this.__eventObject) : value; }, configurable: true }); }); function touchEventTranslator(callback, eventObject) { var changedTouches = eventObject.changedTouches, retVal = null; if (!changedTouches) { return retVal; } for (var i = 0, len = changedTouches.length; i < len; i++) { var touchObject = changedTouches[i]; var pointerEventObject = new PointerEventProxy(eventObject, { pointerType: _MSPointerEvent.MSPOINTER_TYPE_TOUCH, pointerId: touchObject.identifier, isPrimary: i === 0, screenX: touchObject.screenX, screenY: touchObject.screenY, clientX: touchObject.clientX, clientY: touchObject.clientY, pageX: touchObject.pageX, pageY: touchObject.pageY, radiusX: touchObject.radiusX, radiusY: touchObject.radiusY, rotationAngle: touchObject.rotationAngle, force: touchObject.force, _currentTouch: touchObject }); var newRetVal = callback(pointerEventObject); retVal = retVal || newRetVal; } return retVal; } function mouseEventTranslator(callback, eventObject) { eventObject.pointerType = _MSPointerEvent.MSPOINTER_TYPE_MOUSE; eventObject.pointerId = -1; eventObject.isPrimary = true; return callback(eventObject); } function mspointerEventTranslator(callback, eventObject) { return callback(eventObject); } var eventTranslations = { pointerdown: { touch: "touchstart", mspointer: "MSPointerDown", mouse: "mousedown" }, pointerup: { touch: "touchend", mspointer: "MSPointerUp", mouse: "mouseup" }, pointermove: { touch: "touchmove", mspointer: "MSPointerMove", mouse: "mousemove" }, pointerenter: { touch: "touchenter", mspointer: "MSPointerEnter", mouse: "mouseenter" }, pointerover: { touch: null, mspointer: "MSPointerOver", mouse: "mouseover" }, pointerout: { touch: "touchleave", mspointer: "MSPointerOut", mouse: "mouseout" }, pointercancel: { touch: "touchcancel", mspointer: "MSPointerCancel", mouse: null } }; function registerPointerEvent(element, type, callback, capture) { var eventNameLowercase = type.toLowerCase(); var mouseWrapper, touchWrapper, mspointerWrapper; var translations = eventTranslations[eventNameLowercase]; // Browsers fire a touch event and then a mouse event when the input is touch. touchHandled is used to prevent invoking the pointer callback twice. var touchHandled; // If we are in IE10, we should use MSPointer as it provides a better interface than touch events if (_Global.MSPointerEvent) { mspointerWrapper = function (eventObject) { eventObject._normalizedType = eventNameLowercase; touchHandled = true; return mspointerEventTranslator(callback, eventObject); }; element.addEventListener(translations.mspointer, mspointerWrapper, capture); } else { // Otherwise, use a mouse and touch event if (translations.mouse) { mouseWrapper = function (eventObject) { eventObject._normalizedType = eventNameLowercase; if (!touchHandled) { return mouseEventTranslator(callback, eventObject); } touchHandled = false; }; element.addEventListener(translations.mouse, mouseWrapper, capture); } if (translations.touch) { touchWrapper = function (eventObject) { eventObject._normalizedType = eventNameLowercase; touchHandled = true; return touchEventTranslator(callback, eventObject); }; element.addEventListener(translations.touch, touchWrapper, capture); } } addListenerToEventMap(element, type, callback, capture, { mouseWrapper: mouseWrapper, touchWrapper: touchWrapper, mspointerWrapper: mspointerWrapper }); } function unregisterPointerEvent(element, type, callback, capture) { var eventNameLowercase = type.toLowerCase(); var mapping = removeListenerFromEventMap(element, type, callback, capture); if (mapping) { var translations = eventTranslations[eventNameLowercase]; if (mapping.data.mouseWrapper) { element.removeEventListener(translations.mouse, mapping.data.mouseWrapper, capture); } if (mapping.data.touchWrapper) { element.removeEventListener(translations.touch, mapping.data.touchWrapper, capture); } if (mapping.data.mspointerWrapper) { element.removeEventListener(translations.mspointer, mapping.data.mspointerWrapper, capture); } } } // Custom events dispatch table. Event names should be lowercased. // var customEvents = { focusout: { register: registerBubbleListener, unregister: removeListenerFromEventMap }, focusin: { register: registerBubbleListener, unregister: removeListenerFromEventMap }, }; if (!_Global.PointerEvent) { var pointerEventEntry = { register: registerPointerEvent, unregister: unregisterPointerEvent }; customEvents.pointerdown = pointerEventEntry; customEvents.pointerup = pointerEventEntry; customEvents.pointermove = pointerEventEntry; customEvents.pointerenter = pointerEventEntry; customEvents.pointerover = pointerEventEntry; customEvents.pointerout = pointerEventEntry; customEvents.pointercancel = pointerEventEntry; } // The MutationObserverShim only supports the following configuration: // attributes // attributeFilter var MutationObserverShim = _Base.Class.define( function MutationObserverShim_ctor(callback) { this._callback = callback; this._toDispose = []; this._attributeFilter = []; this._scheduled = false; this._pendingChanges = []; this._observerCount = 0; this._handleCallback = this._handleCallback.bind(this); this._targetElements = []; }, { observe: function MutationObserverShim_observe(element, configuration) { if (this._targetElements.indexOf(element) === -1) { this._targetElements.push(element); } this._observerCount++; if (configuration.attributes) { this._addRemovableListener(element, "DOMAttrModified", this._handleCallback); } if (configuration.attributeFilter) { this._attributeFilter = configuration.attributeFilter; } }, disconnect: function MutationObserverShim_disconnect() { this._observerCount = 0; this._targetElements = []; this._toDispose.forEach(function (disposeFunc) { disposeFunc(); }); }, _addRemovableListener: function MutationObserverShim_addRemovableListener(target, event, listener) { target.addEventListener(event, listener); this._toDispose.push(function () { target.removeEventListener(event, listener); }); }, _handleCallback: function MutationObserverShim_handleCallback(evt) { // prevent multiple events from firing when nesting observers evt.stopPropagation(); var attrName = evt.attrName; if (this._attributeFilter.length && this._attributeFilter.indexOf(attrName) === -1) { return; } // subtree:true is not currently supported if (this._targetElements.indexOf(evt.target) === -1) { return; } var isAriaMutation = attrName.indexOf("aria") >= 0; // DOM mutation events use different naming for this attribute if (attrName === 'tabindex') { attrName = 'tabIndex'; } this._pendingChanges.push({ type: 'attributes', target: evt.target, attributeName: attrName }); if (this._observerCount === 1 && !isAriaMutation) { this._dispatchEvent(); } else if (this._scheduled === false) { this._scheduled = true; _BaseUtils._setImmediate(this._dispatchEvent.bind(this)); } }, _dispatchEvent: function MutationObserverShim_dispatchEvent() { try { this._callback(this._pendingChanges); } finally { this._pendingChanges = []; this._scheduled = false; } } }, { _isShim: true } ); var _MutationObserver = _Global.MutationObserver || MutationObserverShim; // Lazily init singleton on first access. var _resizeNotifier = null; // Class to provide a global listener for window.onresize events. // This keeps individual elements from having to listen to window.onresize // and having to dispose themselves to avoid leaks. var ResizeNotifier = _Base.Class.define( function ElementResizer_ctor() { _Global.addEventListener("resize", this._handleResize.bind(this)); }, { subscribe: function ElementResizer_subscribe(element, handler) { element.addEventListener(this._resizeEvent, handler); addClass(element, this._resizeClass); }, unsubscribe: function ElementResizer_unsubscribe(element, handler) { removeClass(element, this._resizeClass); element.removeEventListener(this._resizeEvent, handler); }, _handleResize: function ElementResizer_handleResize() { var resizables = _Global.document.querySelectorAll('.' + this._resizeClass); var length = resizables.length; for (var i = 0; i < length; i++) { var event = _Global.document.createEvent("Event"); event.initEvent(this._resizeEvent, false, true); resizables[i].dispatchEvent(event); } }, _resizeClass: { get: function () { return 'win-element-resize'; } }, _resizeEvent: { get: function () { return 'WinJSElementResize'; } } } ); // - object: The object on which GenericListener will listen for events. // - objectName: A string representing the name of *object*. This will be // incorporated into the names of the events and classNames created by // GenericListener. // - options // - registerThruWinJSCustomEvents: If true, will register for events using // _exports._addEventListener so that you can take advantage of WinJS's custom // events (e.g. focusin, pointer*). Otherwise, registers directly on *object* // using its add/removeEventListener methods. var GenericListener = _Base.Class.define( function GenericListener_ctor(objectName, object, options) { options = options || {}; this.registerThruWinJSCustomEvents = !!options.registerThruWinJSCustomEvents; this.objectName = objectName; this.object = object; this.capture = {}; this.bubble = {}; }, { addEventListener: function GenericListener_addEventListener(element, name, listener, capture) { name = name.toLowerCase(); var handlers = this._getHandlers(capture); var handler = handlers[name]; if (!handler) { handler = this._getListener(name, capture); handler.refCount = 0; handlers[name] = handler; if (this.registerThruWinJSCustomEvents) { exports._addEventListener(this.object, name, handler, capture); } else { this.object.addEventListener(name, handler, capture); } } handler.refCount++; element.addEventListener(this._getEventName(name, capture), listener); addClass(element, this._getClassName(name, capture)); }, removeEventListener: function GenericListener_removeEventListener(element, name, listener, capture) { name = name.toLowerCase(); var handlers = this._getHandlers(capture); var handler = handlers[name]; if (handler) { handler.refCount--; if (handler.refCount === 0) { if (this.registerThruWinJSCustomEvents) { exports._removeEventListener(this.object, name, handler, capture); } else { this.object.removeEventListener(name, handler, capture); } delete handlers[name]; } } removeClass(element, this._getClassName(name, capture)); element.removeEventListener(this._getEventName(name, capture), listener); }, _getHandlers: function GenericListener_getHandlers(capture) { if (capture) { return this.capture; } else { return this.bubble; } }, _getClassName: function GenericListener_getClassName(name, capture) { var captureSuffix = capture ? 'capture' : 'bubble'; return 'win-' + this.objectName.toLowerCase() + '-event-' + name + captureSuffix; }, _getEventName: function GenericListener_getEventName(name, capture) { var captureSuffix = capture ? 'capture' : 'bubble'; return 'WinJS' + this.objectName + 'Event-' + name + captureSuffix; }, _getListener: function GenericListener_getListener(name, capture) { var listener = function GenericListener_generatedListener(ev) { var targets = _Global.document.querySelectorAll('.' + this._getClassName(name, capture)); var length = targets.length; var handled = false; for (var i = 0; i < length; i++) { var event = _Global.document.createEvent("Event"); event.initEvent(this._getEventName(name, capture), false, true); event.detail = { originalEvent: ev }; var doDefault = targets[i].dispatchEvent(event); handled = handled || !doDefault; } return handled; }; return listener.bind(this); } } ); var determinedRTLEnvironment = false, usingWebkitScrollCoordinates = false, usingFirefoxScrollCoordinates = false; function determineRTLEnvironment() { var element = _Global.document.createElement("div"); element.style.direction = "rtl"; element.innerHTML = "" + "
" + "
" + "
"; _Global.document.body.appendChild(element); var elementScroller = element.firstChild; if (elementScroller.scrollLeft > 0) { usingWebkitScrollCoordinates = true; } elementScroller.scrollLeft += 100; if (elementScroller.scrollLeft === 0) { usingFirefoxScrollCoordinates = true; } _Global.document.body.removeChild(element); determinedRTLEnvironment = true; } function getAdjustedScrollPosition(element) { var computedStyle = _getComputedStyle(element), scrollLeft = element.scrollLeft; if (computedStyle.direction === "rtl") { if (!determinedRTLEnvironment) { determineRTLEnvironment(); } if (usingWebkitScrollCoordinates) { scrollLeft = element.scrollWidth - element.clientWidth - scrollLeft; } scrollLeft = Math.abs(scrollLeft); } return { scrollLeft: scrollLeft, scrollTop: element.scrollTop }; } function setAdjustedScrollPosition(element, scrollLeft, scrollTop) { if (scrollLeft !== undefined) { var computedStyle = _getComputedStyle(element); if (computedStyle.direction === "rtl") { if (!determinedRTLEnvironment) { determineRTLEnvironment(); } if (usingFirefoxScrollCoordinates) { scrollLeft = -scrollLeft; } else if (usingWebkitScrollCoordinates) { scrollLeft = element.scrollWidth - element.clientWidth - scrollLeft; } } element.scrollLeft = scrollLeft; } if (scrollTop !== undefined) { element.scrollTop = scrollTop; } } function getScrollPosition(element) { /// /// /// Gets the scrollLeft and scrollTop of the specified element, adjusting the scrollLeft to change from browser specific coordinates to logical coordinates when in RTL. /// /// /// The element. /// /// /// An object with two properties: scrollLeft and scrollTop /// /// return getAdjustedScrollPosition(element); } function setScrollPosition(element, position) { /// /// /// Sets the scrollLeft and scrollTop of the specified element, changing the scrollLeft from logical coordinates to browser-specific coordinates when in RTL. /// /// /// The element. /// /// /// The element. /// /// position = position || {}; setAdjustedScrollPosition(element, position.scrollLeft, position.scrollTop); } // navigator.msManipulationViewsEnabled tells us whether snap points work or not regardless of whether the style properties exist, however, // on Phone WWAs, this check returns false even though snap points are supported. To work around this bug, we check for the presence of // 'MSAppHost' in the user agent string which indicates that we are in a WWA environment; all WWA environments support snap points. var supportsSnapPoints = _Global.navigator.msManipulationViewsEnabled || _Global.navigator.userAgent.indexOf("MSAppHost") >= 0; var supportsTouchDetection = !!(_Global.MSPointerEvent || _Global.TouchEvent); var uniqueElementIDCounter = 0; function uniqueID(e) { if (!(e.uniqueID || e._uniqueID)) { e._uniqueID = "element__" + (++uniqueElementIDCounter); } return e.uniqueID || e._uniqueID; } function ensureId(element) { if (!element.id) { element.id = uniqueID(element); } } function _getCursorPos(eventObject) { var docElement = _Global.document.documentElement; var docScrollPos = getScrollPosition(docElement); return { left: eventObject.clientX + (_Global.document.body.dir === "rtl" ? -docScrollPos.scrollLeft : docScrollPos.scrollLeft), top: eventObject.clientY + docElement.scrollTop }; } function _getElementsByClasses(parent, classes) { var retVal = []; for (var i = 0, len = classes.length; i < len; i++) { var element = parent.querySelector("." + classes[i]); if (element) { retVal.push(element); } } return retVal; } var _selectionPartsSelector = ".win-selectionborder, .win-selectionbackground, .win-selectioncheckmark, .win-selectioncheckmarkbackground"; var _dataKey = "_msDataKey"; _Base.Namespace._moduleDefine(exports, "WinJS.Utilities", { _dataKey: _dataKey, _supportsSnapPoints: { get: function () { return supportsSnapPoints; } }, _supportsTouchDetection: { get: function () { return supportsTouchDetection; } }, _uniqueID: uniqueID, _ensureId: ensureId, _clamp: _clamp, _getCursorPos: _getCursorPos, _getElementsByClasses: _getElementsByClasses, _createGestureRecognizer: function () { if (_Global.MSGesture) { return new _Global.MSGesture(); } var doNothing = function () { }; return { addEventListener: doNothing, removeEventListener: doNothing, addPointer: doNothing, stop: doNothing }; }, _MSGestureEvent: _MSGestureEvent, _MSManipulationEvent: _MSManipulationEvent, _elementsFromPoint: function (x, y) { if (_Global.document.msElementsFromPoint) { return _Global.document.msElementsFromPoint(x, y); } else { var element = _Global.document.elementFromPoint(x, y); return element ? [element] : null; } }, _matchesSelector: function _matchesSelector(element, selectorString) { var matchesSelector = element.matches || element.msMatchesSelector || element.mozMatchesSelector || element.webkitMatchesSelector; return matchesSelector.call(element, selectorString); }, _selectionPartsSelector: _selectionPartsSelector, _isSelectionRendered: function _isSelectionRendered(itemBox) { // The tree is changed at pointerDown but _selectedClass is added only when the user drags an item below the selection threshold so checking for _selectedClass is not reliable. return itemBox.querySelectorAll(_selectionPartsSelector).length > 0; }, _addEventListener: function _addEventListener(element, type, listener, useCapture) { var eventNameLower = type && type.toLowerCase(); var entry = customEvents[eventNameLower]; var equivalentEvent = _BaseUtils._browserEventEquivalents[type]; if (entry) { entry.register(element, type, listener, useCapture); } else if (equivalentEvent) { element.addEventListener(equivalentEvent, listener, useCapture); } else { element.addEventListener(type, listener, useCapture); } }, _removeEventListener: function _removeEventListener(element, type, listener, useCapture) { var eventNameLower = type && type.toLowerCase(); var entry = customEvents[eventNameLower]; var equivalentEvent = _BaseUtils._browserEventEquivalents[type]; if (entry) { entry.unregister(element, type, listener, useCapture); } else if (equivalentEvent) { element.removeEventListener(equivalentEvent, listener, useCapture); } else { element.removeEventListener(type, listener, useCapture); } }, _initEventImpl: function (initType, event, eventType) { eventType = eventType.toLowerCase(); var mapping = eventTranslations[eventType]; if (mapping) { switch (initType.toLowerCase()) { case "pointer": if (!_Global.PointerEvent) { arguments[2] = mapping.mspointer; } break; default: arguments[2] = mapping[initType.toLowerCase()]; break; } } event["init" + initType + "Event"].apply(event, Array.prototype.slice.call(arguments, 2)); }, _initMouseEvent: function (event) { this._initEventImpl.apply(this, ["Mouse", event].concat(Array.prototype.slice.call(arguments, 1))); }, _initPointerEvent: function (event) { this._initEventImpl.apply(this, ["Pointer", event].concat(Array.prototype.slice.call(arguments, 1))); }, _PointerEventProxy: PointerEventProxy, _bubbleEvent: bubbleEvent, _setPointerCapture: function (element, pointerId) { if (element.setPointerCapture) { element.setPointerCapture(pointerId); } }, _releasePointerCapture: function (element, pointerId) { if (element.releasePointerCapture) { element.releasePointerCapture(pointerId); } }, _MSPointerEvent: _MSPointerEvent, _getComputedStyle: _getComputedStyle, _zoomToDuration: _zoomToDuration, _zoomTo: function _zoomTo(element, args) { if (this._supportsSnapPoints && element.msZoomTo) { element.msZoomTo(args); } else { // Schedule to ensure that we're not running from within an event handler. For example, if running // within a focus handler triggered by WinJS.Utilities._setActive, scroll position will not yet be // restored. Scheduler.schedule(function () { var initialPos = getAdjustedScrollPosition(element); var effectiveScrollLeft = (typeof element._zoomToDestX === "number" ? element._zoomToDestX : initialPos.scrollLeft); var effectiveScrollTop = (typeof element._zoomToDestY === "number" ? element._zoomToDestY : initialPos.scrollTop); var cs = _getComputedStyle(element); var scrollLimitX = element.scrollWidth - parseInt(cs.width, 10) - parseInt(cs.paddingLeft, 10) - parseInt(cs.paddingRight, 10); var scrollLimitY = element.scrollHeight - parseInt(cs.height, 10) - parseInt(cs.paddingTop, 10) - parseInt(cs.paddingBottom, 10); if (typeof args.contentX !== "number") { args.contentX = effectiveScrollLeft; } if (typeof args.contentY !== "number") { args.contentY = effectiveScrollTop; } var zoomToDestX = _clamp(args.contentX, 0, scrollLimitX); var zoomToDestY = _clamp(args.contentY, 0, scrollLimitY); if (zoomToDestX === effectiveScrollLeft && zoomToDestY === effectiveScrollTop) { // Scroll position is already in the proper state. This zoomTo is a no-op. return; } element._zoomToId = element._zoomToId || 0; element._zoomToId++; element._zoomToDestX = zoomToDestX; element._zoomToDestY = zoomToDestY; var thisZoomToId = element._zoomToId; var start = _BaseUtils._now(); var xFactor = (element._zoomToDestX - initialPos.scrollLeft) / _zoomToDuration; var yFactor = (element._zoomToDestY - initialPos.scrollTop) / _zoomToDuration; var update = function () { var t = _BaseUtils._now() - start; if (element._zoomToId !== thisZoomToId) { return; } else if (t > _zoomToDuration) { setAdjustedScrollPosition(element, element._zoomToDestX, element._zoomToDestY); element._zoomToDestX = null; element._zoomToDestY = null; } else { setAdjustedScrollPosition(element, initialPos.scrollLeft + t * xFactor, initialPos.scrollTop + t * yFactor); _BaseUtils._requestAnimationFrame(update); } }; _BaseUtils._requestAnimationFrame(update); }, Scheduler.Priority.high, null, "WinJS.Utilities._zoomTo"); } }, _setActive: function _setActive(element, scroller) { var success = true; try { if (_Global.HTMLElement && _Global.HTMLElement.prototype.setActive) { element.setActive(); } else { // We are aware that, unlike setActive(), focus() will scroll to the element that gets focus. However, this is // our current cross-browser solution until there is an equivalent for setActive() in other browsers. // // This _setActive polyfill does have limited support for preventing scrolling: via the scroller parameter, it // can prevent one scroller from scrolling. This functionality is necessary in some scenarios. For example, when using // _zoomTo and _setActive together. var scrollLeft, scrollTop; if (scroller) { scrollLeft = scroller.scrollLeft; scrollTop = scroller.scrollTop; } element.focus(); if (scroller) { scroller.scrollLeft = scrollLeft; scroller.scrollTop = scrollTop; } } } catch (e) { // setActive() raises an exception when trying to focus an invisible item. Checking visibility is non-trivial, so it's best // just to catch the exception and ignore it. focus() on the other hand, does not raise exceptions. success = false; } return success; }, _MutationObserver: _MutationObserver, _resizeNotifier: { get: function () { if (!_resizeNotifier) { _resizeNotifier = new ResizeNotifier(); } return _resizeNotifier; } }, _GenericListener: GenericListener, _globalListener: new GenericListener("Global", _Global, { registerThruWinJSCustomEvents: true }), _documentElementListener: new GenericListener("DocumentElement", _Global.document.documentElement, { registerThruWinJSCustomEvents: true }), _inputPaneListener: _WinRT.Windows.UI.ViewManagement.InputPane ? new GenericListener("InputPane", _WinRT.Windows.UI.ViewManagement.InputPane.getForCurrentView()) : { addEventListener: function () { }, removeEventListener: function () { } }, // Appends a hidden child to the given element that will listen for being added // to the DOM. When the hidden element is added to the DOM, it will dispatch a // "WinJSNodeInserted" event on the provided element. _addInsertedNotifier: function (element) { var hiddenElement = _Global.document.createElement("div"); hiddenElement.style[_BaseUtils._browserStyleEquivalents["animation-name"].scriptName] = "WinJS-node-inserted"; hiddenElement.style[_BaseUtils._browserStyleEquivalents["animation-duration"].scriptName] = "0.01s"; hiddenElement.style["position"] = "absolute"; element.appendChild(hiddenElement); exports._addEventListener(hiddenElement, "animationStart", function (e) { if (e.animationName === "WinJS-node-inserted") { var e = _Global.document.createEvent("Event"); e.initEvent("WinJSNodeInserted", false, true); element.dispatchEvent(e); } }, false); return hiddenElement; }, // Returns a promise which completes when *element* is in the DOM. _inDom: function Utilities_inDom(element) { return new Promise(function (c) { if (_Global.document.body.contains(element)) { c(); } else { var nodeInsertedHandler = function () { element.removeEventListener("WinJSNodeInserted", nodeInsertedHandler, false); c(); }; exports._addInsertedNotifier(element); element.addEventListener("WinJSNodeInserted", nodeInsertedHandler, false); } }); }, // Browser agnostic method to set element flex style // Param is an object in the form {grow: flex-grow, shrink: flex-shrink, basis: flex-basis} // All fields optional _setFlexStyle: function (element, flexParams) { var styleObject = element.style; if (typeof flexParams.grow !== "undefined") { styleObject.msFlexPositive = flexParams.grow; styleObject.webkitFlexGrow = flexParams.grow; styleObject.flexGrow = flexParams.grow; } if (typeof flexParams.shrink !== "undefined") { styleObject.msFlexNegative = flexParams.shrink; styleObject.webkitFlexShrink = flexParams.shrink; styleObject.flexShrink = flexParams.shrink; } if (typeof flexParams.basis !== "undefined") { styleObject.msFlexPreferredSize = flexParams.basis; styleObject.webkitFlexBasis = flexParams.basis; styleObject.flexBasis = flexParams.basis; } }, /// /// Defines a set of keyboard values. /// Key: { /// /// BACKSPACE key. /// backspace: 8, /// /// TAB key. /// tab: 9, /// /// ENTER key. /// enter: 13, /// /// Shift key. /// shift: 16, /// /// CTRL key. /// ctrl: 17, /// /// ALT key /// alt: 18, /// /// Pause key. /// pause: 19, /// /// CAPS LOCK key. /// capsLock: 20, /// /// ESCAPE key. /// escape: 27, /// /// SPACE key. /// space: 32, /// /// PAGE UP key. /// pageUp: 33, /// /// PAGE DOWN key. /// pageDown: 34, /// /// END key. /// end: 35, /// /// HOME key. /// home: 36, /// /// Left arrow key. /// leftArrow: 37, /// /// Up arrow key. /// upArrow: 38, /// /// Right arrow key. /// rightArrow: 39, /// /// Down arrow key. /// downArrow: 40, /// /// INSERT key. /// insert: 45, /// /// DELETE key. /// deleteKey: 46, /// /// Number 0 key. /// num0: 48, /// /// Number 1 key. /// num1: 49, /// /// Number 2 key. /// num2: 50, /// /// Number 3 key. /// num3: 51, /// /// Number 4 key. /// num4: 52, /// /// Number 5 key. /// num5: 53, /// /// Number 6 key. /// num6: 54, /// /// Number 7 key. /// num7: 55, /// /// Number 8 key. /// num8: 56, /// /// Number 9 key. /// num9: 57, /// /// A key. /// a: 65, /// /// B key. /// b: 66, /// /// C key. /// c: 67, /// /// D key. /// d: 68, /// /// E key. /// e: 69, /// /// F key. /// f: 70, /// /// G key. /// g: 71, /// /// H key. /// h: 72, /// /// I key. /// i: 73, /// /// J key. /// j: 74, /// /// K key. /// k: 75, /// /// L key. /// l: 76, /// /// M key. /// m: 77, /// /// N key. /// n: 78, /// /// O key. /// o: 79, /// /// P key. /// p: 80, /// /// Q key. /// q: 81, /// /// R key. /// r: 82, /// /// S key. /// s: 83, /// /// T key. /// t: 84, /// /// U key. /// u: 85, /// /// V key. /// v: 86, /// /// W key. /// w: 87, /// /// X key. /// x: 88, /// /// Y key. /// y: 89, /// /// Z key. /// z: 90, /// /// Left Windows key. /// leftWindows: 91, /// /// Right Windows key. /// rightWindows: 92, /// /// Menu key. /// menu: 93, /// /// Number pad 0 key. /// numPad0: 96, /// /// Number pad 1 key. /// numPad1: 97, /// /// Number pad 2 key. /// numPad2: 98, /// /// Number pad 3 key. /// numPad3: 99, /// /// Number pad 4 key. /// numPad4: 100, /// /// Number pad 5 key. /// numPad5: 101, /// /// Number pad 6 key. /// numPad6: 102, /// /// Number pad 7 key. /// numPad7: 103, /// /// Number pad 8 key. /// numPad8: 104, /// /// Number pad 9 key. /// numPad9: 105, /// /// Multiplication key. /// multiply: 106, /// /// Addition key. /// add: 107, /// /// Subtraction key. /// subtract: 109, /// /// Decimal point key. /// decimalPoint: 110, /// /// Division key. /// divide: 111, /// /// F1 key. /// F1: 112, /// /// F2 key. /// F2: 113, /// /// F3 key. /// F3: 114, /// /// F4 key. /// F4: 115, /// /// F5 key. /// F5: 116, /// /// F6 key. /// F6: 117, /// /// F7 key. /// F7: 118, /// /// F8 key. /// F8: 119, /// /// F9 key. /// F9: 120, /// /// F10 key. /// F10: 121, /// /// F11 key. /// F11: 122, /// /// F12 key. /// F12: 123, /// /// XBox One Remote NavigationView key. /// NavigationView: 136, /// /// XBox One Remote NavigationMenu key. /// NavigationMenu: 137, /// /// XBox One Remote NavigationUp key. /// NavigationUp: 138, /// /// XBox One Remote NavigationDown key. /// NavigationDown: 139, /// /// XBox One Remote NavigationLeft key. /// NavigationLeft: 140, /// /// XBox One Remote NavigationRight key. /// NavigationRight: 141, /// /// XBox One Remote NavigationAccept key. /// NavigationAccept: 142, /// /// XBox One Remote NavigationCancel key. /// NavigationCancel: 143, /// /// NUMBER LOCK key. /// numLock: 144, /// /// SCROLL LOCK key. /// scrollLock: 145, /// /// Browser back key. /// browserBack: 166, /// /// Browser forward key. /// browserForward: 167, /// /// SEMICOLON key. /// semicolon: 186, /// /// EQUAL key. /// equal: 187, /// /// COMMA key. /// comma: 188, /// /// DASH key. /// dash: 189, /// /// PERIOD key. /// period: 190, /// /// FORWARD SLASH key. /// forwardSlash: 191, /// /// Accent grave key. /// graveAccent: 192, /// /// XBox One GamepadA key. /// GamepadA: 195, /// /// XBox One GamepadB key. /// GamepadB: 196, /// /// XBox One GamepadX key. /// GamepadX: 197, /// /// XBox One GamepadY key. /// GamepadY: 198, /// /// XBox One GamepadRightShoulder key. /// GamepadRightShoulder: 199, /// /// XBox One GamepadLeftShoulder key. /// GamepadLeftShoulder: 200, /// /// XBox One GamepadLeftTrigger key. /// GamepadLeftTrigger: 201, /// /// XBox One GamepadRightTrigger key. /// GamepadRightTrigger: 202, /// /// XBox One GamepadDPadUp key. /// GamepadDPadUp: 203, /// /// XBox One GamepadDPadDown key. /// GamepadDPadDown: 204, /// /// XBox One GamepadDPadLeft key. /// GamepadDPadLeft: 205, /// /// XBox One GamepadDPadRight key. /// GamepadDPadRight: 206, /// /// XBox One GamepadMenu key. /// GamepadMenu: 207, /// /// XBox One GamepadView key. /// GamepadView: 208, /// /// XBox One GamepadLeftThumbstick key. /// GamepadLeftThumbstick: 209, /// /// XBox One GamepadRightThumbstick key. /// GamepadRightThumbstick: 210, /// /// XBox One GamepadLeftThumbstickUp key. /// GamepadLeftThumbstickUp: 211, /// /// XBox One GamepadLeftThumbstickDown key. /// GamepadLeftThumbstickDown: 212, /// /// XBox One GamepadLeftThumbstickRight key. /// GamepadLeftThumbstickRight: 213, /// /// XBox One GamepadLeftThumbstickLeft key. /// GamepadLeftThumbstickLeft: 214, /// /// XBox One GamepadRightThumbstickUp key. /// GamepadRightThumbstickUp: 215, /// /// XBox One GamepadRightThumbstickDown key. /// GamepadRightThumbstickDown: 216, /// /// XBox One GamepadRightThumbstickRight key. /// GamepadRightThumbstickRight: 217, /// /// XBox One GamepadRightThumbstickLeft key. /// GamepadRightThumbstickLeft: 218, /// /// OPEN BRACKET key. /// openBracket: 219, /// /// BACKSLASH key. /// backSlash: 220, /// /// CLOSE BRACKET key. /// closeBracket: 221, /// /// SINGLE QUOTE key. /// singleQuote: 222, /// /// Any IME input. /// IME: 229 }, data: function (element) { /// /// /// Gets the data value associated with the specified element. /// /// /// The element. /// /// /// The value associated with the element. /// /// if (!element[_dataKey]) { element[_dataKey] = {}; } return element[_dataKey]; }, hasClass: function (e, name) { /// /// /// Determines whether the specified element has the specified class. /// /// /// The element. /// /// /// The name of the class. /// /// /// true if the specified element contains the specified class; otherwise, false. /// /// if (e.classList) { return e.classList.contains(name); } else { var className = getClassName(e); var names = className.trim().split(" "); var l = names.length; for (var i = 0; i < l; i++) { if (names[i] === name) { return true; } } return false; } }, addClass: addClass, removeClass: removeClass, toggleClass: toggleClass, _setAttribute: setAttribute, getRelativeLeft: function (element, parent) { /// /// /// Gets the left coordinate of the specified element relative to the specified parent. /// /// /// The element. /// /// /// The parent element. /// /// /// The relative left coordinate. /// /// if (!element) { return 0; } var elementPosition = exports._getPositionRelativeTo(element, null); var parentPosition = exports._getPositionRelativeTo(parent, null); return elementPosition.left - parentPosition.left; }, getRelativeTop: function (element, parent) { /// /// /// Gets the top coordinate of the element relative to the specified parent. /// /// /// The element. /// /// /// The parent element. /// /// /// The relative top coordinate. /// /// if (!element) { return 0; } var elementPosition = exports._getPositionRelativeTo(element, null); var parentPosition = exports._getPositionRelativeTo(parent, null); return elementPosition.top - parentPosition.top; }, getScrollPosition: getScrollPosition, setScrollPosition: setScrollPosition, empty: function (element) { /// /// /// Removes all the child nodes from the specified element. /// /// /// The element. /// /// /// The element. /// /// if (element.childNodes && element.childNodes.length > 0) { for (var i = element.childNodes.length - 1; i >= 0; i--) { element.removeChild(element.childNodes.item(i)); } } return element; }, _isDOMElement: function (element) { return element && typeof element === "object" && typeof element.tagName === "string"; }, getContentWidth: function (element) { /// /// /// Gets the width of the content of the specified element. The content width does not include borders or padding. /// /// /// The element. /// /// /// The content width of the element. /// /// var border = getDimension(element, "borderLeftWidth") + getDimension(element, "borderRightWidth"), padding = getDimension(element, "paddingLeft") + getDimension(element, "paddingRight"); return element.offsetWidth - border - padding; }, _getPreciseContentWidth: function (element) { var border = _getPreciseDimension(element, "borderLeftWidth") + _getPreciseDimension(element, "borderRightWidth"), padding = _getPreciseDimension(element, "paddingLeft") + _getPreciseDimension(element, "paddingRight"); return element.offsetWidth - border - padding; }, getTotalWidth: function (element) { /// /// /// Gets the width of the element, including margins. /// /// /// The element. /// /// /// The width of the element including margins. /// /// var margin = getDimension(element, "marginLeft") + getDimension(element, "marginRight"); return element.offsetWidth + margin; }, _getPreciseTotalWidth: function (element) { var margin = _getPreciseDimension(element, "marginLeft") + _getPreciseDimension(element, "marginRight"); return element.offsetWidth + margin; }, getContentHeight: function (element) { /// /// /// Gets the height of the content of the specified element. The content height does not include borders or padding. /// /// /// The element. /// /// /// The content height of the element. /// /// var border = getDimension(element, "borderTopWidth") + getDimension(element, "borderBottomWidth"), padding = getDimension(element, "paddingTop") + getDimension(element, "paddingBottom"); return element.offsetHeight - border - padding; }, _getPreciseContentHeight: function (element) { var border = _getPreciseDimension(element, "borderTopWidth") + _getPreciseDimension(element, "borderBottomWidth"), padding = _getPreciseDimension(element, "paddingTop") + _getPreciseDimension(element, "paddingBottom"); return element.offsetHeight - border - padding; }, getTotalHeight: function (element) { /// /// /// Gets the height of the element, including its margins. /// /// /// The element. /// /// /// The height of the element including margins. /// /// var margin = getDimension(element, "marginTop") + getDimension(element, "marginBottom"); return element.offsetHeight + margin; }, _getPreciseTotalHeight: function (element) { var margin = _getPreciseDimension(element, "marginTop") + _getPreciseDimension(element, "marginBottom"); return element.offsetHeight + margin; }, getPosition: function (element) { /// /// /// Gets the position of the specified element. /// /// /// The element. /// /// /// An object that contains the left, top, width and height properties of the element. /// /// return exports._getPositionRelativeTo(element, null); }, getTabIndex: function (element) { /// /// /// Gets the tabIndex of the specified element. /// /// /// The element. /// /// /// The tabIndex of the element. Returns -1 if the element cannot be tabbed to /// /// // For reference: http://www.w3.org/html/wg/drafts/html/master/single-page.html#specially-focusable var tabbableElementsRE = /BUTTON|COMMAND|MENUITEM|OBJECT|SELECT|TEXTAREA/; if (element.disabled) { return -1; } var tabIndex = element.getAttribute("tabindex"); if (tabIndex === null || tabIndex === undefined) { var name = element.tagName; if (tabbableElementsRE.test(name) || (element.href && (name === "A" || name === "AREA" || name === "LINK")) || (name === "INPUT" && element.type !== "hidden") || (name === "TH" && element.sorted)) { return 0; } return -1; } return parseInt(tabIndex, 10); }, convertToPixels: convertToPixels, _convertToPrecisePixels: _convertToPrecisePixels, _getPreciseMargins: _getPreciseMargins, eventWithinElement: function (element, event) { /// /// /// Determines whether the specified event occurred within the specified element. /// /// /// The element. /// /// /// The event. /// /// /// true if the event occurred within the element; otherwise, false. /// /// var related = event.relatedTarget; if (related && related !== element) { return element.contains(related); } return false; }, //UI Utilities _deprecated: function (message) { _Global.console && _Global.console.warn(message); }, // Take a renderer which may be a function (signature: (data) => element) or a WinJS.Binding.Template // and return a function with a unified synchronous contract which is: // // (data, container) => element // // Where: // // 1) if you pass container the content will be rendered into the container and the // container will be returned. // // 2) if you don't pass a container the content will be rendered and returned. // _syncRenderer: function (renderer, tagName) { tagName = tagName || "div"; if (typeof renderer === "function") { return function (data, container) { if (container) { container.appendChild(renderer(data)); return container; } else { return renderer(data); } }; } var template; if (typeof renderer.render === "function") { template = renderer; } else if (renderer.winControl && typeof renderer.winControl.render === "function") { template = renderer.winControl; } return function (data, container) { var host = container || _Global.document.createElement(tagName); template.render(data, host); if (container) { return container; } else { // The expectation is that the creation of the DOM elements happens synchronously // and as such we steal the first child and make it the root element. // var element = host.firstElementChild; // Because we have changed the "root" we may need to move the dispose method // created by the template to the child and do a little switcheroo on dispose. // if (element && host.dispose) { var prev = element.dispose; element.dispose = function () { element.dispose = prev; host.appendChild(element); host.dispose(); }; } return element; } }; }, _getPositionRelativeTo: function Utilities_getPositionRelativeTo(element, ancestor) { var fromElement = element, offsetParent = element.offsetParent, top = element.offsetTop, left = element.offsetLeft; while ((element = element.parentNode) && element !== ancestor && element !== _Global.document.body && element !== _Global.document.documentElement) { top -= element.scrollTop; var dir = _getComputedStyle(element, null).direction; left -= dir !== "rtl" ? element.scrollLeft : -getAdjustedScrollPosition(element).scrollLeft; if (element === offsetParent) { top += element.offsetTop; left += element.offsetLeft; offsetParent = element.offsetParent; } } return { left: left, top: top, width: fromElement.offsetWidth, height: fromElement.offsetHeight }; }, // *element* is not included in the tabIndex search _getHighAndLowTabIndices: function Utilities_getHighAndLowTabIndices(element) { var descendants = element.getElementsByTagName("*"); var lowestTabIndex = 0; var highestTabIndex = 0; // tabIndex=0 is the highest (considered higher than positive tab indices) so // we can stop searching for a higher tab index once we find tabIndex=0. var foundTabIndex0 = false; for (var i = 0, len = descendants.length; i < len; i++) { var tabIndexStr = descendants[i].getAttribute("tabIndex"); if (tabIndexStr !== null && tabIndexStr !== undefined) { var tabIndex = parseInt(tabIndexStr, 10); // Update lowest if (tabIndex > 0 && (tabIndex < lowestTabIndex || lowestTabIndex === 0)) { lowestTabIndex = tabIndex; } // Update highest if (!foundTabIndex0) { if (tabIndex === 0) { foundTabIndex0 = true; highestTabIndex = 0; } else if (tabIndex > highestTabIndex) { highestTabIndex = tabIndex; } } } } return { highest: highestTabIndex, lowest: lowestTabIndex }; }, _getLowestTabIndexInList: function Utilities_getLowestTabIndexInList(elements) { // Returns the lowest positive tabIndex in a list of elements. // Returns 0 if there are no positive tabIndices. var lowestTabIndex = 0; var elmTabIndex; for (var i = 0; i < elements.length; i++) { elmTabIndex = parseInt(elements[i].getAttribute("tabIndex"), 10); if ((0 < elmTabIndex) && ((elmTabIndex < lowestTabIndex) || !lowestTabIndex)) { lowestTabIndex = elmTabIndex; } } return lowestTabIndex; }, _getHighestTabIndexInList: function Utilities_getHighestTabIndexInList(elements) { // Returns 0 if any element is explicitly set to 0. (0 is the highest tabIndex) // Returns the highest tabIndex in the list of elements. // Returns 0 if there are no positive tabIndices. var highestTabIndex = 0; var elmTabIndex; for (var i = 0; i < elements.length; i++) { elmTabIndex = parseInt(elements[i].getAttribute("tabIndex"), 10); if (elmTabIndex === 0) { return elmTabIndex; } else if (highestTabIndex < elmTabIndex) { highestTabIndex = elmTabIndex; } } return highestTabIndex; }, _hasCursorKeysBehaviors: function Utilities_hasCursorKeysBehaviors(element) { if (element.tagName === "SELECT" || element.tagName === "TEXTAREA") { return true; } if (element.tagName === "INPUT") { return element.type === "" || element.type === "date" || element.type === "datetime" || element.type === "datetime-local" || element.type === "email" || element.type === "month" || element.type === "number" || element.type === "password" || element.type === "range" || element.type === "search" || element.type === "tel" || element.type === "text" || element.type === "time" || element.type === "url" || element.type === "week"; } return false; }, _reparentChildren: function (originalParent, destinationParent) { var child = originalParent.firstChild; while (child) { var sibling = child.nextSibling; destinationParent.appendChild(child); child = sibling; } }, // Ensures that the same element has focus before and after *callback* is // called. Useful if moving focus is an unintentional side effect of *callback*. // For example, this could happen if *callback* removes and reinserts elements // to the DOM. _maintainFocus: function ElementUtilities_maintainFocus(callback) { var focusedElement = _Global.document.activeElement; callback(); exports._trySetActiveOnAnyElement(focusedElement); }, // Tries to give focus to an element (even if its tabIndex is -1) via setActive. _trySetActiveOnAnyElement: function Utilities_trySetActiveOnAnyElement(element, scroller) { return exports._tryFocusOnAnyElement(element, true, scroller); }, // Tries to give focus to an element (even if its tabIndex is -1). _tryFocusOnAnyElement: function Utilities_tryFocusOnAnyElement(element, useSetActive, scroller) { var previousActiveElement = _Global.document.activeElement; if (element === previousActiveElement) { return true; } if (useSetActive) { exports._setActive(element, scroller); } else { element.focus(); } return previousActiveElement !== _Global.document.activeElement; }, // Tries to give focus to an element which is a tabstop (i.e. tabIndex >= 0) // via setActive. _trySetActive: function Utilities_trySetActive(elem, scroller) { return this._tryFocus(elem, true, scroller); }, // Tries to give focus to an element which is a tabstop (i.e. tabIndex >= 0). _tryFocus: function Utilities_tryFocus(elem, useSetActive, scroller) { var previousActiveElement = _Global.document.activeElement; if (elem === previousActiveElement) { return true; } var simpleLogicForValidTabStop = (exports.getTabIndex(elem) >= 0); if (!simpleLogicForValidTabStop) { return false; } if (useSetActive) { exports._setActive(elem, scroller); } else { elem.focus(); } if (previousActiveElement !== _Global.document.activeElement) { return true; } return false; }, _setActiveFirstFocusableElement: function Utilities_setActiveFirstFocusableElement(rootEl, scroller) { return this._focusFirstFocusableElement(rootEl, true, scroller); }, _focusFirstFocusableElement: function Utilities_focusFirstFocusableElement(rootEl, useSetActive, scroller) { var _elms = rootEl.getElementsByTagName("*"); // Get the tabIndex set to the firstDiv (which is the lowest) var _lowestTabIndex = this._getLowestTabIndexInList(_elms); var _nextLowestTabIndex = 0; // If there are positive tabIndices, set focus to the element with the lowest tabIndex. // Keep trying with the next lowest tabIndex until all tabIndices have been exhausted. // Otherwise set focus to the first focusable element in DOM order. var i; while (_lowestTabIndex) { for (i = 0; i < _elms.length; i++) { if (_elms[i].tabIndex === _lowestTabIndex) { if (this._tryFocus(_elms[i], useSetActive, scroller)) { return true; } } else if ((_lowestTabIndex < _elms[i].tabIndex) && ((_elms[i].tabIndex < _nextLowestTabIndex) || (_nextLowestTabIndex === 0))) { // Here if _lowestTabIndex < _elms[i].tabIndex < _nextLowestTabIndex _nextLowestTabIndex = _elms[i].tabIndex; } } // We weren't able to set focus to anything at that tabIndex // If we found a higher valid tabIndex, try that now _lowestTabIndex = _nextLowestTabIndex; _nextLowestTabIndex = 0; } // Wasn't able to set focus to anything with a positive tabIndex, try everything now. // This is where things with tabIndex of 0 will be tried. for (i = 0; i < _elms.length; i++) { if (this._tryFocus(_elms[i], useSetActive, scroller)) { return true; } } return false; }, _setActiveLastFocusableElement: function Utilities_setActiveLastFocusableElement(rootEl, scroller) { return this._focusLastFocusableElement(rootEl, true, scroller); }, _focusLastFocusableElement: function Utilities_focusLastFocusableElement(rootEl, useSetActive, scroller) { var _elms = rootEl.getElementsByTagName("*"); // Get the tabIndex set to the finalDiv (which is the highest) var _highestTabIndex = this._getHighestTabIndexInList(_elms); var _nextHighestTabIndex = 0; // Try all tabIndex 0 first. After this conditional the _highestTabIndex // should be equal to the highest positive tabIndex. var i; if (_highestTabIndex === 0) { for (i = _elms.length - 1; i >= 0; i--) { if (_elms[i].tabIndex === _highestTabIndex) { if (this._tryFocus(_elms[i], useSetActive, scroller)) { return true; } } else if (_nextHighestTabIndex < _elms[i].tabIndex) { _nextHighestTabIndex = _elms[i].tabIndex; } } _highestTabIndex = _nextHighestTabIndex; _nextHighestTabIndex = 0; } // If there are positive tabIndices, set focus to the element with the highest tabIndex. // Keep trying with the next highest tabIndex until all tabIndices have been exhausted. // Otherwise set focus to the last focusable element in DOM order. while (_highestTabIndex) { for (i = _elms.length - 1; i >= 0; i--) { if (_elms[i].tabIndex === _highestTabIndex) { if (this._tryFocus(_elms[i], useSetActive, scroller)) { return true; } } else if ((_nextHighestTabIndex < _elms[i].tabIndex) && (_elms[i].tabIndex < _highestTabIndex)) { // Here if _nextHighestTabIndex < _elms[i].tabIndex < _highestTabIndex _nextHighestTabIndex = _elms[i].tabIndex; } } // We weren't able to set focus to anything at that tabIndex // If we found a lower valid tabIndex, try that now _highestTabIndex = _nextHighestTabIndex; _nextHighestTabIndex = 0; } // Wasn't able to set focus to anything with a tabIndex, try everything now for (i = _elms.length - 2; i > 0; i--) { if (this._tryFocus(_elms[i], useSetActive, scroller)) { return true; } } return false; } }); });