import { IconSize } from "@blueprintjs/core" ; export const CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT = "bp-table-text-no-measure" ; const EXCLUDED_ICON_PLACEHOLDER_WIDTH = IconSize . STANDARD ; /**
 * Since Firefox doesn't provide a computed "font" property, we manually
 * construct it using the ordered properties that can be specifed in CSS.
 */ const CSS_FONT_PROPERTIES = [ "font-style" , "font-variant" , "font-weight" , "font-size" , "font-family" ] ; export interface IKeyAllowlist < T > { include : Array < keyof T > ; } export interface IKeyDenylist < T > { exclude : Array < keyof T > ; } export const Utils = { /**
     * Invokes the callback `n` times, collecting the results in an array, which
     * is the return value. Similar to _.times
     */ times < T > ( n , callback ) { if ( n < 0 ) { throw new Error ( "[Blueprint] times() cannot be called with negative numbers." ) ; } const result = Array ( n ) ; for ( let index = 0 ; index < n ; index ++ ) { result [ index ] = callback ( index ) ; } return result ; } , /**
     * Takes an array of numbers, returns an array of numbers of the same length in which each
     * value is the sum of current and previous values in the input array.
     *
     * Example input:  [10, 20, 50]
     *         output: [10, 30, 80]
     */ accumulate ( numbers ) { const result = [ ] ; let sum = 0 ; for ( const num of numbers ) { sum += num ; result . push ( sum ) ; } return result ; } , /**
     * Returns traditional spreadsheet-style column names
     * e.g. (A, B, ..., Z, AA, AB, ..., ZZ, AAA, AAB, ...).
     *
     * Note that this isn't technically mathematically equivalent to base 26 since
     * there is no zero element.
     */ toBase26Alpha : ( num ) => { let str = "" ; while ( true ) { const letter = num % 26 ; str = String . fromCharCode ( 65 + letter ) + str ; num = num - letter ; if ( num <= 0 ) { return str ; } num = num / 26 - 1 ; } } , /**
     * Returns traditional spreadsheet-style cell names
     * e.g. (A1, B2, ..., Z44, AA1) with rows 1-indexed.
     */ toBase26CellName : ( rowIndex , columnIndex ) => { return `${ Utils . toBase26Alpha ( columnIndex ) }${ rowIndex + 1 }` ; } , param value param high param lookup binarySearch ( value , high , lookup ) { let low = 0 ; while ( low < high ) { const mid = Math . floor ( ( low + high ) / 2.0 ) ; const computed = lookup ( mid ) ; if ( computed < value ) { low = mid + 1 ; } else { high = mid ; } } return high ; } , param array param length param fillValue arrayOfLength < T > ( array , length , fillValue ) { if ( array . length > length ) { return array . slice ( 0 , length ) ; } array = array . slice ( ) ; while ( array . length < length ) { array . push ( fillValue ) ; } return array ; } , param defaults param sparseOverrides assignSparseValues < T > ( defaults , sparseOverrides ) { if ( sparseOverrides == null || defaults . length !== sparseOverrides . length ) { return defaults ; } defaults = defaults . slice ( ) ; for ( let i = 0 ; i < defaults . length ; i ++ ) { const override = sparseOverrides [ i ] ; if ( override != null ) { defaults [ i ] = override ; } } return defaults ; } , /**
     * Measures the bounds of supplied element's textContent.
     * We use the computed font from the supplied element and a non-DOM canvas
     * context to measure the text.
     */ measureElementTextContent ( element ) { const context = document . createElement ( "canvas" ) . getContext ( "2d" ) ; const style = getComputedStyle ( element , null ) ; context . font = CSS_FONT_PROPERTIES . map ( prop => style . getPropertyValue ( prop ) ) . join ( " " ) ; return measureTextContentWithExclusions ( context , element ) ; } , /**
     * Given a number, returns a value that is clamped within a
     * minimum/maximum bounded range. The minimum and maximum are optional. If
     * either is missing, that extrema limit is not applied.
     *
     * Assumes max >= min.
     */ clamp ( value , min ? , max ? ) { if ( min != null && value < min ) { value = min ; } if ( max != null && value > max ) { value = max ; } return value ; } , /**
     * When reordering a contiguous block of rows or columns to a new index, we show a preview guide
     * at the absolute index in the original ordering but emit the new index in the reordered list.
     * This function converts an absolute "guide" index to a relative "reordered" index.
     *
     * Example: Say we want to move the first three columns two spots to the right. While we drag, a
     * vertical guide is shown to preview where we'll be dropping the columns. (In the following
     * ASCII art, `*` denotes a selected column, `·` denotes a cell border, and `|` denotes a
     * vertical guide).
     *
     *     Before mousedown:
     *     · 0 · 1 · 2 · 3 · 4 · 5 ·
     *       *   *   *
     *
     *     During mousemove two spots to the right:
     *     · 0 · 1 · 2 · 3 · 4 | 5 ·
     *       *   *   *
     *
     *     After mouseup:
     *     · 3 · 4 · 0 · 1 · 2 · 5 ·
     *               *   *   *
     *
     * Note that moving the three columns beyond index 4 effectively moves them two spots rightward.
     *
     * In this case, the inputs to this function would be:
     *     - oldIndex: 0 (the left-most index of the selected column range in the original ordering)
     *     - newIndex: 5 (the index on whose left boundary the guide appears in the original ordering)
     *     - length: 3 (the number of columns to move)
     *
     * The return value will then be 2, the left-most index of the columns in the new ordering.
     */ guideIndexToReorderedIndex ( oldIndex , newIndex , length ) { if ( newIndex < oldIndex ) { return newIndex ; } else if ( oldIndex <= newIndex && newIndex < oldIndex + length ) { return oldIndex ; } else { return Math . max ( 0 , newIndex - length ) ; } } , /**
     * When reordering a contiguous block of rows or columns to a new index, we show a preview guide
     * at the absolute index in the original ordering but emit the new index in the reordered list.
     * This function converts a relative "reordered"" index to an absolute "guide" index.
     *
     * For the scenario in the example above, the inputs to this function would be:
     *     - oldIndex: 0 (the left-most index of the selected column range in the original ordering)
     *     - newIndex: 2 (the left-most index of the selected column range in the new ordering)
     *     - length: 3 (the number of columns to move)
     *
     * The return value will then be 5, the index on whose left boundary the guide should appear in
     * the original ordering.
     */ reorderedIndexToGuideIndex ( oldIndex , newIndex , length ) { return newIndex <= oldIndex ? newIndex : newIndex + length ; } , /**
     * Returns a copy of the provided array with the `length` contiguous elements starting at the
     * `from` index reordered to start at the `to` index.
     *
     * For example, given the array [A,B,C,D,E,F], reordering the 3 contiguous elements starting at
     * index 1 (B, C, and D) to start at index 2 would yield [A,E,B,C,D,F].
     */ reorderArray < T > ( array , from , to , length = 1 ) { if ( length === 0 || length === array . length || from === to ) { return array . slice ( ) ; } if ( length < 0 || length > array . length || from + length > array . length ) { return undefined ; } const before = array . slice ( 0 , from ) ; const within = array . slice ( from , from + length ) ; const after = array . slice ( from + length ) ; const result = [ ] ; let i = 0 ; let b = 0 ; let w = 0 ; let a = 0 ; while ( i < to ) { if ( b < before . length ) { result . push ( before [ b ] ) ; b += 1 ; } else { result . push ( after [ a ] ) ; a += 1 ; } i += 1 ; } while ( w < length ) { result . push ( within [ w ] ) ; w += 1 ; i += 1 ; } while ( i < array . length ) { if ( b < before . length ) { result . push ( before [ b ] ) ; b += 1 ; } else { result . push ( after [ a ] ) ; a += 1 ; } i += 1 ; } return result ; } , /**
     * Returns true if the mouse event was triggered by the left mouse button.
     */ isLeftClick ( event ) { return event . button === 0 ; } , getApproxCellHeight ( cellText , columnWidth , approxCharWidth , approxLineHeight , horizontalPadding , numBufferLines ) { const numCharsInCell = cellText == null ? 0 : cellText . length ; const actualCellWidth = columnWidth ; const availableCellWidth = actualCellWidth - horizontalPadding ; const approxCharsPerLine = availableCellWidth / approxCharWidth ; const approxNumLinesDesired = Math . ceil ( numCharsInCell / approxCharsPerLine ) + numBufferLines ; const approxCellHeight = approxNumLinesDesired * approxLineHeight ; return approxCellHeight ; } , } ; /**
 * Wrapper around Canvas measureText which applies some extra logic to optionally
 * exclude an element's text from the computation.
 */ function measureTextContentWithExclusions ( context , element ) { const elementsToExclude = element . querySelectorAll ( `.${ CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT }` ) ; let excludedElementsWidth = 0 ; if ( elementsToExclude && elementsToExclude . length ) { elementsToExclude . forEach ( e => { const excludedMetrics = context . measureText ( e . textContent ) ; excludedElementsWidth += excludedMetrics . width - EXCLUDED_ICON_PLACEHOLDER_WIDTH ; } ) ; } const metrics = context . measureText ( element . textContent ) ; const metricsWithExclusions = { ... metrics , width : metrics . width - excludedElementsWidth , } ; return metricsWithExclusions ; }