<commit_before>/*!
Trivial Components (https://github.com/trivial-components/trivial-components)

Copyright 2016 <NAME> (https://github.com/yamass) and other contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import * as $ from "jquery";
import {DEFAULT_TEMPLATES, defaultListQueryFunctionFactory, EditingMode, HighlightDirection, QueryFunction, TrivialComponent, keyCodes, DEFAULT_RENDERING_FUNCTIONS} from "./TrivialCore";
import {TrivialEvent} from "./TrivialEvent";
import {place} from "place-to";
import {TrivialTreeBox, TrivialTreeBoxConfig} from "./TrivialTreeBox";

export interface TrivialUnitBoxConfig<U> extends TrivialTreeBoxConfig<U> {
    unitValueProperty?: string,
    unitIdProperty?: string,
    decimalPrecision?: number,
    decimalSeparator?: string,
    thousandsSeparator?: string,
    unitDisplayPosition?: 'left' | 'right',
    allowNullAmount?: boolean,
    selectedEntryRenderingFunction?: (entry: U) => string,
    amount?: number,
    noEntriesTemplate?: string,
    queryFunction?: QueryFunction<U>,
    queryOnNonNumberCharacters?: boolean,
    openDropdownOnEditorClick?: boolean,
    showTrigger?: boolean,
    editingMode?: EditingMode,
    
    /**
     * HTML string defining the spinner to be displayed while entries are being retrieved.
     */
    spinnerTemplate?: string
}

export type TrivialUnitBoxChangeEvent<U> = {
    unit: string,
    unitEntry: U,
    amount: number,
    amountAsFloatingPointNumber: number
}

export class TrivialUnitBox<U> implements TrivialComponent {

    private config: TrivialUnitBoxConfig<U>;

    public readonly onChange = new TrivialEvent<TrivialUnitBoxChangeEvent<U>>(this);
    public readonly onSelectedEntryChanged = new TrivialEvent<U>(this);
    public readonly onFocus = new TrivialEvent<void>(this);
    public readonly onBlur = new TrivialEvent<void>(this);

    private listBox: TrivialTreeBox<U>;
    private isDropDownOpen = false;
    private entries: U[];
    private selectedEntry: U;
    private blurCausedByClickInsideComponent = false;
    private numberRegex: RegExp;
    private $spinners: JQuery = $();
    private $originalInput: JQuery;
    private $editor: JQuery;
    private $dropDownTargetElement: JQuery;
    private $unitBox: JQuery;
    private $selectedEntryAndTriggerWrapper: JQuery;
    private $selectedEntryWrapper: JQuery;
    private $dropDown: JQuery;
    private editingMode: EditingMode;
    private usingDefaultQueryFunction: boolean;

    constructor(originalInput: JQuery|Element|string, options: TrivialUnitBoxConfig<U> = {}) {
        this.config = $.extend(<TrivialUnitBoxConfig<U>> {
            unitValueProperty: 'code',
            unitIdProperty: 'code',
            decimalPrecision: 2,
            decimalSeparator: '.',
            thousandsSeparator: ',',
            unitDisplayPosition: 'right', // right or left
            allowNullAmount: true,
            entryRenderingFunction: DEFAULT_RENDERING_FUNCTIONS.currency2Line,
            selectedEntryRenderingFunction: DEFAULT_RENDERING_FUNCTIONS.currencySingleLineShort,
            amount: null,
            selectedEntry: undefined,
            spinnerTemplate: DEFAULT_TEMPLATES.defaultSpinnerTemplate,
            noEntriesTemplate: DEFAULT_TEMPLATES.defaultNoEntriesTemplate,
            entries: null,
            queryFunction: null, // defined below...
            queryOnNonNumberCharacters: true,
            openDropdownOnEditorClick: false,
            showTrigger: true,
            matchingOptions: {
                matchingMode: 'prefix-word',
                ignoreCase: true,
                maxLevenshteinDistance: 2
            },
            editingMode: 'editable', // one of 'editable', 'disabled' and 'readonly'
        }, options);

        if (!this.config.queryFunction) {
            this.config.queryFunction = defaultListQueryFunctionFactory(this.config.entries || [], ["code", "name", "symbol"], this.config.matchingOptions);
            this.usingDefaultQueryFunction = true;
        }

        this.entries = this.config.entries;

        this.numberRegex = new RegExp('\\d*\\' + this.config.decimalSeparator + '?\\d*', 'g');

        this.$originalInput = $(originalInput).addClass("tr-original-input");
        this.$editor = $('<input type="text"/>');
        this.$unitBox = $('<div class="tr-unitbox tr-input-wrapper"/>').insertAfter(this.$originalInput);
        this.$originalInput.appendTo(this.$unitBox);
        this.$selectedEntryAndTriggerWrapper = $('<div class="tr-unitbox-selected-entry-and-trigger-wrapper"/>').appendTo(this.$unitBox);
        this.$selectedEntryWrapper = $('<div class="tr-unitbox-selected-entry-wrapper"/>').appendTo(this.$selectedEntryAndTriggerWrapper);
        if (this.config.showTrigger) {
            $('<div class="tr-trigger"><span class="tr-trigger-icon"/></div>').appendTo(this.$selectedEntryAndTriggerWrapper);
        }
        this.$selectedEntryAndTriggerWrapper.mousedown(() => {
            if (this.isDropDownOpen) {
                this.closeDropDown();
            } else if (this.editingMode === "editable") {
                setTimeout(() => { // TODO remove this when Chrome bug is fixed. Chrome scrolls to the top of the page if we do this synchronously. Maybe this has something to do with https://code.google.com/p/chromium/issues/detail?id=342307 .
                    this.openDropDown();
                    this.query();
                });
            }
        });<commit_msg>unitBox: focus when trigger clicked<commit_after>/*!
Trivial Components (https://github.com/trivial-components/trivial-components)

Copyright 2016 <NAME> (https://github.com/yamass) and other contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import * as $ from "jquery";
import {DEFAULT_TEMPLATES, defaultListQueryFunctionFactory, EditingMode, HighlightDirection, QueryFunction, TrivialComponent, keyCodes, DEFAULT_RENDERING_FUNCTIONS} from "./TrivialCore";
import {TrivialEvent} from "./TrivialEvent";
import {place} from "place-to";
import {TrivialTreeBox, TrivialTreeBoxConfig} from "./TrivialTreeBox";

export interface TrivialUnitBoxConfig<U> extends TrivialTreeBoxConfig<U> {
    unitValueProperty?: string,
    unitIdProperty?: string,
    decimalPrecision?: number,
    decimalSeparator?: string,
    thousandsSeparator?: string,
    unitDisplayPosition?: 'left' | 'right',
    allowNullAmount?: boolean,
    selectedEntryRenderingFunction?: (entry: U) => string,
    amount?: number,
    noEntriesTemplate?: string,
    queryFunction?: QueryFunction<U>,
    queryOnNonNumberCharacters?: boolean,
    openDropdownOnEditorClick?: boolean,
    showTrigger?: boolean,
    editingMode?: EditingMode,
    
    /**
     * HTML string defining the spinner to be displayed while entries are being retrieved.
     */
    spinnerTemplate?: string
}

export type TrivialUnitBoxChangeEvent<U> = {
    unit: string,
    unitEntry: U,
    amount: number,
    amountAsFloatingPointNumber: number
}

export class TrivialUnitBox<U> implements TrivialComponent {

    private config: TrivialUnitBoxConfig<U>;

    public readonly onChange = new TrivialEvent<TrivialUnitBoxChangeEvent<U>>(this);
    public readonly onSelectedEntryChanged = new TrivialEvent<U>(this);
    public readonly onFocus = new TrivialEvent<void>(this);
    public readonly onBlur = new TrivialEvent<void>(this);

    private listBox: TrivialTreeBox<U>;
    private isDropDownOpen = false;
    private entries: U[];
    private selectedEntry: U;
    private blurCausedByClickInsideComponent = false;
    private numberRegex: RegExp;
    private $spinners: JQuery = $();
    private $originalInput: JQuery;
    private $editor: JQuery;
    private $dropDownTargetElement: JQuery;
    private $unitBox: JQuery;
    private $selectedEntryAndTriggerWrapper: JQuery;
    private $selectedEntryWrapper: JQuery;
    private $dropDown: JQuery;
    private editingMode: EditingMode;
    private usingDefaultQueryFunction: boolean;

    constructor(originalInput: JQuery|Element|string, options: TrivialUnitBoxConfig<U> = {}) {
        this.config = $.extend(<TrivialUnitBoxConfig<U>> {
            unitValueProperty: 'code',
            unitIdProperty: 'code',
            decimalPrecision: 2,
            decimalSeparator: '.',
            thousandsSeparator: ',',
            unitDisplayPosition: 'right', // right or left
            allowNullAmount: true,
            entryRenderingFunction: DEFAULT_RENDERING_FUNCTIONS.currency2Line,
            selectedEntryRenderingFunction: DEFAULT_RENDERING_FUNCTIONS.currencySingleLineShort,
            amount: null,
            selectedEntry: undefined,
            spinnerTemplate: DEFAULT_TEMPLATES.defaultSpinnerTemplate,
            noEntriesTemplate: DEFAULT_TEMPLATES.defaultNoEntriesTemplate,
            entries: null,
            queryFunction: null, // defined below...
            queryOnNonNumberCharacters: true,
            openDropdownOnEditorClick: false,
            showTrigger: true,
            matchingOptions: {
                matchingMode: 'prefix-word',
                ignoreCase: true,
                maxLevenshteinDistance: 2
            },
            editingMode: 'editable', // one of 'editable', 'disabled' and 'readonly'
        }, options);

        if (!this.config.queryFunction) {
            this.config.queryFunction = defaultListQueryFunctionFactory(this.config.entries || [], ["code", "name", "symbol"], this.config.matchingOptions);
            this.usingDefaultQueryFunction = true;
        }

        this.entries = this.config.entries;

        this.numberRegex = new RegExp('\\d*\\' + this.config.decimalSeparator + '?\\d*', 'g');

        this.$originalInput = $(originalInput).addClass("tr-original-input");
        this.$editor = $('<input type="text"/>');
        this.$unitBox = $('<div class="tr-unitbox tr-input-wrapper"/>').insertAfter(this.$originalInput);
        this.$originalInput.appendTo(this.$unitBox);
        this.$selectedEntryAndTriggerWrapper = $('<div class="tr-unitbox-selected-entry-and-trigger-wrapper"/>').appendTo(this.$unitBox);
        this.$selectedEntryWrapper = $('<div class="tr-unitbox-selected-entry-wrapper"/>').appendTo(this.$selectedEntryAndTriggerWrapper);
        if (this.config.showTrigger) {
            $('<div class="tr-trigger"><span class="tr-trigger-icon"/></div>').appendTo(this.$selectedEntryAndTriggerWrapper);
        }
        this.$selectedEntryAndTriggerWrapper.mousedown(() => {
            if (this.isDropDownOpen) {
                this.closeDropDown();
            } else if (this.editingMode === "editable") {
                setTimeout(() => { // TODO remove this when Chrome bug is fixed. Chrome scrolls to the top of the page if we do this synchronously. Maybe this has something to do with https://code.google.com/p/chromium/issues/detail?id=342307 .
                    this.openDropDown();
                    this.$editor.focus();
                    this.query();
                });
            }
        });
{'fraction_non_alphanumeric': 0.10777591973244147, 'fraction_numerical': 0.002842809364548495, 'mean_word_length': 3.265691868758916, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 55, 'https://': 6, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3671687', 'n_tokens_mistral': 3198, 'n_tokens_neox': 3144, 'n_words': 799}