<commit_before>import { Feature } from 'toolkit/extension/features/feature';
import { getEmberView } from 'toolkit/extension/utils/ember';
import { formatCurrency } from 'toolkit/extension/utils/currency';
import { isCurrentRouteBudgetPage } from 'toolkit/extension/utils/ynab';
import {
  CategoryAttributes,
  GOAL_TABLE_CELL_CLASSNAME,
} from 'toolkit/extension/features/budget/budget-category-features';

export const Settings = {
  Off: '0',
  WarnBudgetOverTarget: '1',
  GreenBudgetOverTarget: '2',
  NoEmphasis: '3',
};

const EmphasisClass = {
  [Settings.WarnBudgetOverTarget]: 'toolkit-display-goal-warn',
  [Settings.GreenBudgetOverTarget]: 'toolkit-display-goal-highlight',
};

export class DisplayTargetGoalAmount extends Feature {
  injectCSS() {
    return require('./index.css');
  }

  shouldInvoke() {
    return isCurrentRouteBudgetPage();
  }

  invoke() {
    const userSetting = this.settings.enabled;
    const budgetRows = [...document.getElementsByClassName('budget-table-row')];
    budgetRows.forEach(element => {
      const category = getEmberView(element.id, 'category');
      if (!category) {
        return;
      }

      const goalType = element.getAttribute(`data-${CategoryAttributes.GoalType}`);
      const {
        subCategory,
        monthlySubCategoryBudget,
        monthlySubCategoryBudgetCalculation,
      } = category.getProperties(
        'subCategory',
        'monthlySubCategoryBudget',
        'monthlySubCategoryBudgetCalculation'
      );

      if (!subCategory || !monthlySubCategoryBudgetCalculation || !monthlySubCategoryBudget) {
        return;
      }

      const { monthlyFunding, goalTargetAmount } = subCategory.getProperties(
        'monthlyFunding',
        'goalTargetAmount'
      );
      const { balance, targetBalanceDate } = monthlySubCategoryBudgetCalculation.getProperties(
        'balance',
        'goalTarget'
      );
      const { budgeted } = monthlySubCategoryBudget.get('budgeted');

      let goalAmount = null;
      let applyEmphasis = false;
      switch (goalType) {
        case ynab.constants.SubCategoryGoalType.MonthlyFunding:
          goalAmount = monthlyFunding;
          if (userSetting === Settings.WarnBudgetOverTarget && budgeted > monthlyFunding) {
            applyEmphasis = true;
          } else if (userSetting === Settings.GreenBudgetOverTarget && budgeted >= monthlyFunding) {
            applyEmphasis = true;
          }
          break;
        case ynab.constants.SubCategoryGoalType.Needed:
          goalAmount = goalTargetAmount;
          if (userSetting === Settings.WarnBudgetOverTarget && balance > goalAmount) {
            applyEmphasis = true;
          } else if (userSetting === Settings.GreenBudgetOverTarget && balance >= goalAmount) {
            applyEmphasis = true;
          }
          break;
        case ynab.constants.SubCategoryGoalType.TargetBalance:
          goalAmount = goalTargetAmount;
          if (userSetting === Settings.WarnBudgetOverTarget && budgeted > goalTargetAmount) {
            applyEmphasis = true;
          } else if (
            userSetting === Settings.GreenBudgetOverTarget &&
            budgeted >= goalTargetAmount
          ) {
            applyEmphasis = true;
          }
          break;
        case ynab.constants.SubCategoryGoalType.TargetBalanceOnDate:
          goalAmount = targetBalanceDate;
          if (userSetting === Settings.WarnBudgetOverTarget && budgeted > targetBalanceDate) {
            applyEmphasis = true;
          } else if (
            userSetting === Settings.GreenBudgetOverTarget &&
            budgeted >= targetBalanceDate
          ) {
            applyEmphasis = true;
          }
          break;
      }

      const goalAmountElement = element.querySelector('.toolkit-target-goal-amount');
      if (goalAmountElement) {
        if (goalAmount === null) {
          goalAmountElement.remove();
        }

        $(goalAmountElement).text(formatCurrency(goalAmount));

        if (applyEmphasis) {
          $(goalAmountElement).addClass(EmphasisClass[this.settings.enabled]);
        } else {
          $(goalAmountElement).removeClass(EmphasisClass[this.settings.enabled]);
        }
      } else if (goalAmount !== null) {
        $(`.${GOAL_TABLE_CELL_CLASSNAME}`, element).prepend(
          $('<div>', {
            class: `toolkit-target-goal-amount currency ${
              applyEmphasis ? EmphasisClass[this.settings.enabled] : ''
            }`,
            text: formatCurrency(goalAmount),
          })
        );
      }
    });
  }
}
<commit_msg>Fix naming issue with goal amounts (#1661)<commit_after>import { Feature } from 'toolkit/extension/features/feature';
import { getEmberView } from 'toolkit/extension/utils/ember';
import { formatCurrency } from 'toolkit/extension/utils/currency';
import { isCurrentRouteBudgetPage } from 'toolkit/extension/utils/ynab';
import {
  CategoryAttributes,
  GOAL_TABLE_CELL_CLASSNAME,
} from 'toolkit/extension/features/budget/budget-category-features';

export const Settings = {
  Off: '0',
  WarnBudgetOverTarget: '1',
  GreenBudgetOverTarget: '2',
  NoEmphasis: '3',
};

const EmphasisClass = {
  [Settings.WarnBudgetOverTarget]: 'toolkit-display-goal-warn',
  [Settings.GreenBudgetOverTarget]: 'toolkit-display-goal-highlight',
};

export class DisplayTargetGoalAmount extends Feature {
  injectCSS() {
    return require('./index.css');
  }

  shouldInvoke() {
    return isCurrentRouteBudgetPage();
  }

  invoke() {
    const userSetting = this.settings.enabled;
    const budgetRows = [...document.getElementsByClassName('budget-table-row')];
    budgetRows.forEach(element => {
      const category = getEmberView(element.id, 'category');
      if (!category) {
        return;
      }

      const goalType = element.getAttribute(`data-${CategoryAttributes.GoalType}`);
      const {
        subCategory,
        monthlySubCategoryBudget,
        monthlySubCategoryBudgetCalculation,
      } = category.getProperties(
        'subCategory',
        'monthlySubCategoryBudget',
        'monthlySubCategoryBudgetCalculation'
      );

      if (!subCategory || !monthlySubCategoryBudgetCalculation || !monthlySubCategoryBudget) {
        return;
      }

      const { monthlyFunding, goalTargetAmount } = subCategory.getProperties(
        'monthlyFunding',
        'goalTargetAmount'
      );
      const { balance, goalTarget } = monthlySubCategoryBudgetCalculation.getProperties(
        'balance',
        'goalTarget'
      );
      const { budgeted } = monthlySubCategoryBudget.get('budgeted');

      let goalAmount = null;
      let applyEmphasis = false;
      switch (goalType) {
        case ynab.constants.SubCategoryGoalType.MonthlyFunding:
          goalAmount = monthlyFunding;
          if (userSetting === Settings.WarnBudgetOverTarget && budgeted > monthlyFunding) {
            applyEmphasis = true;
          } else if (userSetting === Settings.GreenBudgetOverTarget && budgeted >= monthlyFunding) {
            applyEmphasis = true;
          }
          break;
        case ynab.constants.SubCategoryGoalType.Needed:
          goalAmount = goalTargetAmount;
          if (userSetting === Settings.WarnBudgetOverTarget && balance > goalAmount) {
            applyEmphasis = true;
          } else if (userSetting === Settings.GreenBudgetOverTarget && balance >= goalAmount) {
            applyEmphasis = true;
          }
          break;
        case ynab.constants.SubCategoryGoalType.TargetBalance:
          goalAmount = goalTargetAmount;
          if (userSetting === Settings.WarnBudgetOverTarget && budgeted > goalTargetAmount) {
            applyEmphasis = true;
          } else if (
            userSetting === Settings.GreenBudgetOverTarget &&
            budgeted >= goalTargetAmount
          ) {
            applyEmphasis = true;
          }
          break;
        case ynab.constants.SubCategoryGoalType.TargetBalanceOnDate:
          goalAmount = goalTarget;
          if (userSetting === Settings.WarnBudgetOverTarget && budgeted > goalTarget) {
            applyEmphasis = true;
          } else if (userSetting === Settings.GreenBudgetOverTarget && budgeted >= goalTarget) {
            applyEmphasis = true;
          }
          break;
      }

      const goalAmountElement = element.querySelector('.toolkit-target-goal-amount');
      if (goalAmountElement) {
        if (goalAmount === null) {
          goalAmountElement.remove();
        }

        $(goalAmountElement).text(formatCurrency(goalAmount));

        if (applyEmphasis) {
          $(goalAmountElement).addClass(EmphasisClass[this.settings.enabled]);
        } else {
          $(goalAmountElement).removeClass(EmphasisClass[this.settings.enabled]);
        }
      } else if (goalAmount !== null) {
        $(`.${GOAL_TABLE_CELL_CLASSNAME}`, element).prepend(
          $('<div>', {
            class: `toolkit-target-goal-amount currency ${
              applyEmphasis ? EmphasisClass[this.settings.enabled] : ''
            }`,
            text: formatCurrency(goalAmount),
          })
        );
      }
    });
  }
}
<|endoftext|>


<commit_before>"use strict";

angular.module('arethusa.core').service('arethusaGrid', [
  'gridsterConfig',
  '$window',
  function(gridsterConfig, $window) {
    var self = this;

    var win = angular.element($window);

    this.options = angular.extend(gridsterConfig, {
      columns: 20,
      rowHeight: 'match',
      resizable: {
        enabled: true,
        stop: function() { win.trigger('resize'); }
      }
    });

    function Item(size, position, plugin) {
      return {
        sizeX: size[0],
        sizeY: size[1],
        row: position[0],
        col: position[1],
        plugin: plugin,
      };
    }

    this.items = [
      new Item([14, 1], [0, 0], 'text'),
      new Item([9, 8],  [2, 0], 'depTree'),
      new Item([5, 8],  [1, 9], 'comments'),
      new Item([6, 2],  [0, 14], 'search'),
      new Item([6, 4],  [2, 14], 'morph'),
      new Item([6, 3],  [6, 14], 'relation')
    ];
  }
]);
<commit_msg>Show resize handles in grid<commit_after>"use strict";

angular.module('arethusa.core').service('arethusaGrid', [
  'gridsterConfig',
  '$window',
  function(gridsterConfig, $window) {
    var self = this;

    var win = angular.element($window);

    this.options = angular.extend(gridsterConfig, {
      columns: 20,
      rowHeight: 'match',
      resizable: {
        enabled: true,
        handles: ['n', 'e', 's', 'w', 'ne', 'nw', 'se', 'sw'],
        stop: function() { win.trigger('resize'); }
      }
    });

    function Item(size, position, plugin) {
      return {
        sizeX: size[0],
        sizeY: size[1],
        row: position[0],
        col: position[1],
        plugin: plugin,
      };
    }

    this.items = [
      new Item([14, 1], [0, 0], 'text'),
      new Item([9, 8],  [2, 0], 'depTree'),
      new Item([5, 8],  [1, 9], 'comments'),
      new Item([6, 2],  [0, 14], 'search'),
      new Item([6, 4],  [2, 14], 'morph'),
      new Item([6, 3],  [6, 14], 'relation')
    ];
  }
]);
<|endoftext|>


<commit_before>import React from 'react';

import { ReactComponent as Logo } from 'interface/images/logo.svg';

// This has an elevator music easter egg that occurs randomly once every 5 times.
// Lazy load it to minimize bundle impact. This whole joke has a tiny amount of overhead.
let elevator;
async function loadElevator() {
  elevator = (await import('./elevate')).default;
}
let useElevator = Math.random() < 0.2;

function scrollToTop() {
  if (!useElevator || !elevator) {
    window.scrollTo(0, 0);
  } else {
    elevator();
    // Only do it once to increase ~~confusion~~ mystery
    useElevator = false;
  }
}

const ScrollToTop = () => (

    <Logo
      style={{ '--arrow': '#fab700', '--main': '#1c1c1b', maxWidth: 50 }}
    />

);

export default ScrollToTop;
<commit_msg>Reduce elevator chance to 1 in 10 again<commit_after>import React from 'react';

import { ReactComponent as Logo } from 'interface/images/logo.svg';

// This has an elevator music easter egg that occurs randomly once every 5 times.
// Lazy load it to minimize bundle impact. This whole joke has a tiny amount of overhead.
let elevator;
async function loadElevator() {
  elevator = (await import('./elevate')).default;
}
let useElevator = Math.random() < 0.1;

function scrollToTop() {
  if (!useElevator || !elevator) {
    window.scrollTo(0, 0);
  } else {
    elevator();
    // Only do it once to increase ~~confusion~~ mystery
    useElevator = false;
  }
}

const ScrollToTop = () => (

    <Logo
      style={{ '--arrow': '#fab700', '--main': '#1c1c1b', maxWidth: 50 }}
    />

);

export default ScrollToTop;
<|endoftext|>


<commit_before>import { promise } from "lively.lang";
import bowser from "bowser";

const placeholderValue = "\x01\x01",
      placeholderRe = new RegExp("\x01", "g");

export default class TextInput {

  constructor(eventDispatcher) {
    this.eventDispatcher = eventDispatcher;

    this.domState = {
      rootNode: null,
      textareaNode: null,
      eventHandlers: [],
      isInstalled: false
    }

    this.inputState = {
      composition: null,
      manualCopy: null,
      manualPaste: null
    }
  }

  install(newRootNode) {
    let domState = this.domState,
        {isInstalled, rootNode} = domState;

    if (isInstalled) {
      if (rootNode === newRootNode) return;
      this.uninstall();
    }

    domState.isInstalled = true;
    domState.rootNode = newRootNode;

    newRootNode.tabIndex = 1; // focusable so that we can relay the focus to the textarea

    // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    // textarea element that acts as an event proxy

    var doc = newRootNode.ownerDocument,
        textareaNode = domState.textareaNode = doc.createElement("textarea");

    textareaNode.setAttribute("style", `
      position: absolute;
      /*extent cannot be 0, input won't work correctly in Chrome 52.0*/
      width: 20px; height: 20px;
      z-index: 0;
      opacity: 0;
      background: transparent;
      -moz-appearance: none;
      appearance: none;
      border: none;
      resize: none;
      outline: none;
      overflow: hidden;
      font: inherit;
      padding: 0 1px;
      margin: 0 -1px;
      text-indent: -1em;
      -ms-user-select: text;
      -moz-user-select: text;
      -webkit-user-select: text;
      user-select: text;
      /*with pre-line chrome inserts   instead of space*/
      white-space: pre!important;`);

    if (bowser.tablet || bowser.mobile)
      textareaNode.setAttribute("x-palm-disable-auto-cap", true);

    textareaNode.setAttribute("wrap", "off");
    textareaNode.setAttribute("autocorrect", "off");
    textareaNode.setAttribute("autocapitalize", "off");
    textareaNode.setAttribute("spellcheck", false);
    textareaNode.className = "lively-text-input";
    textareaNode.value = "";
    newRootNode.insertBefore(textareaNode, newRootNode.firstChild);

    // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    // event handlers
    domState.eventHandlers = [
      {type: "focus",   node: newRootNode,           fn: evt => this.onRootNodeFocus(evt), capturing: true},
      {type: "blur",    node: domState.textareaNode, fn: evt => this.onTextAreaBlur(evt), capturing: true},
      {type: "keydown", node: domState.textareaNode, fn: evt => this.eventDispatcher.dispatchDOMEvent(evt), capturing: false},
      {type: "keyup",   node: domState.textareaNode, fn: evt => this.eventDispatcher.dispatchDOMEvent(evt), capturing: false},
      {type: "cut",     node: domState.textareaNode, fn: evt => this.eventDispatcher.dispatchDOMEvent(evt), capturing: false},
      {type: "copy",    node: domState.textareaNode, fn: evt => this.inputState.manualCopy ? this.inputState.manualCopy.onEvent(evt) : this.eventDispatcher.dispatchDOMEvent(evt), capturing: false},
      {type: "paste",   node: domState.textareaNode, fn: evt => this.inputState.manualPaste ? this.inputState.manualPaste.onEvent(evt) : this.eventDispatcher.dispatchDOMEvent(evt), capturing: false},

      {type: "compositionstart",  node: domState.textareaNode, fn: evt => this.onCompositionStart(evt), capturing: false},
      {type: "compositionend",    node: domState.textareaNode, fn: evt => this.onCompositionEnd(evt), capturing: false},
      {type: "compositionupdate", node: domState.textareaNode, fn: evt => this.onCompositionUpdate(evt), capturing: false},
      {type: "input",             node: domState.textareaNode, fn: evt => this.onInput(evt), capturing: false},
    ]
    domState.eventHandlers.forEach(({type, node, fn, capturing}) =>
      node.addEventListener(type, fn, capturing));

    return this;
  }

  uninstall() {
    var domState = this.domState;

    domState.isInstalled = false;

    domState.eventHandlers.forEach(({node, type, fn, capturing}) =>
      node.removeEventListener(type, fn, capturing));

    var n = domState.textareaNode;
    n && n.parentNode && n.parentNode.removeChild(n)
    domState.rootNode = null;

    return this;
  }

  resetValue() {
    var n = this.domState.textareaNode;
    n && (n.value = placeholderValue);
  }

  readValue() {
    var n = this.domState.textareaNode;
    return n ? n.value.replace(placeholderRe, "") : "";

  //   if (!n) return "";
  //   var val = n.value;
  //   var placeholder1 = placeholderValue.charAt(0);
  // // if (val == placeholder1) return "DELETE";
  //   if (val.substring(0, 2) == placeholderValue)
  //     val = val.substr(2);
  //   else if (val.charAt(0) == placeholder1)
  //     val = val.substr(1);
  //   else if (val.charAt(val.length - 1) == placeholder1)
  //     val = val.slice(0, -1);
  //   // can happen if undo in textarea isn't stopped
  //   if (val.charAt(val.length - 1) == placeholder1)
  //     val = val.slice(0, -1);
  //   return val;

  }

  focus(morph, world) {
    var node = this.domState.textareaNode;
    if (!node) return;
    node.ownerDocument.activeElement !== node && node.focus();

    if (bowser.firefox) // FF needs an extra invitation...
      Promise.resolve().then(() => node.ownerDocument.activeElement !== node && node.focus());

    if (morph && morph.isText) this.ensureBeingAtCursorOfText(morph);
    else if (world) this.ensureBeingInVisibleBoundsOfWorld(world);
  }

  blur() {
    var node = this.domState.textareaNode;
    node && node.blur();
  }

  doCopyWithMimeTypes(dataAndTypes) {
    // dataAndTypes [{data: STRING, type: mime-type-STRING}]
    return this.execCommand("manualCopy", () => {
      var el = this.domState.textareaNode;
      let h = evt => {
        el.removeEventListener('copy', h);
        evt.preventDefault();
        dataAndTypes.forEach(({data, type}) => evt.clipboardData.setData(type, data));
      }
      setTimeout(() => el.removeEventListener('copy', h), 300);
      el.addEventListener('copy', h);
      el.value = "";
      el.select();
      el.ownerDocument.execCommand("copy")
    });
  }

  doCopy(content) {
    // attempt to manually copy to the clipboard
    // this might fail for various strange browser reasons
    // also it will probably steal the focus...
    return this.execCommand("manualCopy", () => {
      var el = this.domState.textareaNode;
      el.value = content;
      el.select();
      el.ownerDocument.execCommand("copy");
    });
  }

  doPaste() {
    return this.execCommand("manualPaste", () => {
      var el = this.domState.textareaNode;
      el.value = "";
      el.select();
      el.ownerDocument.execCommand("paste");
    });
  }

  async execCommand(stateName, execFn) {
    if (!this.domState.isInstalled)
      throw new Error("Cannot copy to clipboard – input helper is not installed into DOM!");

    var state = this.inputState;
    if (state[stateName]) {
      try {
        await state[stateName].promise;
      } catch (e) {}
    }

    var deferred = promise.deferred(), isDone = false;
    state[stateName] = {
      onEvent: evt => {
        if (isDone) return;
        state[stateName] = null;
        isDone = true;
        deferred.resolve(evt);
      },
      promise: deferred.promise
    }

    execFn();

    try {
      await promise.waitFor(1000, () => isDone);
    } catch (e) {
      state[stateName] = null;
      isDone = true;
      deferred.reject(e);
    }

    return deferred.promise;
  }

  onTextAreaBlur(evt) {
    setTimeout(() => {
      var {textareaNode, rootNode} = this.domState || {};
      if (rootNode && document.activeElement === rootNode)
        rootNode && rootNode.focus();
    });
  }

  onRootNodeFocus(evt) {
    var {textareaNode, rootNode} = this.domState || {};
    if (evt.target === textareaNode || evt.target === rootNode)
      this.focus();
    this.inputState.composition = null;
  }

  onInput(evt) {
    if (this.inputState.composition) return;
    if (!evt.data) evt.data = this.readValue();
    this.resetValue();
    this.eventDispatcher.dispatchDOMEvent(evt);
  }

  onCompositionStart(evt) {
    this.inputState.composition = {};
  }

  onCompositionUpdate(evt) {
    var {composition: c} = this.inputState,
        val = this.readValue();
    if (c.lastValue === val) return;
    c.lastValue = val;
  }

  onCompositionEnd(evt) {
    this.inputState.composition = null;
  }

  setPosition(pos) {
    var {textareaNode} = this.domState || {};
    if (!textareaNode) return;
    textareaNode.style.left = pos.x + "px";
    textareaNode.style.top = pos.y + "px";
  }

  ensureBeingInVisibleBoundsOfWorld(world) {
    this.setPosition(world.visibleBounds().center());
  }

  ensureBeingAtCursorOfText(textMorph) {
    // move the textarea to the text cursor

    if (!textMorph.world()) return;

    let {startRow, endRow} = textMorph.whatsVisible,
        {row, column} = textMorph.cursorPosition;
    row = Math.max(startRow, Math.min(row, endRow));
    let localCursorPos = textMorph.charBoundsFromTextPosition({row, column}).topLeft(),
        globalCursorPos = textMorph.worldPoint(localCursorPos.subPt(textMorph.scroll));

    this.setPosition(globalCursorPos);
  }
}
<commit_msg>iPad: show virtual keyboard on focus()<commit_after>import { promise } from "lively.lang";
import bowser from "bowser";

const placeholderValue = "\x01\x01",
      placeholderRe = new RegExp("\x01", "g");

export default class TextInput {

  constructor(eventDispatcher) {
    this.eventDispatcher = eventDispatcher;

    this.domState = {
      rootNode: null,
      textareaNode: null,
      eventHandlers: [],
      isInstalled: false
    }

    this.inputState = {
      composition: null,
      manualCopy: null,
      manualPaste: null
    }
  }

  install(newRootNode) {
    let domState = this.domState,
        {isInstalled, rootNode} = domState;

    if (isInstalled) {
      if (rootNode === newRootNode) return;
      this.uninstall();
    }

    domState.isInstalled = true;
    domState.rootNode = newRootNode;

    newRootNode.tabIndex = 1; // focusable so that we can relay the focus to the textarea

    // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    // textarea element that acts as an event proxy

    var doc = newRootNode.ownerDocument,
        textareaNode = domState.textareaNode = doc.createElement("textarea");

    textareaNode.setAttribute("style", `
      position: absolute;
      /*extent cannot be 0, input won't work correctly in Chrome 52.0*/
      width: 20px; height: 20px;
      z-index: 0;
      opacity: 0;
      background: transparent;
      -moz-appearance: none;
      appearance: none;
      border: none;
      resize: none;
      outline: none;
      overflow: hidden;
      font: inherit;
      padding: 0 1px;
      margin: 0 -1px;
      text-indent: -1em;
      -ms-user-select: text;
      -moz-user-select: text;
      -webkit-user-select: text;
      user-select: text;
      /*with pre-line chrome inserts   instead of space*/
      white-space: pre!important;`);

    if (bowser.tablet || bowser.mobile)
      textareaNode.setAttribute("x-palm-disable-auto-cap", true);

    textareaNode.setAttribute("wrap", "off");
    textareaNode.setAttribute("autocorrect", "off");
    textareaNode.setAttribute("autocapitalize", "off");
    textareaNode.setAttribute("spellcheck", false);
    textareaNode.className = "lively-text-input";
    textareaNode.value = "";
    newRootNode.insertBefore(textareaNode, newRootNode.firstChild);

    // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    // event handlers
    domState.eventHandlers = [
      {type: "focus",   node: newRootNode,           fn: evt => this.onRootNodeFocus(evt), capturing: true},
      {type: "blur",    node: domState.textareaNode, fn: evt => this.onTextAreaBlur(evt), capturing: true},
      {type: "keydown", node: domState.textareaNode, fn: evt => this.eventDispatcher.dispatchDOMEvent(evt), capturing: false},
      {type: "keyup",   node: domState.textareaNode, fn: evt => this.eventDispatcher.dispatchDOMEvent(evt), capturing: false},
      {type: "cut",     node: domState.textareaNode, fn: evt => this.eventDispatcher.dispatchDOMEvent(evt), capturing: false},
      {type: "copy",    node: domState.textareaNode, fn: evt => this.inputState.manualCopy ? this.inputState.manualCopy.onEvent(evt) : this.eventDispatcher.dispatchDOMEvent(evt), capturing: false},
      {type: "paste",   node: domState.textareaNode, fn: evt => this.inputState.manualPaste ? this.inputState.manualPaste.onEvent(evt) : this.eventDispatcher.dispatchDOMEvent(evt), capturing: false},

      {type: "compositionstart",  node: domState.textareaNode, fn: evt => this.onCompositionStart(evt), capturing: false},
      {type: "compositionend",    node: domState.textareaNode, fn: evt => this.onCompositionEnd(evt), capturing: false},
      {type: "compositionupdate", node: domState.textareaNode, fn: evt => this.onCompositionUpdate(evt), capturing: false},
      {type: "input",             node: domState.textareaNode, fn: evt => this.onInput(evt), capturing: false},
    ]
    domState.eventHandlers.forEach(({type, node, fn, capturing}) =>
      node.addEventListener(type, fn, capturing));

    return this;
  }

  uninstall() {
    var domState = this.domState;

    domState.isInstalled = false;

    domState.eventHandlers.forEach(({node, type, fn, capturing}) =>
      node.removeEventListener(type, fn, capturing));

    var n = domState.textareaNode;
    n && n.parentNode && n.parentNode.removeChild(n)
    domState.rootNode = null;

    return this;
  }

  resetValue() {
    var n = this.domState.textareaNode;
    n && (n.value = placeholderValue);
  }

  readValue() {
    var n = this.domState.textareaNode;
    return n ? n.value.replace(placeholderRe, "") : "";

  //   if (!n) return "";
  //   var val = n.value;
  //   var placeholder1 = placeholderValue.charAt(0);
  // // if (val == placeholder1) return "DELETE";
  //   if (val.substring(0, 2) == placeholderValue)
  //     val = val.substr(2);
  //   else if (val.charAt(0) == placeholder1)
  //     val = val.substr(1);
  //   else if (val.charAt(val.length - 1) == placeholder1)
  //     val = val.slice(0, -1);
  //   // can happen if undo in textarea isn't stopped
  //   if (val.charAt(val.length - 1) == placeholder1)
  //     val = val.slice(0, -1);
  //   return val;

  }

  focus(morph, world) {
    var node = this.domState.textareaNode;
    if (!node) return;
    node.ownerDocument.activeElement !== node && node.focus();

    if (bowser.firefox) // FF needs an extra invitation...
      Promise.resolve().then(() => node.ownerDocument.activeElement !== node && node.focus());

    if (morph && morph.isText) {
      // need this even if node === activeElement
      // to bring up virtual keyboard on iPad
      node.focus();
      this.ensureBeingAtCursorOfText(morph);
    } else if (world) this.ensureBeingInVisibleBoundsOfWorld(world);
  }

  blur() {
    var node = this.domState.textareaNode;
    node && node.blur();
  }

  doCopyWithMimeTypes(dataAndTypes) {
    // dataAndTypes [{data: STRING, type: mime-type-STRING}]
    return this.execCommand("manualCopy", () => {
      var el = this.domState.textareaNode;
      let h = evt => {
        el.removeEventListener('copy', h);
        evt.preventDefault();
        dataAndTypes.forEach(({data, type}) => evt.clipboardData.setData(type, data));
      }
      setTimeout(() => el.removeEventListener('copy', h), 300);
      el.addEventListener('copy', h);
      el.value = "";
      el.select();
      el.ownerDocument.execCommand("copy")
    });
  }

  doCopy(content) {
    // attempt to manually copy to the clipboard
    // this might fail for various strange browser reasons
    // also it will probably steal the focus...
    return this.execCommand("manualCopy", () => {
      var el = this.domState.textareaNode;
      el.value = content;
      el.select();
      el.ownerDocument.execCommand("copy");
    });
  }

  doPaste() {
    return this.execCommand("manualPaste", () => {
      var el = this.domState.textareaNode;
      el.value = "";
      el.select();
      el.ownerDocument.execCommand("paste");
    });
  }

  async execCommand(stateName, execFn) {
    if (!this.domState.isInstalled)
      throw new Error("Cannot copy to clipboard – input helper is not installed into DOM!");

    var state = this.inputState;
    if (state[stateName]) {
      try {
        await state[stateName].promise;
      } catch (e) {}
    }

    var deferred = promise.deferred(), isDone = false;
    state[stateName] = {
      onEvent: evt => {
        if (isDone) return;
        state[stateName] = null;
        isDone = true;
        deferred.resolve(evt);
      },
      promise: deferred.promise
    }

    execFn();

    try {
      await promise.waitFor(1000, () => isDone);
    } catch (e) {
      state[stateName] = null;
      isDone = true;
      deferred.reject(e);
    }

    return deferred.promise;
  }

  onTextAreaBlur(evt) {
    setTimeout(() => {
      var {textareaNode, rootNode} = this.domState || {};
      if (rootNode && document.activeElement === rootNode)
        rootNode && rootNode.focus();
    });
  }

  onRootNodeFocus(evt) {
    var {textareaNode, rootNode} = this.domState || {};
    if (evt.target === textareaNode || evt.target === rootNode)
      this.focus();
    this.inputState.composition = null;
  }

  onInput(evt) {
    if (this.inputState.composition) return;
    if (!evt.data) evt.data = this.readValue();
    this.resetValue();
    this.eventDispatcher.dispatchDOMEvent(evt);
  }

  onCompositionStart(evt) {
    this.inputState.composition = {};
  }

  onCompositionUpdate(evt) {
    var {composition: c} = this.inputState,
        val = this.readValue();
    if (c.lastValue === val) return;
    c.lastValue = val;
  }

  onCompositionEnd(evt) {
    this.inputState.composition = null;
  }

  setPosition(pos) {
    var {textareaNode} = this.domState || {};
    if (!textareaNode) return;
    textareaNode.style.left = pos.x + "px";
    textareaNode.style.top = pos.y + "px";
  }

  ensureBeingInVisibleBoundsOfWorld(world) {
    this.setPosition(world.visibleBounds().center());
  }

  ensureBeingAtCursorOfText(textMorph) {
    // move the textarea to the text cursor

    if (!textMorph.world()) return;

    let {startRow, endRow} = textMorph.whatsVisible,
        {row, column} = textMorph.cursorPosition;
    row = Math.max(startRow, Math.min(row, endRow));
    let localCursorPos = textMorph.charBoundsFromTextPosition({row, column}).topLeft(),
        globalCursorPos = textMorph.worldPoint(localCursorPos.subPt(textMorph.scroll));

    this.setPosition(globalCursorPos);
  }
}
<|endoftext|>


<commit_before>'use strict';

angular.module('arethusa').factory('TreebankPersister', [
  'documentStore',
  'configurator',
  'navigator',
  'idHandler',
  function (documentStore, configurator, navigator, idHandler) {
    return function(conf) {
      var self = this;
      var resource = configurator.provideResource(conf.resource);
      var identifier = conf.docIdentifier;

      function updateXml() {
        doc().xml = arethusaUtil.json2xml(doc().json);
      }

      function updateWord(word, stateWord, fullMap) {
        // This whole function is horrificly ugly and could be refactored
        // to use more function calls - but this is not done on purpose.
        //
        // We want saving to be as fast as possible and avoid more calls.
        //
        // The if/else dancing is used to determine whether we should write
        // to the document or not.
        //
        // We write
        // - when a value is set in the current state
        // - when no value is set in the current state, but present in the
        //   source document (i.e.: user has unannotated on purpose
        //
        // We don't write
        // - when no value is present in state or document

        var head = stateWord.head;
        if ((head && head.id)) {
          // If the token has a head and it's not inside the full map,
          // it's the root token.
          word._head = fullMap[head.id] || 0;
        } else {
          // react against 0 values in head
          if (angular.isDefined(word._head)) word._head = '';
        }

        var relation = stateWord.relation;
        if (relation) {
          word._relation = relation.label;
        } else {
          if (word._relation) word._relation = '';
        }

        var morph = stateWord.morphology;
        if (morph) {
          if (angular.isDefined(morph.lemma))  word._lemma = morph.lemma;
          if (angular.isDefined(morph.postag)) word._postag= morph.postag;
          if (angular.isDefined(morph.gloss))  word._gloss = morph.gloss;
        } else {
          if (word._lemma || word._postag) {
            word._lemma = word._postag = '';
          }
        }

        var sg = stateWord.sg;
        if (sg) {
          word._sg = arethusaUtil.map(sg.ancestors, function(el) {
            return el.short;
          }).join(' ');
        } else {
          if (word._sg) word._sg = '';
        }

        word._form = stateWord.string;
      }

      function ArtificialNode(id, insertionId, type) {
        this._id = id;

        // We have to strip the sentence id for now, as the insertionId
        // won't make it through Perseids validation process.
        // It won't affect the import, as we're catching such cases there
        // for backwards compatibility anyway.
        //
        // Mind that this is a temporal solution and tightly couples this
        // process to the current id formatting.
        this._insertion_id = insertionId.split('-')[1];
        this._artificial = type || 'elliptic';
      }

      var lastId;
      function idCreator() {
        lastId++;
        return lastId;
      }

      function updateSentence(sentences, sentence) {
        var updated = sentences[sentence._id];

        // Check for changes - we might have nothing to do.
        if (!updated.changed) return;

        var wordsInXml = arethusaUtil.toAry(sentence.word);
        // We create a new object that holds all tokens of a sentence,
        // identified by their mappings in the original document.
        // Unmapped tokens are exposed through an array and will receive
        // mapped ids later
        lastId = wordsInXml[wordsInXml.length - 1]._id;

        var tokens = idHandler.transformToSoureIds(updated.tokens, identifier, idCreator);
        var withMappings = tokens.mapped;
        var fullMap = tokens.fullMap;
        var toDelete = [];
        angular.forEach(wordsInXml, function(word, i) {
          var stateWord = withMappings[word._id];
          if (stateWord) {
            updateWord(word, stateWord, fullMap);
          } else {
            toDelete.unshift(i); // unshift, because we want reverse order
          }
        });

        angular.forEach(toDelete, function(index, i) {
          wordsInXml.splice(index, 1);
        });

        // Usually, updatedTokens will be empty by now - it won't be
        // if artificialTokens were added during the annotation. New
        // word nodes needs to be inserted in the document now.
        angular.forEach(tokens.unmapped, function(token, i) {
          var internalId = token.id;
          var sourceId   = token.idMap.sourceId(identifier);
          var newWord = new ArtificialNode(sourceId, internalId);
          updateWord(newWord, token, fullMap);
          wordsInXml.push(newWord);
          // update the formerly unmapped token
          token.idMap.add(identifier, internalId, lastId);
        });
      }

      function updateDocument() {
        var stored = arethusaUtil.toAry(doc().json.treebank.sentence);
        // navigator has to provide means to retrieve sentences by id
        // and not only through a flat array!
        var sentences = navigator.sentencesById;

        angular.forEach(stored, function(sentence, i) {
          updateSentence(sentences, sentence);
        });
      }

      function doc() {
        return documentStore.store[identifier];
      }

      this.output = function(noFormat) {
        updateDocument();
        updateXml();
        var xml = doc().xml;
        return noFormat ? xml : aU.formatXml(xml);
      };

      this.saveData = function(callback, errCallback) {
        resource.save(self.output(true), self.mimeType).then(callback, errCallback);
      };

      this.identifier = identifier;
      this.mimeType = 'text/xml';
      this.fileType = 'xml';
    };
  }
]);

<commit_msg>Fix ellipsis problem in TreebankPersister<commit_after>'use strict';

angular.module('arethusa').factory('TreebankPersister', [
  'documentStore',
  'configurator',
  'navigator',
  'idHandler',
  function (documentStore, configurator, navigator, idHandler) {
    return function(conf) {
      var self = this;
      var resource = configurator.provideResource(conf.resource);
      var identifier = conf.docIdentifier;

      function updateXml() {
        doc().xml = arethusaUtil.json2xml(doc().json);
      }

      function updateWord(word, stateWord, fullMap) {
        // This whole function is horrificly ugly and could be refactored
        // to use more function calls - but this is not done on purpose.
        //
        // We want saving to be as fast as possible and avoid more calls.
        //
        // The if/else dancing is used to determine whether we should write
        // to the document or not.
        //
        // We write
        // - when a value is set in the current state
        // - when no value is set in the current state, but present in the
        //   source document (i.e.: user has unannotated on purpose
        //
        // We don't write
        // - when no value is present in state or document

        var head = stateWord.head;
        if ((head && head.id)) {
          // If the token has a head and it's not inside the full map,
          // it's the root token.
          word._head = fullMap[head.id] || 0;
        } else {
          // react against 0 values in head
          if (angular.isDefined(word._head)) word._head = '';
        }

        var relation = stateWord.relation;
        if (relation) {
          word._relation = relation.label;
        } else {
          if (word._relation) word._relation = '';
        }

        var morph = stateWord.morphology;
        if (morph) {
          if (angular.isDefined(morph.lemma))  word._lemma = morph.lemma;
          if (angular.isDefined(morph.postag)) word._postag= morph.postag;
          if (angular.isDefined(morph.gloss))  word._gloss = morph.gloss;
        } else {
          if (word._lemma || word._postag) {
            word._lemma = word._postag = '';
          }
        }

        var sg = stateWord.sg;
        if (sg) {
          word._sg = arethusaUtil.map(sg.ancestors, function(el) {
            return el.short;
          }).join(' ');
        } else {
          if (word._sg) word._sg = '';
        }

        word._form = stateWord.string;
      }

      function ArtificialNode(id, insertionId, type) {
        this._id = id;

        // We have to strip the sentence id for now, as the insertionId
        // won't make it through Perseids validation process.
        // It won't affect the import, as we're catching such cases there
        // for backwards compatibility anyway.
        //
        // Mind that this is a temporal solution and tightly couples this
        // process to the current id formatting.
        this._insertion_id = insertionId.split('-')[1];
        this._artificial = type || 'elliptic';
      }

      var lastId;
      function idCreator() {
        lastId++;
        return lastId;
      }

      function updateSentence(sentences, sentence) {
        var updated = sentences[sentence._id];

        // Check for changes - we might have nothing to do.
        if (!updated.changed) return;

        var wordsInXml = arethusaUtil.toAry(sentence.word);
        // We create a new object that holds all tokens of a sentence,
        // identified by their mappings in the original document.
        // Formerly unmapped tokens are exposed through an array to allow
        // postprocessing on them (such as adding artificialToken information)
        lastId = wordsInXml[wordsInXml.length - 1]._id;

        var tokens = idHandler.transformToSoureIds(updated.tokens, identifier, idCreator);
        var withMappings = tokens.mapped;
        var fullMap = tokens.fullMap;
        var toDelete = [];
        angular.forEach(wordsInXml, function(word, i) {
          var stateWord = withMappings[word._id];
          if (stateWord) {
            updateWord(word, stateWord, fullMap);
          } else {
            toDelete.unshift(i); // unshift, because we want reverse order
          }
        });

        angular.forEach(toDelete, function(index, i) {
          wordsInXml.splice(index, 1);
        });

        // tokens - the result of the id.Handler.transfomToSource call -
        // exposes all previously unmapped ids in an Array.
        // When artificialTokens were added during the last call of this function
        // and now, the unmapped Array will contain them - we have to add
        // the artificialToken information now to complete the insertion of such
        // new nodes in the XML document.
        // After they have been inserted once, they will already have their id
        // mapping, so an artificialToken can never end up in the unmapped Array
        // twice.
        angular.forEach(tokens.unmapped, function(token, i) {
          var internalId = token.id;
          var sourceId   = token.idMap.sourceId(identifier);
          var newWord = new ArtificialNode(sourceId, internalId);
          updateWord(newWord, token, fullMap);
          wordsInXml.push(newWord);
        });
        updated.changed = false;
      }

      function updateDocument() {
        var stored = arethusaUtil.toAry(doc().json.treebank.sentence);
        // navigator has to provide means to retrieve sentences by id
        // and not only through a flat array!
        var sentences = navigator.sentencesById;

        angular.forEach(stored, function(sentence, i) {
          updateSentence(sentences, sentence);
        });
      }

      function doc() {
        return documentStore.store[identifier];
      }

      this.output = function(noFormat) {
        updateDocument();
        updateXml();
        var xml = doc().xml;
        return noFormat ? xml : aU.formatXml(xml);
      };

      this.saveData = function(callback, errCallback) {
        resource.save(self.output(true), self.mimeType).then(callback, errCallback);
      };

      this.identifier = identifier;
      this.mimeType = 'text/xml';
      this.fileType = 'xml';
    };
  }
]);

<|endoftext|>


<commit_before>Application.Directives.directive("displayFileUploadEntry", displayFileUploadEntryDirective);
function displayFileUploadEntryDirective() {
    return {
        restrict: "E",
        replace: true,
        scope: {
            file: "=file"
        },
        controller: "displayFileUploadEntryDirectiveController",
        templateUrl: "application/core/projects/project/home/directives/display-file-upload-entry.html"
    };
}
Application.Controllers.controller("displayFileUploadEntryDirectiveController",
                                   ["$scope", "pubsub",
                                    displayFileUploadEntryDirectiveController]);

function displayFileUploadEntryDirectiveController($scope, pubsub) {
    $scope.removeFromUpload = function(file) {
        file.cancel();
    };
}
<commit_msg>When deleting an upload request also delete on server.<commit_after>Application.Directives.directive("displayFileUploadEntry", displayFileUploadEntryDirective);
function displayFileUploadEntryDirective() {
    return {
        restrict: "E",
        replace: true,
        scope: {
            file: "=file"
        },
        controller: "displayFileUploadEntryDirectiveController",
        templateUrl: "application/core/projects/project/home/directives/display-file-upload-entry.html"
    };
}
Application.Controllers.controller("displayFileUploadEntryDirectiveController",
                                   ["$scope", "mcapi",
                                    displayFileUploadEntryDirectiveController]);

function displayFileUploadEntryDirectiveController($scope, mcapi) {
    $scope.removeFromUpload = function(file) {
        mcapi("/upload/%", file.uniqueIdentifier).delete();
        file.cancel();
    };
}
<|endoftext|>


<commit_before>/**

What listeners work on what platforms?

Chrome - wheel or mousewheel (if wheel is missing)
Safari - mousewheel
Firefox - both wheel and domscroll

*/

var VirtualScroll = (function(document) {

	var vs = {};

	var numListeners, listeners = [], initialized = false;

	var touchStartX, touchStartY;

	vs.whichListener = [];

	var hasWheelEvent = 'onwheel' in document;
	var hasMouseWheelEvent = 'onmousewheel' in document;
	var hasTouchStart = 'ontouchstart' in document;
	var hasTouchMove = 'ontouchmove' in document;
	var hasKeyDown = 'onkeydown' in document;

	var event = {
		y: 0,
		x: 0,
		deltaX: 0,
		deltaY: 0,
		originalEvent: null
	};

	vs.addEventListener = function(f) {
		if(!initialized) initListeners();
		listeners.push(f);
		numListeners = listeners.length;
	}

	vs.removeEventListener = function(f) {
		listeners.splice(f, 1);
		numListeners = listeners.length;
		if(numListeners <= 0) destroyListeners();
	}

	var notify = function() {
		event.x += event.deltaX;
		event.y += event.deltaY;
		event.originalEvent = e;

		for(var i = 0; i < numListeners; i++) {
			listeners[i](event);
		}
	}

	var onWheel = function(e) {

		//
		if(vs.whichListener.indexOf("wheel") == -1) vs.whichListener.push("wheel");
		//

		// In Chrome and in Firefox (at least the new one)
		event.deltaX = e.wheelDeltaX || e.deltaX;
		event.deltaY = e.wheelDeltaY || e.deltaY;

		notify();
	}

	var onMouseWheel = function(e) {

		//
		if(vs.whichListener.indexOf("mousewheel") == -1) vs.whichListener.push("mousewheel");
		//

		// In Safari and in Chrome if 'wheel' isn't defined
		event.deltaX = e.wheelDeltaX;
		event.deltaY = e.wheelDeltaY;

		notify();
	}

	var onTouchStart = function(e) {

		//
		if(vs.whichListener.indexOf("touchstart") == -1) vs.whichListener.push("touchstart");
		//

		// e.preventDefault();
		var t0 = e.targetTouches[0];
		touchStartX = t0.pageX;
		touchStartY = t0.pageY;
	}

	var onTouchMove = function(e) {

		//
		if(vs.whichListener.indexOf("touchmove") == -1) vs.whichListener.push("touchmove");
		//

		e.preventDefault();
		var t0 = e.targetTouches[0];

		event.deltaX = t0.pageX - touchStartX;
		event.deltaY = t0.pageY - touchStartY;

		touchStartX = t0.pageX;
		touchStartY = t0.pageY;

		notify();
	}

	var initListeners = function() {
		if(hasWheelEvent) document.addEventListener("wheel", onWheel);
		if(hasMouseWheelEvent) document.addEventListener("mousewheel", onMouseWheel);
		if(hasTouchStart) document.addEventListener("touchstart", onTouchStart);
		if(hasTouchMove) document.addEventListener("touchmove", onTouchMove);
		initialized = true;
	}

	var destroyListeners = function() {
		if(hasWheelEvent) document.removeEventListener("wheel", onWheel);
		if(hasMouseWheelEvent) document.removeEventListener("mousewheel", onMouseWheel);
		if(hasTouchStart) document.removeEventListener("touchstart", onTouchStart);
		if(hasTouchMove) document.removeEventListener("touchmove", onTouchMove);
		initialized = false;
	}

	return vs;
})(document);

<commit_msg>Working on VirtualScroll test<commit_after>/**

What listeners work on what platforms?

Chrome - wheel or mousewheel (if wheel is missing)
Safari - mousewheel
Firefox - both wheel and domscroll

*/

var VirtualScroll = (function(document) {

	var vs = {};

	var numListeners, listeners = [], initialized = false;

	var touchStartX, touchStartY;

	vs.whichListener = [];

	var hasWheelEvent = 'onwheel' in document;
	var hasMouseWheelEvent = 'onmousewheel' in document;
	var hasTouchStart = 'ontouchstart' in document;
	var hasTouchMove = 'ontouchmove' in document;
	var hasKeyDown = 'onkeydown' in document;

	var event = {
		y: 0,
		x: 0,
		deltaX: 0,
		deltaY: 0,
		originalEvent: null
	};

	vs.addEventListener = function(f) {
		if(!initialized) initListeners();
		listeners.push(f);
		numListeners = listeners.length;
	}

	vs.removeEventListener = function(f) {
		listeners.splice(f, 1);
		numListeners = listeners.length;
		if(numListeners <= 0) destroyListeners();
	}

	var notify = function(e) {
		event.x += event.deltaX;
		event.y += event.deltaY;
		event.originalEvent = e;

		for(var i = 0; i < numListeners; i++) {
			listeners[i](event);
		}
	}

	var onWheel = function(e) {

		//
		if(vs.whichListener.indexOf("wheel") == -1) vs.whichListener.push("wheel");
		//

		// In Chrome, IE and in Firefox (at least the new one)
		event.deltaX = e.wheelDeltaX || e.deltaX;
		event.deltaY = e.wheelDeltaY || e.deltaY;

		notify(e);
	}

	var onMouseWheel = function(e) {

		//
		if(vs.whichListener.indexOf("mousewheel") == -1) vs.whichListener.push("mousewheel");
		//

		// In Safari and in Chrome if 'wheel' isn't defined
		event.deltaX = e.wheelDeltaX;
		event.deltaY = e.wheelDeltaY;

		notify(e);
	}

	var onTouchStart = function(e) {

		//
		if(vs.whichListener.indexOf("touchstart") == -1) vs.whichListener.push("touchstart");
		//

		// e.preventDefault();
		var t0 = e.targetTouches[0];
		touchStartX = t0.pageX;
		touchStartY = t0.pageY;
	}

	var onTouchMove = function(e) {

		//
		if(vs.whichListener.indexOf("touchmove") == -1) vs.whichListener.push("touchmove");
		//

		e.preventDefault();
		var t0 = e.targetTouches[0];

		event.deltaX = t0.pageX - touchStartX;
		event.deltaY = t0.pageY - touchStartY;

		touchStartX = t0.pageX;
		touchStartY = t0.pageY;

		notify(e);
	}

	var initListeners = function() {
		if(hasWheelEvent) document.addEventListener("wheel", onWheel);
		if(hasMouseWheelEvent) document.addEventListener("mousewheel", onMouseWheel);
		if(hasTouchStart) document.addEventListener("touchstart", onTouchStart);
		if(hasTouchMove) document.addEventListener("touchmove", onTouchMove);
		initialized = true;
	}

	var destroyListeners = function() {
		if(hasWheelEvent) document.removeEventListener("wheel", onWheel);
		if(hasMouseWheelEvent) document.removeEventListener("mousewheel", onMouseWheel);
		if(hasTouchStart) document.removeEventListener("touchstart", onTouchStart);
		if(hasTouchMove) document.removeEventListener("touchmove", onTouchMove);
		initialized = false;
	}

	return vs;
})(document);

<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';

import { UP, DOWN, LEFT, RIGHT } from '../constants/keyCodes';
import getField from '../utils/getField';
import minMaxLoop from '../utils/NumberUtils/minMaxLoop';
import controlled from '../utils/PropTypes/controlled';
import SelectionControl from './SelectionControl';

/**
 * A custom PropTypes validator to make sure that each `control` in the `controls` prop
 * contains the given `propName`, or the `SelectionControlGroup` has defined that prop.
 */
function requiredByAllControls(validator) {
  return function validate(props, propName, component, ...others) {
    let err = validator(props, propName, component, ...others);

    if (!err && typeof props[propName] === 'undefined') {
      const invalids = props.controls.filter(c => !c[propName]).map((_, i) => i);
      if (invalids.length) {
        const invalidPrefix = invalids.length === props.controls.length
          ? 'All `controls`'
          : `The \`controls\` at indexes \`${invalids.join('`, `')}\``;
        const invalidMsg = `${invalidPrefix} are missing the \`${propName}\` prop.`;

        err = new Error(
          `The \`${propName}\` prop is required to make \`${component}\` accessible for users of ` +
          `assistive technologies such as screen readers. Either add the \`${propName}\` to the \`${component}\` ` +
          `or add the \`${propName}\` to each \`control\` in the \`controls\` prop. ${invalidMsg}`
        );
      }
    }

    return err;
  };
}

/**
 * The `SelectionControlGroup` component is used to simplify the generation of a list
 * of `SelectionControl`. Any common props are extracted to this component and passed
 * to the `SelectionControl`.
 */
export default class SelectionControlGroup extends PureComponent {
  static propTypes = {
    /**
     * An optional style to apply to the container.
     */
    style: PropTypes.object,

    /**
     * An optional className to apply to the container.
     */
    className: PropTypes.string,

    /**
     * An optional style to apply to every `SelectionControl`. This will be merged with any `style`
     * that a `control` might have.
     */
    controlStyle: PropTypes.object,

    /**
     * An optional className to apply to every `SelectionControl`. This will be merged with any
     * `className` that a `control` might have.
     */
    controlClassName: PropTypes.string,

    /**
     * An optional base id to apply to each `SelectionControl`. When this is included, the id for
     * each control will start with this and end with their current index. If this is omitted,
     * each `control` in the `controls` prop *must* have an `id` prop. This is required for
     * accessibility.
     */
    id: requiredByAllControls(PropTypes.oneOfType([
      PropTypes.number,
      PropTypes.string,
    ])),

    /**
     * The type to apply to each `SelectionControl` in the group. Only `checkbox` and `radio` is
     * valid for a grouping.
     */
    type: PropTypes.oneOf(['checkbox', 'radio']).isRequired,

    /**
     * The component to render the `SelectionControlGroup` in. This can only be a valid dom element
     * since it relies on the ref callback to add keyboard accessibility.
     */
    component: PropTypes.string.isRequired,

    /**
     * An optional label to display above the group of `SelectionControl`s.
     */
    label: PropTypes.node,

    /**
     * An optional className to apply to the node surrounding the `label` prop.
     */
    labelClassName: PropTypes.string,

    /**
     * The component to render the `SelectionControl` in
     */
    selectionControl: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.func,
    ]).isRequired,

    /**
     * The component to render the optional `label` in.
     */
    labelComponent: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.func,
    ]).isRequired,

    /**
     * An optional function to call when any `SelectionControl`'s `checked` state is changed
     * in the group. If the `type` of the group is `radio`, the `value` of the `checked` radio
     * will be given in the callback. If the `type` of the group is `checkbox`, a comma-delimited
     * string of all `checked` checkboxes values will be given.
     *
     * ```js
     * // checkbox
     * onChange('Alpha,Omega', changeEvent);
     *
     * // radio
     * onChange('Omega', changeEvent);
     * ```
     */
    onChange: PropTypes.func,

    /**
     * A name to use for each `SelectionControl` in the group. If the `type` of the group is
     * `checkbox`, the name will be updated to be an array name so that using
     * `document.querySelector('input[name="yourName[]"].value` will give the comma-delimited
     * string of checked checkboxes.
     *
     * It is either required to have this prop set or every `control` in the `controls` prop to
     * have the `name` prop.
     */
    name: requiredByAllControls(PropTypes.oneOfType([
      PropTypes.number,
      PropTypes.string,
    ])),

    /**
     * The default value for the `SelectionControlGroup`. This can either be a single value
     * or a comma-delimited string of checkbox values. When the `type` of the group is `radio`
     * and the group is uncontrolled, it is recommended to set this prop. Otherwise the first
     * value of the `controls` prop will be used as the default value.
     */
    defaultValue: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.number,
      PropTypes.string,
    ]),

    /**
     * An optional value to use for the `SelectionControlGroup`. This will make the component
     * controlled and require the `onChange` prop to be defined. Like the `defaultValue`, this
     * can either be a single value or a comma-delimited list of checkbox values.
     */
    value: controlled(PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.number,
      PropTypes.string,
    ]), 'onChange'),

    /**
     * A list of objects to create the `SelectionControl` components. The shape of the object
     * is the `propTypes` of the `SelectionControl` component, except that `value` prop is
     * now required.
     *
     * The `SelectionControl` will inherit any inheritable props from the `SelectionControlGroup`.
     */
    controls: PropTypes.arrayOf(PropTypes.shape({
      key: PropTypes.oneOfType([
        PropTypes.number,
        PropTypes.string,
      ]),
      label: PropTypes.node.isRequired,
      value: PropTypes.oneOfType([
        PropTypes.bool,
        PropTypes.number,
        PropTypes.string,
      ]).isRequired,
    })).isRequired,

    /**
     * Boolean if the `SelectionControl` should be displayed inline.
     */
    inline: PropTypes.bool,

    /**
     * Boolean if all the selection controls in the group are disabled.
     */
    disabled: PropTypes.bool,

    /**
     * An optional function to call when the keydown event is triggered.
     */
    onKeyDown: PropTypes.func,

    /**
     * An icon to use for a checked `radio` control. This will be applied to each
     * control that has a `type="radio"` in the `controls` list. This is just a simpler
     * way of applying the custom icons for each radio in the list.
     *
     * This will default to the `checkedRadioIcon` on the `SelectionControl`.
     *
     * @see {@link #controls}
     * @see {@link #uncheckedRadioIcon}
     * @see {@link SelectionControls/SelectionControls#checkedRadioIcon}
     * @see {@link SelectionControls/SelectionControls#uncheckedRadioIcon}
     */
    checkedRadioIcon: PropTypes.node,

    /**
     * An icon to use for an unchecked `radio` control. This will be applied to each
     * control that has a `type="radio"` in the `controls` list. This is just a simpler
     * way of applying the custom icons for each radio in the list.
     *
     * This will default to the `uncheckedRadioIcon` on the `SelectionControl`.
     *
     * @see {@link #controls}
     * @see {@link #checkedRadioIcon}
     * @see {@link SelectionControls/SelectionControls#checkedRadioIcon}
     * @see {@link SelectionControls/SelectionControls#uncheckedRadioIcon}
     */
    uncheckedRadioIcon: PropTypes.node,

    /**
     * An icon to use for a checked `checkbox` control. This will be applied to each
     * control that has a `type="checkbox"` in the `controls` list. This is just a simpler
     * way of applying the custom icons for each checkbox in the list.
     *
     * This will default to the `checkedCheckboxIcon` on the `SelectionControl`.
     *
     * @see {@link #controls}
     * @see {@link #uncheckedCheckboxIcon}
     * @see {@link SelectionControls/SelectionControls#checkedCheckboxIcon}
     * @see {@link SelectionControls/SelectionControls#uncheckedCheckboxIcon}
     */
    checkedCheckboxIcon: PropTypes.node,

    /**
     * An icon to use for an unchecked `checkbox` control. This will be applied to each
     * control that has a `type="checkbox"` in the `controls` list. This is just a simpler
     * way of applying the custom icons for each checkbox in the list.
     *
     * This will default to the `uncheckedCheckboxIcon` on the `SelectionControl`.
     *
     * @see {@link #controls}
     * @see {@link #checkedCheckboxIcon}
     * @see {@link SelectionControls/SelectionControls#checkedCheckboxIcon}
     * @see {@link SelectionControls/SelectionControls#uncheckedCheckboxIcon}
     */
    uncheckedCheckboxIcon: PropTypes.node,
  };

  static defaultProps = {
    component: 'fieldset',
    labelComponent: 'legend',
    labelClassName: 'md-subheading-1',
    selectionControl: SelectionControl
  };

  constructor(props) {
    super(props);

    const radio = props.type === 'radio';
    this.state = {};

    if (typeof props.value === 'undefined') {
      let value = props.defaultValue;

      if (typeof value === 'undefined') {
        value = radio ? props.controls[0].value : '';
      }

      this.state.value = value;
    }

    const groupValue = getField(props, this.state, 'value');
    this._activeIndex = -1;
    props.controls.some(({ value }, i) => {
      if (value === groupValue) {
        this._activeIndex = i;
      }

      return this._activeIndex > -1;
    });
  }

  _isChecked(value, controlValue, type) {
    return type === 'radio'
      ? value === controlValue
      : value.split(',').indexOf(controlValue) !== -1;
  }

  _setGroup = (group) => {
    this._group = group;
  };

  _handleChange = (e) => {
    let value = e.target.value;
    if (this.props.type === 'checkbox') {
      const { checked } = e.target;

      const values = getField(this.props, this.state, 'value').split(',');
      const index = values.indexOf(value);
      if (checked) {
        values.push(value);
      } else {
        values.splice(index, 1);
      }

      value = values.join(',');
    }

    if (this.props.onChange) {
      this.props.onChange(value, e);
    }

    if (typeof this.props.value === 'undefined') {
      this.setState({ value });
    }
  };

  _handleKeyDown = (e) => {
    if (this.props.onKeyDown) {
      this.props.onKeyDown(e);
    }

    const key = e.which || e.keyCode;
    const dec = key === UP || key === LEFT;
    const inc = key === DOWN || key === RIGHT;
    if (!this._group || (!dec && !inc)) {
      return;
    }

    e.preventDefault();
    const radios = this._group.querySelectorAll('*[role="radio"]');
    this._activeIndex = minMaxLoop(this._activeIndex, 0, radios.length - 1, inc);
    radios[this._activeIndex].focus();
    const { value } = this.props.controls[this._activeIndex];
    if (getField(this.props, this.state, 'value') !== value) {
      if (this.props.onChange) {
        this.props.onChange(value, e);
      }

      if (typeof this.props.value === 'undefined') {
        this.setState({ value });
      }
    }
  };

  render() {
    const {
      id,
      name,
      type,
      label,
      labelClassName,
      className,
      controlStyle,
      controlClassName,
      component: Component,
      labelComponent: LabelComponent,
      inline,
      disabled,
      checkedRadioIcon,
      uncheckedRadioIcon,
      checkedCheckboxIcon,
      uncheckedCheckboxIcon,
      selectionControl,
      /* eslint-disable no-unused-vars */
      value: propValue,
      controls: propControls,
      defaultValue,
      /* eslint-enable no-unused-vars */
      ...props
    } = this.props;

    const value = getField(this.props, this.state, 'value');
    const radio = type === 'radio';

    const controls = this.props.controls.map((control, i) => {
      let style = control.style;
      if (controlStyle) {
        style = style ? { ...controlStyle, ...style } : controlStyle;
      }

      const checked = this._isChecked(value, control.value, type);
      const controlProps = {
        id: `${id}${i}`,
        key: `control${i}`,
        name: `${name}${type === 'checkbox' ? '[]' : ''}`,
        type,
        inline,
        disabled,
        checked,
        tabIndex: !radio || checked || (i === 0 && this._activeIndex === -1) ? undefined : -1,
        checkedRadioIcon,
        uncheckedRadioIcon,
        checkedCheckboxIcon,
        uncheckedCheckboxIcon,
        ...control,
        style,
        className: cn(controlClassName, control.className),
      };

      return React.createElement(selectionControl, controlProps);
    });

    let ariaLabel;
    if (label) {
      ariaLabel = <LabelComponent className={labelClassName}>{label}</LabelComponent>;
    }

    return (
      <Component
        {...props}
        ref={this._setGroup}
        className={cn('md-selection-control-group', className)}
        onChange={this._handleChange}
        onKeyDown={radio ? this._handleKeyDown : null}
      >
        {ariaLabel}
        {controls}
      </Component>
    );
  }
}
<commit_msg>Fixed code style<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';

import { UP, DOWN, LEFT, RIGHT } from '../constants/keyCodes';
import getField from '../utils/getField';
import minMaxLoop from '../utils/NumberUtils/minMaxLoop';
import controlled from '../utils/PropTypes/controlled';
import SelectionControl from './SelectionControl';

/**
 * A custom PropTypes validator to make sure that each `control` in the `controls` prop
 * contains the given `propName`, or the `SelectionControlGroup` has defined that prop.
 */
function requiredByAllControls(validator) {
  return function validate(props, propName, component, ...others) {
    let err = validator(props, propName, component, ...others);

    if (!err && typeof props[propName] === 'undefined') {
      const invalids = props.controls.filter(c => !c[propName]).map((_, i) => i);
      if (invalids.length) {
        const invalidPrefix = invalids.length === props.controls.length
          ? 'All `controls`'
          : `The \`controls\` at indexes \`${invalids.join('`, `')}\``;
        const invalidMsg = `${invalidPrefix} are missing the \`${propName}\` prop.`;

        err = new Error(
          `The \`${propName}\` prop is required to make \`${component}\` accessible for users of ` +
          `assistive technologies such as screen readers. Either add the \`${propName}\` to the \`${component}\` ` +
          `or add the \`${propName}\` to each \`control\` in the \`controls\` prop. ${invalidMsg}`
        );
      }
    }

    return err;
  };
}

/**
 * The `SelectionControlGroup` component is used to simplify the generation of a list
 * of `SelectionControl`. Any common props are extracted to this component and passed
 * to the `SelectionControl`.
 */
export default class SelectionControlGroup extends PureComponent {
  static propTypes = {
    /**
     * An optional style to apply to the container.
     */
    style: PropTypes.object,

    /**
     * An optional className to apply to the container.
     */
    className: PropTypes.string,

    /**
     * An optional style to apply to every `SelectionControl`. This will be merged with any `style`
     * that a `control` might have.
     */
    controlStyle: PropTypes.object,

    /**
     * An optional className to apply to every `SelectionControl`. This will be merged with any
     * `className` that a `control` might have.
     */
    controlClassName: PropTypes.string,

    /**
     * An optional base id to apply to each `SelectionControl`. When this is included, the id for
     * each control will start with this and end with their current index. If this is omitted,
     * each `control` in the `controls` prop *must* have an `id` prop. This is required for
     * accessibility.
     */
    id: requiredByAllControls(PropTypes.oneOfType([
      PropTypes.number,
      PropTypes.string,
    ])),

    /**
     * The type to apply to each `SelectionControl` in the group. Only `checkbox` and `radio` is
     * valid for a grouping.
     */
    type: PropTypes.oneOf(['checkbox', 'radio']).isRequired,

    /**
     * The component to render the `SelectionControlGroup` in. This can only be a valid dom element
     * since it relies on the ref callback to add keyboard accessibility.
     */
    component: PropTypes.string.isRequired,

    /**
     * An optional label to display above the group of `SelectionControl`s.
     */
    label: PropTypes.node,

    /**
     * An optional className to apply to the node surrounding the `label` prop.
     */
    labelClassName: PropTypes.string,

    /**
     * The component to render the `SelectionControl` in
     */
    selectionControl: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.func,
    ]).isRequired,

    /**
     * The component to render the optional `label` in.
     */
    labelComponent: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.func,
    ]).isRequired,

    /**
     * An optional function to call when any `SelectionControl`'s `checked` state is changed
     * in the group. If the `type` of the group is `radio`, the `value` of the `checked` radio
     * will be given in the callback. If the `type` of the group is `checkbox`, a comma-delimited
     * string of all `checked` checkboxes values will be given.
     *
     * ```js
     * // checkbox
     * onChange('Alpha,Omega', changeEvent);
     *
     * // radio
     * onChange('Omega', changeEvent);
     * ```
     */
    onChange: PropTypes.func,

    /**
     * A name to use for each `SelectionControl` in the group. If the `type` of the group is
     * `checkbox`, the name will be updated to be an array name so that using
     * `document.querySelector('input[name="yourName[]"].value` will give the comma-delimited
     * string of checked checkboxes.
     *
     * It is either required to have this prop set or every `control` in the `controls` prop to
     * have the `name` prop.
     */
    name: requiredByAllControls(PropTypes.oneOfType([
      PropTypes.number,
      PropTypes.string,
    ])),

    /**
     * The default value for the `SelectionControlGroup`. This can either be a single value
     * or a comma-delimited string of checkbox values. When the `type` of the group is `radio`
     * and the group is uncontrolled, it is recommended to set this prop. Otherwise the first
     * value of the `controls` prop will be used as the default value.
     */
    defaultValue: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.number,
      PropTypes.string,
    ]),

    /**
     * An optional value to use for the `SelectionControlGroup`. This will make the component
     * controlled and require the `onChange` prop to be defined. Like the `defaultValue`, this
     * can either be a single value or a comma-delimited list of checkbox values.
     */
    value: controlled(PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.number,
      PropTypes.string,
    ]), 'onChange'),

    /**
     * A list of objects to create the `SelectionControl` components. The shape of the object
     * is the `propTypes` of the `SelectionControl` component, except that `value` prop is
     * now required.
     *
     * The `SelectionControl` will inherit any inheritable props from the `SelectionControlGroup`.
     */
    controls: PropTypes.arrayOf(PropTypes.shape({
      key: PropTypes.oneOfType([
        PropTypes.number,
        PropTypes.string,
      ]),
      label: PropTypes.node.isRequired,
      value: PropTypes.oneOfType([
        PropTypes.bool,
        PropTypes.number,
        PropTypes.string,
      ]).isRequired,
    })).isRequired,

    /**
     * Boolean if the `SelectionControl` should be displayed inline.
     */
    inline: PropTypes.bool,

    /**
     * Boolean if all the selection controls in the group are disabled.
     */
    disabled: PropTypes.bool,

    /**
     * An optional function to call when the keydown event is triggered.
     */
    onKeyDown: PropTypes.func,

    /**
     * An icon to use for a checked `radio` control. This will be applied to each
     * control that has a `type="radio"` in the `controls` list. This is just a simpler
     * way of applying the custom icons for each radio in the list.
     *
     * This will default to the `checkedRadioIcon` on the `SelectionControl`.
     *
     * @see {@link #controls}
     * @see {@link #uncheckedRadioIcon}
     * @see {@link SelectionControls/SelectionControls#checkedRadioIcon}
     * @see {@link SelectionControls/SelectionControls#uncheckedRadioIcon}
     */
    checkedRadioIcon: PropTypes.node,

    /**
     * An icon to use for an unchecked `radio` control. This will be applied to each
     * control that has a `type="radio"` in the `controls` list. This is just a simpler
     * way of applying the custom icons for each radio in the list.
     *
     * This will default to the `uncheckedRadioIcon` on the `SelectionControl`.
     *
     * @see {@link #controls}
     * @see {@link #checkedRadioIcon}
     * @see {@link SelectionControls/SelectionControls#checkedRadioIcon}
     * @see {@link SelectionControls/SelectionControls#uncheckedRadioIcon}
     */
    uncheckedRadioIcon: PropTypes.node,

    /**
     * An icon to use for a checked `checkbox` control. This will be applied to each
     * control that has a `type="checkbox"` in the `controls` list. This is just a simpler
     * way of applying the custom icons for each checkbox in the list.
     *
     * This will default to the `checkedCheckboxIcon` on the `SelectionControl`.
     *
     * @see {@link #controls}
     * @see {@link #uncheckedCheckboxIcon}
     * @see {@link SelectionControls/SelectionControls#checkedCheckboxIcon}
     * @see {@link SelectionControls/SelectionControls#uncheckedCheckboxIcon}
     */
    checkedCheckboxIcon: PropTypes.node,

    /**
     * An icon to use for an unchecked `checkbox` control. This will be applied to each
     * control that has a `type="checkbox"` in the `controls` list. This is just a simpler
     * way of applying the custom icons for each checkbox in the list.
     *
     * This will default to the `uncheckedCheckboxIcon` on the `SelectionControl`.
     *
     * @see {@link #controls}
     * @see {@link #checkedCheckboxIcon}
     * @see {@link SelectionControls/SelectionControls#checkedCheckboxIcon}
     * @see {@link SelectionControls/SelectionControls#uncheckedCheckboxIcon}
     */
    uncheckedCheckboxIcon: PropTypes.node,
  };

  static defaultProps = {
    component: 'fieldset',
    labelComponent: 'legend',
    labelClassName: 'md-subheading-1',
    selectionControl: SelectionControl,
  };

  constructor(props) {
    super(props);

    const radio = props.type === 'radio';
    this.state = {};

    if (typeof props.value === 'undefined') {
      let value = props.defaultValue;

      if (typeof value === 'undefined') {
        value = radio ? props.controls[0].value : '';
      }

      this.state.value = value;
    }

    const groupValue = getField(props, this.state, 'value');
    this._activeIndex = -1;
    props.controls.some(({ value }, i) => {
      if (value === groupValue) {
        this._activeIndex = i;
      }

      return this._activeIndex > -1;
    });
  }

  _isChecked(value, controlValue, type) {
    return type === 'radio'
      ? value === controlValue
      : value.split(',').indexOf(controlValue) !== -1;
  }

  _setGroup = (group) => {
    this._group = group;
  };

  _handleChange = (e) => {
    let value = e.target.value;
    if (this.props.type === 'checkbox') {
      const { checked } = e.target;

      const values = getField(this.props, this.state, 'value').split(',');
      const index = values.indexOf(value);
      if (checked) {
        values.push(value);
      } else {
        values.splice(index, 1);
      }

      value = values.join(',');
    }

    if (this.props.onChange) {
      this.props.onChange(value, e);
    }

    if (typeof this.props.value === 'undefined') {
      this.setState({ value });
    }
  };

  _handleKeyDown = (e) => {
    if (this.props.onKeyDown) {
      this.props.onKeyDown(e);
    }

    const key = e.which || e.keyCode;
    const dec = key === UP || key === LEFT;
    const inc = key === DOWN || key === RIGHT;
    if (!this._group || (!dec && !inc)) {
      return;
    }

    e.preventDefault();
    const radios = this._group.querySelectorAll('*[role="radio"]');
    this._activeIndex = minMaxLoop(this._activeIndex, 0, radios.length - 1, inc);
    radios[this._activeIndex].focus();
    const { value } = this.props.controls[this._activeIndex];
    if (getField(this.props, this.state, 'value') !== value) {
      if (this.props.onChange) {
        this.props.onChange(value, e);
      }

      if (typeof this.props.value === 'undefined') {
        this.setState({ value });
      }
    }
  };

  render() {
    const {
      id,
      name,
      type,
      label,
      labelClassName,
      className,
      controlStyle,
      controlClassName,
      component: Component,
      labelComponent: LabelComponent,
      inline,
      disabled,
      checkedRadioIcon,
      uncheckedRadioIcon,
      checkedCheckboxIcon,
      uncheckedCheckboxIcon,
      selectionControl,
      /* eslint-disable no-unused-vars */
      value: propValue,
      controls: propControls,
      defaultValue,
      /* eslint-enable no-unused-vars */
      ...props
    } = this.props;

    const value = getField(this.props, this.state, 'value');
    const radio = type === 'radio';

    const controls = this.props.controls.map((control, i) => {
      let style = control.style;
      if (controlStyle) {
        style = style ? { ...controlStyle, ...style } : controlStyle;
      }

      const checked = this._isChecked(value, control.value, type);
      const controlProps = {
        id: `${id}${i}`,
        key: `control${i}`,
        name: `${name}${type === 'checkbox' ? '[]' : ''}`,
        type,
        inline,
        disabled,
        checked,
        tabIndex: !radio || checked || (i === 0 && this._activeIndex === -1) ? undefined : -1,
        checkedRadioIcon,
        uncheckedRadioIcon,
        checkedCheckboxIcon,
        uncheckedCheckboxIcon,
        ...control,
        style,
        className: cn(controlClassName, control.className),
      };

      return React.createElement(selectionControl, controlProps);
    });

    let ariaLabel;
    if (label) {
      ariaLabel = <LabelComponent className={labelClassName}>{label}</LabelComponent>;
    }

    return (
      <Component
        {...props}
        ref={this._setGroup}
        className={cn('md-selection-control-group', className)}
        onChange={this._handleChange}
        onKeyDown={radio ? this._handleKeyDown : null}
      >
        {ariaLabel}
        {controls}
      </Component>
    );
  }
}
<|endoftext|>


<commit_before>$.tooltipster.plugin({
	name: 'tooltipster.SVG',
	core: {
		_init: function() {

			$.tooltipster.on('init', function(event) {

				var window = $.tooltipster.env.window,
					$origin = $(event.origin);

				if (	window.SVGElement
					&&	event.origin instanceof window.SVGElement
				) {

					// jQuery < v3.0's addClass and hasClass do not work on SVG elements.
					// However, $('.tooltipstered') does find elements having the class.
					if (!$origin.hasClass('tooltipstered')) {

						var c = $origin.attr('class') || '';

						if (c.indexOf('tooltipstered') == -1) {
							$origin.attr('class', c + ' tooltipstered');
						}
					}

					// auto-activation of the plugin on the instance
					if ($.inArray('tooltipster.SVG', event.instance.option('plugins')) === -1) {
						event.instance._plugin('tooltipster.SVG');
					}

					// if there is no content yet, let's look for a <title> child element
					if (event.instance.content() === null) {

						// TODO: when there are several <title> tags (not supported in
						// today's browsers yet though, still an RFC drat), pick the right
						// one based on the "lang" attribute
						var content = $origin.find('title').text();
						event.instance.content(content);
					}

					// rectify the geometry if SVG.js and its screenBBox plugin have been included
					event.instance
						._on('geometry', function(event) {

							// SVG coordinates may need fixing but we need svg.screenbox.js
							// to provide it. SVGElement is IE8+
							if (window.SVG.svgjs) {

								if (!window.SVG.parser) {
									window.SVG.prepare();
								}

								var svgEl = window.SVG.adopt(event.origin);

								// not all figures need (and have) screenBBox
								if (svgEl && svgEl.screenBBox) {

									var bbox = svgEl.screenBBox();

									event.edit({
										height: bbox.height,
										left: bbox.x,
										top: bbox.y,
										width: bbox.width
									});
								}
							}
						})
						// if jQuery < v3.0, we have to remove the class ourselves
						._on('destroyed', function() {

							if (!$origin.hasClass('tooltipstered')) {
								var c = $origin.attr('class').replace('tooltipstered', '');
								$origin.attr('class', c);
							}
						});
				}
			});
		}
	}
});

/* a build task will add "return $;" here */
<commit_msg>if there is no svg title either<commit_after>$.tooltipster.plugin({
	name: 'tooltipster.SVG',
	core: {
		_init: function() {

			$.tooltipster.on('init', function(event) {

				var window = $.tooltipster.env.window,
					$origin = $(event.origin);

				if (	window.SVGElement
					&&	event.origin instanceof window.SVGElement
				) {

					// jQuery < v3.0's addClass and hasClass do not work on SVG elements.
					// However, $('.tooltipstered') does find elements having the class.
					if (!$origin.hasClass('tooltipstered')) {

						var c = $origin.attr('class') || '';

						if (c.indexOf('tooltipstered') == -1) {
							$origin.attr('class', c + ' tooltipstered');
						}
					}

					// auto-activation of the plugin on the instance
					if ($.inArray('tooltipster.SVG', event.instance.option('plugins')) === -1) {
						event.instance._plugin('tooltipster.SVG');
					}

					// if there is no content yet, let's look for a <title> child element
					if (event.instance.content() === null) {

						// TODO: when there are several <title> tags (not supported in
						// today's browsers yet though, still an RFC draft), pick the right
						// one based on its "lang" attribute
						var $title = $origin.find('title');

						if ($title[0]) {
							event.instance.content($title.text());
						}
					}

					// rectify the geometry if SVG.js and its screenBBox plugin have been included
					event.instance
						._on('geometry', function(event) {

							// SVG coordinates may need fixing but we need svg.screenbox.js
							// to provide it. SVGElement is IE8+
							if (window.SVG.svgjs) {

								if (!window.SVG.parser) {
									window.SVG.prepare();
								}

								var svgEl = window.SVG.adopt(event.origin);

								// not all figures need (and have) screenBBox
								if (svgEl && svgEl.screenBBox) {

									var bbox = svgEl.screenBBox();

									event.edit({
										height: bbox.height,
										left: bbox.x,
										top: bbox.y,
										width: bbox.width
									});
								}
							}
						})
						// if jQuery < v3.0, we have to remove the class ourselves
						._on('destroyed', function() {

							if (!$origin.hasClass('tooltipstered')) {
								var c = $origin.attr('class').replace('tooltipstered', '');
								$origin.attr('class', c);
							}
						});
				}
			});
		}
	}
});

/* a build task will add "return $;" here */
<|endoftext|>


<commit_before>var axsPath = require.resolve('../vendor/axs_testing')

exports.addCommand = function (client, requireName) {
  client.addCommand('auditAccessibility', function () {
    return this.execute(function (axsPath, requireName) {
      var axs = window[requireName](axsPath)
      var failures = axs.Audit.run().filter(function (result) {
        return result.result === 'FAIL'
      })

      if (failures.length > 0) {
        var message = 'Accessibilty audit failed\n\n'
        message += failures.map(function (result) {
          return axs.Audit.accessibilityErrorMessage(result)
        }).join('\n\n')

        return {
          message: message,
          failed: true,
          results: failures.map(function (result) {
            return {
              code: result.rule.code,
              elements: result.elements.map(function (element) {
                return axs.utils.getQuerySelectorText(element)
              }),
              message: result.rule.heading,
              severity: result.rule.severity,
              url: result.rule.url
            }
          })
        }
      } else {
        return {
          message: 'Accessibilty audit passed',
          results: [],
          failed: false
        }
      }
    }, axsPath, requireName).then(function (response) {
      return response.value
    })
  })
}
<commit_msg>Enable console api and remove warning<commit_after>var axsPath = require.resolve('../vendor/axs_testing')

exports.addCommand = function (client, requireName) {
  client.addCommand('auditAccessibility', function () {
    return this.execute(function (axsPath, requireName) {
      var axs = window[requireName](axsPath)
      var config = {
        withConsoleApi: true,
        showUnsupportedRulesWarning: false
      }
      var failures = axs.Audit.run().filter(function (result) {
        return result.result === 'FAIL'
      })

      if (failures.length > 0) {
        var message = 'Accessibilty audit failed\n\n'
        message += failures.map(function (result) {
          return axs.Audit.accessibilityErrorMessage(result)
        }).join('\n\n')

        return {
          message: message,
          failed: true,
          results: failures.map(function (result) {
            return {
              code: result.rule.code,
              elements: result.elements.map(function (element) {
                return axs.utils.getQuerySelectorText(element)
              }),
              message: result.rule.heading,
              severity: result.rule.severity,
              url: result.rule.url
            }
          })
        }
      } else {
        return {
          message: 'Accessibilty audit passed',
          results: [],
          failed: false
        }
      }
    }, axsPath, requireName).then(function (response) {
      return response.value
    })
  })
}
<|endoftext|>