/*! Select 5.0.0 for DataTables
 * Copyright (c) SpryMedia Ltd - datatables.net/license
 */

import DataTable, { Dom, util } from 'datatables.net';

var _instCounter = 0;
class FixedHeader {
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * Public methods (exposed via the DataTables API below)
     */
    /**
     * Kill off FH and any events
     */
    destroy() {
        this.s.dt.off('.dtfc');
        Dom.s('body').off('.dtfc');
        Dom.w.off(this.s.namespace);
        // Remove clones of FC blockers
        if (this.dom.header.rightBlocker) {
            this.dom.header.rightBlocker.remove();
        }
        if (this.dom.header.leftBlocker) {
            this.dom.header.leftBlocker.remove();
        }
        if (this.dom.footer.rightBlocker) {
            this.dom.footer.rightBlocker.remove();
        }
        if (this.dom.footer.leftBlocker) {
            this.dom.footer.leftBlocker.remove();
        }
        if (this.c.header) {
            this._modeChange('in-place', 'header', true);
        }
        if (this.c.footer && this.dom.tfoot.count()) {
            this._modeChange('in-place', 'footer', true);
        }
    }
    /**
     * Enable / disable the fixed elements
     *
     * @param enable `true` to enable, `false` to disable
     */
    enable(enable, update) {
        this.s.enable = enable;
        if (update || update === undefined) {
            this._positions();
            this._scroll(true);
        }
    }
    /**
     * Get enabled status
     */
    enabled() {
        return this.s.enable;
    }
    /**
     * Set header offset
     *
     * @param offset value for headerOffset
     */
    headerOffset(offset) {
        if (offset !== undefined) {
            this.c.headerOffset = offset;
            this.s.headerOffset = offset;
            this.update();
        }
        return this.s.headerOffset;
    }
    /**
     * Set footer offset
     *
     * @param offset value for footerOffset
     */
    footerOffset(offset) {
        if (offset !== undefined) {
            this.c.footerOffset = offset;
            this.s.footerOffset = offset;
            this.update();
        }
        return this.s.footerOffset;
    }
    /**
     * Recalculate the position of the fixed elements and force them into place
     */
    update(force = true) {
        var table = this.s.dt.table().node();
        // Update should only do something if enabled by the dev.
        if (!this.s.enable && !this.s.autoDisable) {
            return;
        }
        if (Dom.s(table).isVisible()) {
            this.s.autoDisable = false;
            this.enable(true, false);
        }
        else {
            this.s.autoDisable = true;
            this.enable(false, false);
        }
        // Don't update if header is not in the document atm (due to
        // async events)
        if (Dom.s(table).children('thead').count() === 0) {
            return;
        }
        this._positions();
        this._scroll(force);
        this._widths(this.dom.header);
        this._widths(this.dom.footer);
    }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * Constructor
     */
    constructor(ctx, config) {
        let dt = new DataTable.Api(ctx);
        this.c = util.object.assign({}, FixedHeader.defaults, config);
        this.s = {
            dt: dt,
            position: {
                theadBottom: 0,
                theadTop: 0,
                tbodyTop: 0,
                tbodyHeight: 0,
                tbodyWidth: 0,
                tfootTop: 0,
                tfootBottom: 0,
                width: 0,
                left: 0,
                tfootHeight: 0,
                theadHeight: 0,
                windowHeight: document.querySelector('html').clientHeight,
                visible: true
            },
            headerMode: null,
            headerOffset: 0,
            footerMode: null,
            footerOffset: 0,
            autoWidth: dt.settings()[0].features.autoWidth,
            namespace: '.dtfc' + _instCounter++,
            scrollLeft: {
                header: -1,
                footer: -1
            },
            enable: true,
            autoDisable: false
        };
        this.dom = {
            floatingHeader: null,
            thead: Dom.s(dt.table().header()),
            tbody: Dom.s(dt.table().body()),
            tfoot: Dom.s(dt.table().footer()),
            header: {
                host: null,
                scrollAdjust: null,
                floating: null,
                floatingParent: Dom
                    .c('div')
                    .classAdd('dtfh-floatingparent')
                    .append(
                // location
                Dom.c('div').classAdd('dtfh-floating-limiter').append(
                // hidden overflow / scrolling
                Dom.c('div') // adjustment for scrollbar (padding)
                )),
                limiter: null,
                placeholder: null,
                rightBlocker: null,
                leftBlocker: null
            },
            footer: {
                host: null,
                scrollAdjust: null,
                floating: null,
                floatingParent: Dom
                    .c('div')
                    .classAdd('dtfh-floatingparent')
                    .append(Dom
                    .c('div')
                    .classAdd('dtfh-floating-limiter')
                    .append(Dom.c('div'))),
                limiter: null,
                placeholder: null,
                rightBlocker: null,
                leftBlocker: null
            }
        };
        this.dom.header.host = this.dom.thead.parent();
        this.dom.header.limiter = this.dom.header.floatingParent.children();
        this.dom.header.scrollAdjust = this.dom.header.limiter.children();
        this.dom.footer.host = this.dom.tfoot.parent();
        this.dom.footer.limiter = this.dom.footer.floatingParent.children();
        this.dom.footer.scrollAdjust = this.dom.footer.limiter.children();
        var dtSettings = dt.settings()[0];
        if (dtSettings._fixedHeader) {
            throw ('FixedHeader already initialised on table ' +
                dtSettings.table.id);
        }
        dtSettings._fixedHeader = this;
        this._init();
    }
    /**
     * FixedHeader constructor - adding the required event listeners and
     * simple initialisation
     */
    _init() {
        var that = this;
        var dt = this.s.dt;
        Dom.w.on('scroll' + this.s.namespace, function () {
            that._scroll();
        });
        Dom.w.on('resize' + this.s.namespace, DataTable.util.throttle(function () {
            that.s.position.windowHeight = Dom.w.height();
            that.update();
        }, 50));
        dt.on('column-reorder.dt.dtfc column-visibility.dt.dtfc column-sizing.dt.dtfc responsive-display.dt.dtfc', function (e, ctx) {
            that.update();
        });
        Dom.s('body').on('draw.dt.dtfc', function (e, ctx) {
            // For updates from our own table, don't reclone, but for all others, do
            that.update(ctx === dt.settings()[0] ? false : true);
        });
        dt.on('destroy.dtfc', function () {
            that.destroy();
        });
        this._positions();
        this._scroll();
    }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * Private methods
     */
    /**
     * Clone a fixed item to act as a place holder for the original element
     * which is moved into a clone of the table element, and moved around the
     * document to give the fixed effect.
     *
     * @param item  'header' or 'footer'
     * @param force Force the clone to happen, or allow automatic decision
     *   (reuse existing if available)
     */
    _clone(item, force) {
        var that = this;
        var dt = this.s.dt;
        var itemDom = this.dom[item];
        var itemElement = item === 'header' ? this.dom.thead : this.dom.tfoot;
        // If footer and scrolling is enabled then we don't clone
        // Instead the table's height is decreased accordingly - see `_scroll()`
        if (item === 'footer' && this._scrollEnabled()) {
            return;
        }
        if (!force && itemDom.floating) {
            // existing floating element - reuse it
            itemDom.floating.classRemove('fixedHeader-floating fixedHeader-locked');
        }
        else {
            if (itemDom.floating) {
                if (itemDom.placeholder !== null) {
                    itemDom.placeholder.detach();
                }
                itemDom.floating.detach();
            }
            var tableNode = Dom.s(dt.table().node());
            var scrollBody = tableNode.parent();
            var scrollEnabled = this._scrollEnabled();
            itemDom.floating = Dom
                .s(dt.table().node().cloneNode(false))
                .attr('aria-hidden', 'true')
                .css({
                top: '0px',
                left: '0px'
            })
                .attrRemove('id');
            itemDom.floatingParent
                .css({
                width: scrollBody.get(0).offsetWidth + 'px',
                position: 'fixed',
                left: scrollEnabled
                    ? tableNode.offset().left +
                        scrollBody.scrollLeft() +
                        'px'
                    : '0px'
            })
                .css(item === 'header'
                ? {
                    top: this.s.headerOffset + 'px',
                    bottom: ''
                }
                : {
                    top: '',
                    bottom: this.s.footerOffset + 'px'
                })
                .classAdd(item === 'footer'
                ? 'dtfh-floatingparent-foot'
                : 'dtfh-floatingparent-head')
                .appendTo('body')
                .children()
                .eq(0);
            itemDom.limiter.css({
                width: '100%',
                overflow: 'hidden',
                height: 'fit-content'
            });
            itemDom.scrollAdjust.append(itemDom.floating);
            this._stickyPosition(itemDom.floating, '-');
            var scrollLeftUpdate = function () {
                var scrollLeft = scrollBody.scrollLeft();
                that.s.scrollLeft = { footer: scrollLeft, header: scrollLeft };
                itemDom.limiter.scrollLeft(that.s.scrollLeft.header);
            };
            scrollLeftUpdate();
            scrollBody.off('scroll.dtfh').on('scroll.dtfh', scrollLeftUpdate);
            // Need padding on the header's container to allow for a scrollbar,
            // just like how DataTables handles it
            itemDom.scrollAdjust.css({
                width: 'fit-content',
                paddingRight: that.s.dt.settings()[0].browser.barWidth + 'px'
            });
            // Blocker to hide the table behind the scrollbar - this needs to
            // use fixed positioning in the container since we don't have an
            // outer wrapper
            let blocker = Dom
                .s(dt.table().container())
                .find(item === 'footer'
                ? 'div.dtfc-bottom-blocker'
                : 'div.dtfc-top-blocker');
            if (blocker.count()) {
                blocker
                    .clone()
                    .appendTo(itemDom.floatingParent)
                    .css({
                    position: 'fixed',
                    right: blocker.width() + 'px'
                });
            }
            // Insert a fake thead/tfoot into the DataTable to stop it jumping
            // around
            itemDom.placeholder = itemElement.clone(true);
            itemDom.placeholder.find('*[id]').attrRemove('id');
            // Move the thead / tfoot elements around - original into the
            // floating element and clone into the original table
            itemDom.host.prepend(itemDom.placeholder);
            itemDom.floating.append(itemElement);
            // Move the thead / tfoot elements around - original into the
            // floating element and clone into the original table. Note that the
            // order is important in Chrome. It must be colgroup, thead, tbody,
            // tfoot. Otherwise a "jitter" when scrolling will occur.
            itemDom.placeholder.insertAfter(itemDom.host.find(item === 'header' ? 'colgroup' : 'tbody'));
            itemDom.floating.append(itemElement);
            this._widths(itemDom);
            return scrollLeftUpdate;
        }
    }
    /**
     * This method sets the sticky position of the header elements to match
     * fixed columns
     *
     * @param el
     * @param sign
     */
    _stickyPosition(el, sign) {
        if (this._scrollEnabled()) {
            var that = this;
            var rtl = Dom.s(that.s.dt.table().node()).css('direction') === 'rtl';
            el.find('th').each(function (el) {
                let th = Dom.s(el);
                // Find out if fixed header has previously set this column
                if (th.css('position') === 'sticky') {
                    var right = th.css('right');
                    var left = th.css('left');
                    var potential;
                    if (right !== 'auto' && !rtl) {
                        potential = +right.replace(/px/g, '');
                        th.css('right', (potential > 0 ? potential : 0) + 'px');
                    }
                    else if (left !== 'auto' && rtl) {
                        potential = +left.replace(/px/g, '');
                        th.css('left', (potential > 0 ? potential : 0) + 'px');
                    }
                }
            });
        }
    }
    /**
     * Reposition the floating elements to take account of horizontal page
     * scroll
     *
     * @param item The `header` or `footer`
     * @param scrollLeft Document scrollLeft
     */
    _horizontal(item, scrollLeft) {
        var itemDom = this.dom[item];
        var lastScrollLeft = this.s.scrollLeft;
        if (itemDom.floating && lastScrollLeft[item] !== scrollLeft) {
            // If scrolling is enabled we need to match the floating header to
            // the body
            if (this._scrollEnabled()) {
                var newScrollLeft = Dom
                    .s(this.s.dt.table().node().parentNode)
                    .scrollLeft();
                itemDom.floating.scrollLeft(newScrollLeft);
                itemDom.floatingParent.scrollLeft(newScrollLeft);
            }
            lastScrollLeft[item] = scrollLeft;
        }
    }
    /**
     * Change from one display mode to another. Each fixed item can be in one
     * of:
     *
     * * `in-place` - In the main DataTable
     * * `in` - Floating over the DataTable
     * * `below` - (Header only) Fixed to the bottom of the table body
     * * `above` - (Footer only) Fixed to the top of the table body
     *
     * @param mode Mode that the item should be shown in
     * @param item 'header' or 'footer'
     * @param forceChange Force a redraw of the mode, even if already in that
     *     mode.
     */
    _modeChange(mode, item, forceChange) {
        var _a, _b, _c;
        var dt = this.s.dt;
        var itemDom = this.dom[item];
        var position = this.s.position;
        // Just determine if scroll is enabled once
        var scrollEnabled = this._scrollEnabled();
        // If footer and scrolling is enabled then we don't clone
        // Instead the table's height is decreased accordingly - see `_scroll()`
        if (item === 'footer' && scrollEnabled) {
            return;
        }
        // It isn't trivial to add a !important css attribute...
        var importantWidth = function (w) {
            var _a;
            (_a = itemDom.floating) === null || _a === void 0 ? void 0 : _a.get(0).style.setProperty('width', w + 'px', 'important');
            // If not scrolling also have to update the floatingParent
            if (!scrollEnabled) {
                itemDom.floatingParent
                    .get(0)
                    .style.setProperty('width', w + 'px', 'important');
            }
        };
        // Record focus. Browser's will cause input elements to loose focus if
        // they are inserted else where in the doc
        var tablePart = this.dom[item === 'footer' ? 'tfoot' : 'thead'];
        var focus = tablePart.find(document.activeElement).count()
            ? document.activeElement
            : null;
        var scrollBody = Dom.s(this.s.dt.table().node()).parent();
        if (mode === 'in-place') {
            // Insert the header back into the table's real header
            if (itemDom.placeholder) {
                itemDom.placeholder.remove();
                itemDom.placeholder = null;
            }
            if (itemDom.host) {
                if (!itemDom.host.contains(tablePart)) {
                    if (item === 'header') {
                        tablePart.insertAfter(itemDom.host.find('colgroup'));
                    }
                    else {
                        itemDom.host.append(tablePart);
                    }
                }
                if (itemDom.floating) {
                    itemDom.floating.remove();
                    itemDom.floating = null;
                    this._stickyPosition(itemDom.host, '+');
                }
                if (itemDom.floatingParent) {
                    itemDom.floatingParent
                        .find('div.dtfc-top-blocker')
                        .remove();
                    itemDom.floatingParent.remove();
                }
                itemDom.host
                    .parent()
                    .parent()
                    .scrollLeft(scrollBody.scrollLeft());
            }
        }
        else if (mode === 'in') {
            // Remove the header from the real table and insert into a fixed
            // positioned floating table clone
            let scrollLeftUpdate = this._clone(item, forceChange);
            // Get useful position values
            var scrollOffset = scrollBody.offset();
            var windowTop = Dom.w.scrollTop();
            var windowHeight = Dom.w.height();
            var windowBottom = windowTop + windowHeight;
            var bodyTop = scrollEnabled ? scrollOffset.top : position.tbodyTop;
            var bodyBottom = scrollEnabled
                ? scrollOffset.top + scrollBody.height('outer')
                : position.tfootTop;
            // Calculate the amount that the footer or header needs to be shuffled
            var shuffle;
            if (item === 'footer') {
                shuffle =
                    bodyTop > windowBottom
                        ? position.tfootHeight // Yes - push the footer below
                        : bodyTop + position.tfootHeight - windowBottom; // No
            }
            else {
                // Otherwise must be a header so get the difference from the
                // bottom of the desired floating header and the bottom of the
                // table body
                shuffle =
                    windowTop +
                        this.s.headerOffset +
                        position.theadHeight -
                        bodyBottom;
            }
            // Set the top or bottom based off of the offset and the shuffle value
            var prop = item === 'header' ? 'top' : 'bottom';
            var val = this.s[item + 'Offset'] - (shuffle > 0 ? shuffle : 0);
            (_a = itemDom.floating) === null || _a === void 0 ? void 0 : _a.classAdd('fixedHeader-floating');
            itemDom.floatingParent.css({
                [prop]: val + 'px',
                left: position.left + 'px',
                'z-index': '3'
            });
            importantWidth(position.width);
            if (scrollLeftUpdate) {
                scrollLeftUpdate();
            }
            if (item === 'footer' && itemDom.floating) {
                itemDom.floating.css('top', '');
            }
        }
        else if (mode === 'below') {
            // only used for the header
            // Fix the position of the floating header at base of the table body
            this._clone(item, forceChange);
            (_b = itemDom.floating) === null || _b === void 0 ? void 0 : _b.classAdd('fixedHeader-locked');
            itemDom.floatingParent.css({
                position: 'absolute',
                top: position.tfootTop - position.theadHeight + 'px',
                left: position.left + 'px'
            });
            importantWidth(position.width);
        }
        else if (mode === 'above') {
            // only used for the footer
            // Fix the position of the floating footer at top of the table body
            this._clone(item, forceChange);
            (_c = itemDom.floating) === null || _c === void 0 ? void 0 : _c.classAdd('fixedHeader-locked');
            itemDom.floatingParent.css({
                position: 'absolute',
                top: position.tbodyTop + 'px',
                left: position.left + 'px'
            });
            importantWidth(position.width);
        }
        // Restore focus if it was lost
        if (focus && focus !== document.activeElement) {
            setTimeout(function () {
                if (focus) {
                    focus.focus();
                }
            }, 10);
        }
        this.s.scrollLeft.header = -1;
        this.s.scrollLeft.footer = -1;
        this.s[item === 'header' ? 'headerMode' : 'footerMode'] = mode;
        dt.trigger('fixedheader-mode', [mode, item]);
    }
    /**
     * Cache the positional information that is required for the mode
     * calculations that FixedHeader performs.
     */
    _positions() {
        var dt = this.s.dt;
        var table = dt.table();
        var position = this.s.position;
        var tableNode = Dom.s(table.node());
        var scrollEnabled = this._scrollEnabled();
        // Offsets
        var autoHeader = Dom.s('.fh-fixedHeader');
        if (!this.c.headerOffset && autoHeader.count()) {
            this.s.headerOffset = autoHeader.height('outer');
        }
        else {
            this.s.headerOffset = this.c.headerOffset;
        }
        var autoFooter = Dom.s('.fh-fixedFooter');
        if (!this.c.footerOffset && autoFooter.count()) {
            this.s.footerOffset = autoFooter.height('outer');
        }
        else {
            this.s.footerOffset = this.c.footerOffset;
        }
        // Need to use the header and footer that are in the main table,
        // regardless of if they are clones, since they hold the positions we
        // want to measure from
        var thead = Dom.s(dt.table().header());
        var tfoot = Dom.s(dt.table().footer());
        var tbody = this.dom.tbody;
        var scrollBody = tableNode.parent();
        position.visible = tableNode.isVisible();
        position.width = tableNode.width('outer');
        position.left = tableNode.offset().left;
        position.theadTop = thead.offset().top;
        position.tbodyTop = scrollEnabled
            ? scrollBody.offset().top
            : tbody.offset().top;
        position.tbodyHeight = scrollEnabled
            ? scrollBody.height('outer')
            : tbody.height('outer');
        position.theadHeight = thead.height('outer');
        position.theadBottom = position.theadTop + position.theadHeight;
        position.tfootTop = position.tbodyTop + position.tbodyHeight; //tfoot.offset().top;
        if (tfoot.count()) {
            position.tfootBottom = position.tfootTop + tfoot.height('outer');
            position.tfootHeight = tfoot.height('outer');
        }
        else {
            position.tfootBottom = position.tfootTop;
            position.tfootHeight = 0;
        }
    }
    /**
     * Mode calculation - determine what mode the fixed items should be placed
     * into.
     *
     * @param forceChange Force a redraw of the mode, even if already
     *     in that mode.
     */
    _scroll(forceChange = false) {
        if (this.s.dt.settings()[0].destroying) {
            return;
        }
        // ScrollBody details
        var scrollEnabled = this._scrollEnabled();
        var scrollBody = Dom.s(this.s.dt.table().node()).parent();
        var scrollOffset = scrollBody.offset();
        var scrollHeight = scrollBody.height('outer');
        // Window details
        var windowLeft = Dom.w.scrollLeft();
        var windowTop = Dom.w.scrollTop();
        var windowHeight = Dom.w.height();
        var windowBottom = windowHeight + windowTop;
        var position = this.s.position;
        var headerMode, footerMode;
        // Body Details
        var bodyTop = scrollEnabled ? scrollOffset.top : position.tbodyTop;
        var bodyLeft = scrollEnabled ? scrollOffset.left : position.left;
        var bodyBottom = scrollEnabled
            ? scrollOffset.top + scrollHeight
            : position.tfootTop;
        var bodyWidth = scrollEnabled
            ? scrollBody.width('outer')
            : position.tbodyWidth;
        if (this.c.header) {
            if (!this.s.enable) {
                headerMode = 'in-place';
            }
            // The header is in it's normal place if the body top is lower than
            // the scroll of the window plus the headerOffset and the height of
            // the header
            else if (!position.visible ||
                windowTop + this.s.headerOffset + position.theadHeight <=
                    bodyTop) {
                headerMode = 'in-place';
            }
            // The header should be floated if
            else if (
            // The scrolling plus the header offset plus the height of the
            // header is lower than the top of the body
            windowTop + this.s.headerOffset + position.theadHeight >
                bodyTop &&
                // And the scrolling at the top plus the header offset is above
                // the bottom of the body
                windowTop + this.s.headerOffset + position.theadHeight <
                    bodyBottom) {
                headerMode = 'in';
                // Further to the above, If the scrolling plus the header offset
                // plus the header height is lower than the bottom of the table
                // a shuffle is required so have to force the calculation
                if (windowTop + this.s.headerOffset + position.theadHeight >
                    bodyBottom ||
                    this.dom.header.floatingParent === undefined) {
                    forceChange = true;
                }
                else {
                    var child = this.dom.header.floatingParent
                        .css({
                        top: this.s.headerOffset + 'px',
                        position: 'fixed'
                    })
                        .children()
                        .eq(0);
                    if (child.find(this.dom.header.floating).count() === 0) {
                        child.append(this.dom.header.floating);
                    }
                }
            }
            // Anything else and the view is below the table
            else {
                headerMode = 'below';
            }
            if (forceChange || headerMode !== this.s.headerMode) {
                this._modeChange(headerMode, 'header', forceChange);
            }
            this._horizontal('header', windowLeft);
        }
        var header = {
            offset: { top: 0},
            height: 0
        };
        var footer = {
            offset: { top: 0},
            height: 0
        };
        if (this.c.footer &&
            this.dom.tfoot.count() &&
            this.dom.tfoot.find('th, td').count()) {
            if (!this.s.enable) {
                footerMode = 'in-place';
            }
            else if (!position.visible ||
                position.tfootBottom + this.s.footerOffset <= windowBottom) {
                footerMode = 'in-place';
            }
            else if (bodyBottom + position.tfootHeight + this.s.footerOffset >
                windowBottom &&
                bodyTop + this.s.footerOffset < windowBottom) {
                footerMode = 'in';
                forceChange = true;
            }
            else {
                footerMode = 'above';
            }
            if (forceChange || footerMode !== this.s.footerMode) {
                this._modeChange(footerMode, 'footer', forceChange);
            }
            this._horizontal('footer', windowLeft);
            var getOffsetHeight = function (el) {
                return {
                    offset: el.offset(),
                    height: el.height('outer')
                };
            };
            header = this.dom.header.floating
                ? getOffsetHeight(this.dom.header.floating)
                : getOffsetHeight(this.dom.thead);
            footer = this.dom.footer.floating
                ? getOffsetHeight(this.dom.footer.floating)
                : getOffsetHeight(this.dom.tfoot);
            // If scrolling is enabled and the footer is off the screen
            if (scrollEnabled && footer.offset.top > windowTop) {
                // && footer.offset.top >= windowBottom) { Calculate the gap
                // between the top of the scrollBody and the top of the window
                var overlap = windowTop - scrollOffset.top;
                // The new height is the bottom of the window
                var newHeight = windowBottom +
                    // If the gap between the top of the scrollbody and the
                    //  window is more than the height of the header then the
                    //  top of the table is still visible so add that gap Doing
                    //  this has effectively calculated the height from the top
                    //  of the table to the bottom of the current page
                    (overlap > -header.height ? overlap : 0) -
                    // Take from that
                    // The top of the header plus
                    (header.offset.top +
                        // The header height if the standard header is present
                        (overlap < -header.height ? header.height : 0) +
                        // And the height of the footer
                        footer.height);
                // Don't want a negative height
                if (newHeight < 0) {
                    newHeight = 0;
                }
                // At the end of the above calculation the space between the
                // header (top of the page if floating) and the point just above
                // the footer should be the new value for the height of the
                // table.
                scrollBody.height(newHeight);
                // Need some rounding here as sometimes very small decimal
                // places are encountered If the actual height is bigger or
                // equal to the height we just applied then the footer is
                // "Floating"
                if (Math.round(scrollBody.height('outer')) >=
                    Math.round(newHeight)) {
                    this.dom.tfoot.parent().classAdd('fixedHeader-floating');
                }
                // Otherwise max-width has kicked in so it is not floating
                else {
                    this.dom.tfoot.parent().classRemove('fixedHeader-floating');
                }
            }
        }
        if (this.dom.header.floating) {
            this.dom.header.floatingParent.css('left', bodyLeft - windowLeft + 'px');
        }
        if (this.dom.footer.floating) {
            this.dom.footer.floatingParent.css('left', bodyLeft - windowLeft + 'px');
        }
        // If fixed columns is being used on this table then the blockers need
        // to be copied across Cloning these is cleaner than creating as our own
        // as it will keep consistency with fixedColumns automatically ASSUMING
        // that the class remains the same
        if (this.s.dt.settings()[0]._fixedColumns !== undefined) {
            var adjustBlocker = function (side, end, el) {
                if (el === undefined) {
                    var blocker = Dom.s('div.dtfc-' + side + '-' + end + '-blocker');
                    el =
                        blocker.count() === 0
                            ? null
                            : blocker.clone().css('z-index', '1');
                }
                if (el !== null) {
                    if (headerMode === 'in' || headerMode === 'below') {
                        el.appendTo('body').css({
                            top: (end === 'top'
                                ? header.offset.top
                                : footer.offset.top) + 'px',
                            left: (side === 'right'
                                ? bodyLeft + bodyWidth - el.width()
                                : bodyLeft) + 'px'
                        });
                    }
                    else {
                        el.detach();
                    }
                }
                return el;
            };
            // Adjust all blockers
            this.dom.header.rightBlocker = adjustBlocker('right', 'top', this.dom.header.rightBlocker);
            this.dom.header.leftBlocker = adjustBlocker('left', 'top', this.dom.header.leftBlocker);
            this.dom.footer.rightBlocker = adjustBlocker('right', 'bottom', this.dom.footer.rightBlocker);
            this.dom.footer.leftBlocker = adjustBlocker('left', 'bottom', this.dom.footer.leftBlocker);
        }
    }
    /**
     * Function to check if scrolling is enabled on the table or not
     *
     * @returns Boolean value indicating if scrolling on the table is enabled or
     * not
     */
    _scrollEnabled() {
        var scroll = this.s.dt.settings()[0].scroll;
        return scroll.y !== '' || scroll.x !== '' ? true : false;
    }
    /**
     * Realign columns by using the colgroup tag and checking column widths
     *
     * @param itemDom Header or Footer dom object
     */
    _widths(itemDom) {
        if (!itemDom || !itemDom.placeholder) {
            return;
        }
        // Match the table overall width
        var tableNode = Dom.s(this.s.dt.table().node());
        var scrollBody = tableNode.parent();
        itemDom.floatingParent.css('width', scrollBody.get(0).offsetWidth + 'px');
        itemDom.floating.css('width', tableNode.get(0).offsetWidth + 'px');
        // Strip out the old colgroup
        itemDom.floating.find('colgroup').remove();
        // Copy the `colgroup` element to define the number of columns - needed
        // for complex header cases where a column might not have a unique
        // header
        var cols = itemDom.placeholder
            .parent()
            .find('colgroup')
            .clone(true)
            .appendTo(itemDom.floating)
            .find('col');
        // However, the widths defined in the colgroup from the DataTable might
        // not exactly reflect the actual widths of the columns (content can
        // force it to stretch). So we need to copy the actual widths into the
        // colgroup / col's used for the floating header.
        var widths = this.s.dt.columns(':visible').widths();
        for (var i = 0; i < widths.length; i++) {
            cols.eq(i).css('width', widths[i] + 'px');
        }
    }
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Statics
 */
/**
 * Defaults
 */
FixedHeader.defaults = {
    header: true,
    footer: false,
    headerOffset: 0,
    footerOffset: 0
};
/** Version */
FixedHeader.version = '5.0.0';


if (!DataTable || !DataTable.versionCheck('3')) {
    throw 'Warning: FixedHeader requires DataTables 3 or newer';
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * DataTables interfaces
 */
// Attach for constructor access
DataTable.FixedHeader = FixedHeader;
// DataTables creation - check if the FixedHeader option has been defined on the
// table and if so, initialise
Dom.s(document).on('init.dt.dtfh', function (e, settings) {
    if (e.namespace !== 'dt') {
        return;
    }
    var init = settings.init.fixedHeader;
    var defaults = DataTable.defaults.fixedHeader;
    if ((init || defaults) && !settings._fixedHeader) {
        let opts = {};
        if (util.is.plainObject(defaults)) {
            util.object.assign(opts, defaults);
        }
        if (util.is.plainObject(init)) {
            util.object.assign(opts, init);
        }
        if (init !== false) {
            new FixedHeader(settings, opts);
        }
    }
});
// DataTables API methods
DataTable.Api.register('fixedHeader()', function () { });
DataTable.Api.register('fixedHeader.adjust()', function () {
    return this.iterator('table', function (ctx) {
        var fh = ctx._fixedHeader;
        if (fh) {
            fh.update();
        }
    });
});
DataTable.Api.register('fixedHeader.enable()', function (flag) {
    return this.iterator('table', function (ctx) {
        var fh = ctx._fixedHeader;
        flag = flag !== undefined ? flag : true;
        if (fh && flag !== fh.enabled()) {
            fh.enable(flag);
        }
    });
});
DataTable.Api.register('fixedHeader.enabled()', function () {
    if (this.context.length) {
        var fh = this.context[0]._fixedHeader;
        if (fh) {
            return fh.enabled();
        }
    }
    return false;
});
DataTable.Api.register('fixedHeader.disable()', function () {
    return this.iterator('table', function (ctx) {
        var fh = ctx._fixedHeader;
        if (fh && fh.enabled()) {
            fh.enable(false);
        }
    });
});
['header', 'footer'].forEach(function (el) {
    DataTable.Api.register('fixedHeader.' + el + 'Offset()', function (offset) {
        var ctx = this.context;
        if (offset === undefined) {
            return ctx.length && ctx[0]._fixedHeader
                ? ctx[0]._fixedHeader[el + 'Offset']()
                : undefined;
        }
        return this.iterator('table', function (ctx) {
            var fh = ctx._fixedHeader;
            if (fh) {
                fh[el + 'Offset'](offset);
            }
        });
    });
});


export default DataTable;

