<commit_before>import events from 'events';
import Clusterize from 'clusterize.js';
import { flatten, Node } from 'flattree';
import LookupTable from './lookup-table';
import { defaultRowRenderer } from './renderer';
import {
    extend,
    preventDefault,
    addEventListener,
    removeEventListener,
    classNames,
    addClass,
    hasClass,
    removeClass,
    isDOMElement
} from './helper';

const error = (...args) => {
    if (console && console.error) {
        const prefix = '[InfiniteTree]';
        console.error.apply(console, [prefix].concat(args));
    }
};

const ensureNodeInstance = (node) => {
    if (!node) { // undefined or null
        return false;
    }
    if (!(node instanceof Node)) {
        error('The node must be a Node object.');
        return false;
    }
    return true;
};

class InfiniteTree extends events.EventEmitter {
    options = {
        autoOpen: false,
        droppable: false,
        el: null,
        layout: 'div',
        loadNodes: null,
        noDataClass: 'infinite-tree-no-data',
        noDataText: 'No data',
        nodeIdAttr: 'data-id',
        rowRenderer: defaultRowRenderer,
        selectable: true,
        shouldSelectNode: null,
        togglerClass: 'infinite-tree-toggler'
    };
    state = {
        openNodes: [],
        rootNode: null,
        selectedNode: null
    };
    clusterize = null;
    nodeTable = new LookupTable();
    nodes = [];
    rows = [];
    scrollElement = null;
    contentElement = null;
    draggableTarget = null;
    droppableTarget = null;

    contentListener = {
        'click': (event) => {
            event = event || window.event;

            // Wrap stopPropagation that allows to stop execution of subsequent calls
            const stopPropagation = event.stopPropagation;
            event.stopPropagation = function() {
                // Setting the cancelBubble property in browsers that don't support it doesn't hurt.
                // Of course it doesn't actually cancel the bubbling, but the assignment itself is safe.
                event.cancelBubble = true;
                if (stopPropagation) {
                    stopPropagation.call(event);
                }
            };

            // Use setTimeout(fn, 0) to re-queues the selectNode operation, it
            // allows the click event to bubble up to higher level event handlers.
            setTimeout(() => {
                // Prevent execution of subsequent calls if the cancelBubble property is set to true
                if (event.cancelBubble) {
                    return;
                }

                let itemTarget = null;
                let clickToggler = false;

                if (event.target && event.currentTarget) {
                    itemTarget = (event.target !== event.currentTarget) ? event.target : null;
                } else if (event.srcElement) { // IE8
                    itemTarget = event.srcElement;
                }

                while (itemTarget && itemTarget.parentElement !== this.contentElement) {
                    if (hasClass(itemTarget, this.options.togglerClass)) {
                        clickToggler = true;
                    }
                    itemTarget = itemTarget.parentElement;
                }

                if (!itemTarget) {
                    return;
                }

                // Emit 'click' event
                this.emit('click', event);

                if (event.cancelBubble) {
                    // Stop execution of subsequent event bindings
                    return;
                }

                const id = itemTarget.getAttribute(this.options.nodeIdAttr);
                const node = this.getNodeById(id);
                if (!node) {
                    return;
                }

                // Click on the toggler to open/close a tree node
                if (clickToggler) {
                    this.toggleNode(node);
                    return;
                }

                this.selectNode(node); // selectNode will re-render the tree
            }, 0);
        },
        // https://developer.mozilla.org/en-US/docs/Web/Events/dragstart
        // The dragstart event is fired when the user starts dragging an element or text selection.
        'dragstart': (e) => {
            this.draggableTarget = e.target || e.srcElement;
        },
        // https://developer.mozilla.org/en-US/docs/Web/Events/dragend
        // The dragend event is fired when a drag operation is being ended (by releasing a mouse button or hitting the escape key).
        'dragend': (e) => {
            const { hoverClass = '' } = this.options.droppable;
<commit_msg>Emit click event before handling tree node operations<commit_after>import events from 'events';
import Clusterize from 'clusterize.js';
import { flatten, Node } from 'flattree';
import LookupTable from './lookup-table';
import { defaultRowRenderer } from './renderer';
import {
    extend,
    preventDefault,
    addEventListener,
    removeEventListener,
    classNames,
    addClass,
    hasClass,
    removeClass,
    isDOMElement
} from './helper';

const error = (...args) => {
    if (console && console.error) {
        const prefix = '[InfiniteTree]';
        console.error.apply(console, [prefix].concat(args));
    }
};

const ensureNodeInstance = (node) => {
    if (!node) { // undefined or null
        return false;
    }
    if (!(node instanceof Node)) {
        error('The node must be a Node object.');
        return false;
    }
    return true;
};

class InfiniteTree extends events.EventEmitter {
    options = {
        autoOpen: false,
        droppable: false,
        el: null,
        layout: 'div',
        loadNodes: null,
        noDataClass: 'infinite-tree-no-data',
        noDataText: 'No data',
        nodeIdAttr: 'data-id',
        rowRenderer: defaultRowRenderer,
        selectable: true,
        shouldSelectNode: null,
        togglerClass: 'infinite-tree-toggler'
    };
    state = {
        openNodes: [],
        rootNode: null,
        selectedNode: null
    };
    clusterize = null;
    nodeTable = new LookupTable();
    nodes = [];
    rows = [];
    scrollElement = null;
    contentElement = null;
    draggableTarget = null;
    droppableTarget = null;

    contentListener = {
        'click': (event) => {
            event = event || window.event;

            // Wrap stopPropagation that allows click event handler to stop execution
            // by setting the cancelBubble property
            const stopPropagation = event.stopPropagation;
            event.stopPropagation = function() {
                // Setting the cancelBubble property in browsers that don't support it doesn't hurt.
                // Of course it doesn't actually cancel the bubbling, but the assignment itself is safe.
                event.cancelBubble = true;

                if (stopPropagation) {
                    stopPropagation.call(event);
                }
            };

            // Call setTimeout(fn, 0) to re-queues the execution of subsequent calls, it allows the
            // click event to bubble up to higher level event handlers before handling tree events.
            setTimeout(() => {
                // Stop execution if the cancelBubble property is set to true by higher level event handlers
                if (event.cancelBubble === true) {
                    return;
                }

                // Emit 'click' event
                this.emit('click', event);

                // Stop execution if the cancelBubble property is set to true after emitting the click event
                if (event.cancelBubble === true) {
                    return;
                }

                let itemTarget = null;
                let clickToggler = false;

                if (event.target && event.currentTarget) {
                    itemTarget = (event.target !== event.currentTarget) ? event.target : null;
                } else if (event.srcElement) { // IE8
                    itemTarget = event.srcElement;
                }

                while (itemTarget && itemTarget.parentElement !== this.contentElement) {
                    if (hasClass(itemTarget, this.options.togglerClass)) {
                        clickToggler = true;
                    }
                    itemTarget = itemTarget.parentElement;
                }

                if (!itemTarget) {
                    return;
                }

                const id = itemTarget.getAttribute(this.options.nodeIdAttr);
                const node = this.getNodeById(id);
                if (!node) {
                    return;
                }

                // Click on the toggler to open/close a tree node
                if (clickToggler) {
                    this.toggleNode(node);
                    return;
                }

                this.selectNode(node); // selectNode will re-render the tree
            }, 0);
        },
        // https://developer.mozilla.org/en-US/docs/Web/Events/dragstart
        // The dragstart event is fired when the user starts dragging an element or text selection.
        'dragstart': (e) => {
            this.draggableTarget = e.target || e.srcElement;
        },
        // https://developer.mozilla.org/en-US/docs/Web/Events/dragend
        // The dragend event is fired when a drag operation is being ended (by releasing a mouse button or hitting the escape key).
        'dragend': (e) => {
            const { hoverClass = '' } = this.options.droppable;

{'fraction_non_alphanumeric': 0.09613572101790764, 'fraction_numerical': 0.0006283380458686773, 'mean_word_length': 1.6050190943807965, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, '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': '5176561', 'n_tokens_mistral': 2483, 'n_tokens_neox': 2366, 'n_words': 703}