|  | // Copyright 2012 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | // NOTE: ui.js and the autogenerated ui.m.js module version are deprecated. | 
|  | // These files and files that depend on them should only be used by legacy UIs | 
|  | // that have not yet been updated to new patterns. Use Web Components in any new | 
|  | // code. | 
|  |  | 
|  | /** | 
|  | * Decorates elements as an instance of a class. | 
|  | * @param {string|!Element} source The way to find the element(s) to decorate. | 
|  | *     If this is a string then {@code querySeletorAll} is used to find the | 
|  | *     elements to decorate. | 
|  | * @param {!Function} constr The constructor to decorate with. The constr | 
|  | *     needs to have a {@code decorate} function. | 
|  | * @closurePrimitive {asserts.matchesReturn} | 
|  | */ | 
|  | export function decorate(source, constr) { | 
|  | let elements; | 
|  | if (typeof source === 'string') { | 
|  | elements = document.querySelectorAll(source); | 
|  | } else { | 
|  | elements = [source]; | 
|  | } | 
|  |  | 
|  | for (let i = 0, el; el = elements[i]; i++) { | 
|  | if (!(el instanceof constr)) { | 
|  | constr.decorate(el); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper function for creating new element for define. | 
|  | */ | 
|  | function createElementHelper(tagName, opt_bag) { | 
|  | // Allow passing in ownerDocument to create in a different document. | 
|  | let doc; | 
|  | if (opt_bag && opt_bag.ownerDocument) { | 
|  | doc = opt_bag.ownerDocument; | 
|  | } else { | 
|  | doc = document; | 
|  | } | 
|  | return doc.createElement(tagName); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Creates the constructor for a UI element class. | 
|  | * | 
|  | * Usage: | 
|  | * <pre> | 
|  | * var List = cr.ui.define('list'); | 
|  | * List.prototype = { | 
|  | *   __proto__: HTMLUListElement.prototype, | 
|  | *   decorate() { | 
|  | *     ... | 
|  | *   }, | 
|  | *   ... | 
|  | * }; | 
|  | * </pre> | 
|  | * | 
|  | * @param {string|Function} tagNameOrFunction The tagName or | 
|  | *     function to use for newly created elements. If this is a function it | 
|  | *     needs to return a new element when called. | 
|  | * @return {function(Object=):Element} The constructor function which takes | 
|  | *     an optional property bag. The function also has a static | 
|  | *     {@code decorate} method added to it. | 
|  | */ | 
|  | export function define(tagNameOrFunction) { | 
|  | let createFunction; | 
|  | let tagName; | 
|  | if (typeof tagNameOrFunction === 'function') { | 
|  | createFunction = tagNameOrFunction; | 
|  | tagName = ''; | 
|  | } else { | 
|  | createFunction = createElementHelper; | 
|  | tagName = tagNameOrFunction; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Creates a new UI element constructor. | 
|  | * @param {Object=} opt_propertyBag Optional bag of properties to set on the | 
|  | *     object after created. The property {@code ownerDocument} is special | 
|  | *     cased and it allows you to create the element in a different | 
|  | *     document than the default. | 
|  | * @constructor | 
|  | */ | 
|  | function f(opt_propertyBag) { | 
|  | const el = createFunction(tagName, opt_propertyBag); | 
|  | f.decorate(el); | 
|  | for (const propertyName in opt_propertyBag) { | 
|  | el[propertyName] = opt_propertyBag[propertyName]; | 
|  | } | 
|  | return el; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Decorates an element as a UI element class. | 
|  | * @param {!Element} el The element to decorate. | 
|  | */ | 
|  | f.decorate = function(el) { | 
|  | el.__proto__ = f.prototype; | 
|  | if (el.decorate) { | 
|  | el.decorate(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | return f; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Input elements do not grow and shrink with their content. This is a simple | 
|  | * (and not very efficient) way of handling shrinking to content with support | 
|  | * for min width and limited by the width of the parent element. | 
|  | * @param {!HTMLElement} el The element to limit the width for. | 
|  | * @param {!HTMLElement} parentEl The parent element that should limit the | 
|  | *     size. | 
|  | * @param {number} min The minimum width. | 
|  | * @param {number=} opt_scale Optional scale factor to apply to the width. | 
|  | */ | 
|  | export function limitInputWidth(el, parentEl, min, opt_scale) { | 
|  | // Needs a size larger than borders | 
|  | el.style.width = '10px'; | 
|  | const doc = el.ownerDocument; | 
|  | const win = doc.defaultView; | 
|  | const computedStyle = win.getComputedStyle(el); | 
|  | const parentComputedStyle = win.getComputedStyle(parentEl); | 
|  | const rtl = computedStyle.direction === 'rtl'; | 
|  |  | 
|  | // To get the max width we get the width of the treeItem minus the position | 
|  | // of the input. | 
|  | const inputRect = el.getBoundingClientRect();  // box-sizing | 
|  | const parentRect = parentEl.getBoundingClientRect(); | 
|  | const startPos = rtl ? parentRect.right - inputRect.right : | 
|  | inputRect.left - parentRect.left; | 
|  |  | 
|  | // Add up border and padding of the input. | 
|  | const inner = parseInt(computedStyle.borderLeftWidth, 10) + | 
|  | parseInt(computedStyle.paddingLeft, 10) + | 
|  | parseInt(computedStyle.paddingRight, 10) + | 
|  | parseInt(computedStyle.borderRightWidth, 10); | 
|  |  | 
|  | // We also need to subtract the padding of parent to prevent it to overflow. | 
|  | const parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) : | 
|  | parseInt(parentComputedStyle.paddingRight, 10); | 
|  |  | 
|  | let max = parentEl.clientWidth - startPos - inner - parentPadding; | 
|  | if (opt_scale) { | 
|  | max *= opt_scale; | 
|  | } | 
|  |  | 
|  | function limit() { | 
|  | if (el.scrollWidth > max) { | 
|  | el.style.width = max + 'px'; | 
|  | } else { | 
|  | el.style.width = 0; | 
|  | const sw = el.scrollWidth; | 
|  | if (sw < min) { | 
|  | el.style.width = min + 'px'; | 
|  | } else { | 
|  | el.style.width = sw + 'px'; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | el.addEventListener('input', limit); | 
|  | limit(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Takes a number and spits out a value CSS will be happy with. To avoid | 
|  | * subpixel layout issues, the value is rounded to the nearest integral value. | 
|  | * @param {number} pixels The number of pixels. | 
|  | * @return {string} e.g. '16px'. | 
|  | */ | 
|  | export function toCssPx(pixels) { | 
|  | if (!window.isFinite(pixels)) { | 
|  | console.error('Pixel value is not a number: ' + pixels); | 
|  | } | 
|  | return Math.round(pixels) + 'px'; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Users complain they occasionaly use doubleclicks instead of clicks | 
|  | * (http://crbug.com/140364). To fix it we freeze click handling for | 
|  | * the doubleclick time interval. | 
|  | * @param {MouseEvent} e Initial click event. | 
|  | */ | 
|  | export function swallowDoubleClick(e) { | 
|  | const doc = e.target.ownerDocument; | 
|  | let counter = Math.min(1, e.detail); | 
|  | function swallow(e) { | 
|  | e.stopPropagation(); | 
|  | e.preventDefault(); | 
|  | } | 
|  | function onclick(e) { | 
|  | if (e.detail > counter) { | 
|  | counter = e.detail; | 
|  | // Swallow the click since it's a click inside the doubleclick timeout. | 
|  | swallow(e); | 
|  | } else { | 
|  | // Stop tracking clicks and let regular handling. | 
|  | doc.removeEventListener('dblclick', swallow, true); | 
|  | doc.removeEventListener('click', onclick, true); | 
|  | } | 
|  | } | 
|  | // The following 'click' event (if e.type === 'mouseup') mustn't be taken | 
|  | // into account (it mustn't stop tracking clicks). Start event listening | 
|  | // after zero timeout. | 
|  | setTimeout(function() { | 
|  | doc.addEventListener('click', onclick, true); | 
|  | doc.addEventListener('dblclick', swallow, true); | 
|  | }, 0); | 
|  | } | 
|  |  |