chromium/third_party/google-closure-library/closure/goog/ui/emoji/popupemojipicker.js

/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Popup Emoji Picker implementation. This provides a UI widget
 * for choosing an emoji from a grid of possible choices. The widget is a popup,
 * so it is suitable for a toolbar, for instance the TrogEdit toolbar.
 *
 * @see ../demos/popupemojipicker.html for an example of how to instantiate
 * an emoji picker.
 *
 * See goog.ui.emoji.EmojiPicker in emojipicker.js for more details.
 *
 * Based on goog.ui.PopupColorPicker (popupcolorpicker.js).
 *
 * @see ../../demos/popupemojipicker.html
 */

goog.provide('goog.ui.emoji.PopupEmojiPicker');

goog.require('goog.events.EventType');
goog.require('goog.positioning.AnchoredPosition');
goog.require('goog.positioning.Corner');
goog.require('goog.ui.Component');
goog.require('goog.ui.Popup');
goog.require('goog.ui.emoji.EmojiPicker');
goog.requireType('goog.dom.DomHelper');
goog.requireType('goog.events.BrowserEvent');
goog.requireType('goog.events.Event');
goog.requireType('goog.ui.PopupBase');
goog.requireType('goog.ui.TabPane.TabLocation');
goog.requireType('goog.ui.emoji.Emoji');



/**
 * Constructs a popup emoji picker widget.
 *
 * @param {string} defaultImgUrl Url of the img that should be used to fill up
 *     the cells in the emoji table, to prevent jittering. Should be the same
 *     size as the emoji.
 * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
 * @extends {goog.ui.Component}
 * @constructor
 * @final
 */
goog.ui.emoji.PopupEmojiPicker = function(defaultImgUrl, opt_domHelper) {
  'use strict';
  goog.ui.Component.call(this, opt_domHelper);

  this.emojiPicker_ =
      new goog.ui.emoji.EmojiPicker(defaultImgUrl, opt_domHelper);
  this.addChild(this.emojiPicker_);

  this.getHandler().listen(
      this.emojiPicker_, goog.ui.Component.EventType.ACTION,
      this.onEmojiPicked_);
};
goog.inherits(goog.ui.emoji.PopupEmojiPicker, goog.ui.Component);


/**
 * Instance of an emoji picker control.
 * @type {?goog.ui.emoji.EmojiPicker}
 * @private
 */
goog.ui.emoji.PopupEmojiPicker.prototype.emojiPicker_ = null;


/**
 * Instance of goog.ui.Popup used to manage the behavior of the emoji picker.
 * @type {?goog.ui.Popup}
 * @private
 */
goog.ui.emoji.PopupEmojiPicker.prototype.popup_ = null;


/**
 * Reference to the element that triggered the last popup.
 * @type {?Element}
 * @private
 */
goog.ui.emoji.PopupEmojiPicker.prototype.lastTarget_ = null;


/**
 * Whether the emoji picker can accept focus.
 * @type {boolean}
 * @private
 */
goog.ui.emoji.PopupEmojiPicker.prototype.focusable_ = true;


/**
 * If true, then the emojipicker will toggle off if it is already visible.
 * Default is true.
 * @type {boolean}
 * @private
 */
goog.ui.emoji.PopupEmojiPicker.prototype.toggleMode_ = true;


/**
 * Adds a group of emoji to the picker.
 *
 * @param {string|Element} title Title for the group.
 * @param {Array<Array<?>>} emojiGroup A new group of emoji to be added. Each
 *    internal array contains [emojiUrl, emojiId].
 */
goog.ui.emoji.PopupEmojiPicker.prototype.addEmojiGroup = function(
    title, emojiGroup) {
  'use strict';
  this.emojiPicker_.addEmojiGroup(title, emojiGroup);
};


/**
 * Sets whether the emoji picker should toggle if it is already open.
 * @param {boolean} toggle The toggle mode to use.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.setToggleMode = function(toggle) {
  'use strict';
  this.toggleMode_ = toggle;
};


/**
 * Gets whether the emojipicker is in toggle mode
 * @return {boolean} toggle.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.getToggleMode = function() {
  'use strict';
  return this.toggleMode_;
};


/**
 * Sets whether loading of images should be delayed until after dom creation.
 * Thus, this function must be called before {@link #createDom}. If set to true,
 * the client must call {@link #loadImages} when they wish the images to be
 * loaded.
 *
 * @param {boolean} shouldDelay Whether to delay loading the images.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.setDelayedLoad = function(
    shouldDelay) {
  'use strict';
  if (this.emojiPicker_) {
    this.emojiPicker_.setDelayedLoad(shouldDelay);
  }
};


/**
 * Sets whether the emoji picker can accept focus.
 * @param {boolean} focusable Whether the emoji picker should accept focus.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.setFocusable = function(focusable) {
  'use strict';
  this.focusable_ = focusable;
  if (this.emojiPicker_) {
    // TODO(user): In next revision sort the behavior of passing state to
    // children correctly
    this.emojiPicker_.setFocusable(focusable);
  }
};


/**
 * Sets the URL prefix for the emoji URLs.
 *
 * @param {string} urlPrefix Prefix that should be prepended to all URLs.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.setUrlPrefix = function(urlPrefix) {
  'use strict';
  this.emojiPicker_.setUrlPrefix(urlPrefix);
};


/**
 * Sets the location of the tabs in relation to the emoji grids. This should
 * only be called before the picker has been rendered.
 *
 * @param {goog.ui.TabPane.TabLocation} tabLocation The location of the tabs.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.setTabLocation = function(
    tabLocation) {
  'use strict';
  this.emojiPicker_.setTabLocation(tabLocation);
};


/**
 * Sets the number of rows per grid in the emoji picker. This should only be
 * called before the picker has been rendered.
 *
 * @param {number} numRows Number of rows per grid.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.setNumRows = function(numRows) {
  'use strict';
  this.emojiPicker_.setNumRows(numRows);
};


/**
 * Sets the number of columns per grid in the emoji picker. This should only be
 * called before the picker has been rendered.
 *
 * @param {number} numCols Number of columns per grid.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.setNumColumns = function(numCols) {
  'use strict';
  this.emojiPicker_.setNumColumns(numCols);
};


/**
 * Sets the progressive rendering aspect of this emojipicker. Must be called
 * before createDom to have an effect.
 *
 * @param {boolean} progressive Whether the picker should render progressively.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.setProgressiveRender = function(
    progressive) {
  'use strict';
  if (this.emojiPicker_) {
    this.emojiPicker_.setProgressiveRender(progressive);
  }
};


/**
 * Returns the number of emoji groups in this picker.
 *
 * @return {number} The number of emoji groups in this picker.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.getNumEmojiGroups = function() {
  'use strict';
  return this.emojiPicker_.getNumEmojiGroups();
};


/**
 * Causes the emoji imgs to be loaded into the picker. Used for delayed loading.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.loadImages = function() {
  'use strict';
  if (this.emojiPicker_) {
    this.emojiPicker_.loadImages();
  }
};


/** @override */
goog.ui.emoji.PopupEmojiPicker.prototype.createDom = function() {
  'use strict';
  goog.ui.emoji.PopupEmojiPicker.superClass_.createDom.call(this);

  this.emojiPicker_.createDom();

  this.getElement().className = goog.getCssName('goog-ui-popupemojipicker');
  this.getElement().appendChild(
      /** @type {!Node} */ (this.emojiPicker_.getElement()));

  this.popup_ = new goog.ui.Popup(this.getElement());
  this.getElement().unselectable = 'on';
};


/** @override */
goog.ui.emoji.PopupEmojiPicker.prototype.disposeInternal = function() {
  'use strict';
  goog.ui.emoji.PopupEmojiPicker.superClass_.disposeInternal.call(this);
  this.emojiPicker_ = null;
  this.lastTarget_ = null;
  if (this.popup_) {
    this.popup_.dispose();
    this.popup_ = null;
  }
};


/**
 * Attaches the popup emoji picker to an element.
 *
 * @param {Element} element The element to attach to.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.attach = function(element) {
  'use strict';
  // TODO(user): standardize event type, popups should use MOUSEDOWN, but
  // currently apps are using click.
  this.getHandler().listen(element, goog.events.EventType.CLICK, this.show_);
};


/**
 * Detatches the popup emoji picker from an element.
 *
 * @param {Element} element The element to detach from.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.detach = function(element) {
  'use strict';
  this.getHandler().unlisten(element, goog.events.EventType.CLICK, this.show_);
};


/**
 * @return {goog.ui.emoji.EmojiPicker} The emoji picker instance.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.getEmojiPicker = function() {
  'use strict';
  return this.emojiPicker_;
};


/**
 * Returns whether the Popup dismisses itself when the user clicks outside of
 * it.
 * @return {boolean} Whether the Popup autohides on an external click.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.getAutoHide = function() {
  'use strict';
  return !!this.popup_ && this.popup_.getAutoHide();
};


/**
 * Sets whether the Popup dismisses itself when the user clicks outside of it -
 * must be called after the Popup has been created (in createDom()),
 * otherwise it does nothing.
 *
 * @param {boolean} autoHide Whether to autohide on an external click.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.setAutoHide = function(autoHide) {
  'use strict';
  if (this.popup_) {
    this.popup_.setAutoHide(autoHide);
  }
};


/**
 * Returns the region inside which the Popup dismisses itself when the user
 * clicks, or null if it was not set. Null indicates the entire document is
 * the autohide region.
 * @return {Element} The DOM element for autohide, or null if it hasn't been
 *     set.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.getAutoHideRegion = function() {
  'use strict';
  return this.popup_ && this.popup_.getAutoHideRegion();
};


/**
 * Sets the region inside which the Popup dismisses itself when the user
 * clicks - must be called after the Popup has been created (in createDom()),
 * otherwise it does nothing.
 *
 * @param {Element} element The DOM element for autohide.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.setAutoHideRegion = function(element) {
  'use strict';
  if (this.popup_) {
    this.popup_.setAutoHideRegion(element);
  }
};


/**
 * Returns the {@link goog.ui.PopupBase} from this picker. Returns null if the
 * popup has not yet been created.
 *
 * NOTE: This should *ONLY* be called from tests. If called before createDom(),
 * this should return null.
 *
 * @return {goog.ui.PopupBase?} The popup, or null if it hasn't been created.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.getPopup = function() {
  'use strict';
  return this.popup_;
};


/**
 * @return {Element} The last element that triggered the popup.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.getLastTarget = function() {
  'use strict';
  return this.lastTarget_;
};


/**
 * @return {goog.ui.emoji.Emoji} The currently selected emoji.
 */
goog.ui.emoji.PopupEmojiPicker.prototype.getSelectedEmoji = function() {
  'use strict';
  return this.emojiPicker_.getSelectedEmoji();
};


/**
 * Handles click events on the element this picker is attached to and shows the
 * emoji picker in a popup.
 *
 * @param {goog.events.BrowserEvent} e The browser event.
 * @private
 */
goog.ui.emoji.PopupEmojiPicker.prototype.show_ = function(e) {
  'use strict';
  if (this.popup_.isOrWasRecentlyVisible() && this.toggleMode_ &&
      this.lastTarget_ == e.currentTarget) {
    this.popup_.setVisible(false);
    return;
  }

  this.lastTarget_ = /** @type {Element} */ (e.currentTarget);
  this.popup_.setPosition(
      new goog.positioning.AnchoredPosition(
          this.lastTarget_, goog.positioning.Corner.BOTTOM_LEFT));
  this.popup_.setVisible(true);
};


/**
 * Handles selection of an emoji.
 *
 * @param {goog.events.Event} e The event object.
 * @private
 */
goog.ui.emoji.PopupEmojiPicker.prototype.onEmojiPicked_ = function(e) {
  'use strict';
  this.popup_.setVisible(false);
};