From f441310a981ba8fa5602d2cd933875e6df9853f2 Mon Sep 17 00:00:00 2001
From: Valentine Nuikin <zwerg44@gmail.com>
Date: Thu, 23 Jan 2020 01:41:28 +0300
Subject: [PATCH] [table] fix: column resize calculation on header double click
 (#3732)

---
 packages/table/src/common/utils.ts            | 32 +++++++++----------
 .../table/src/headers/columnHeaderCell.tsx    |  3 +-
 packages/table/src/headers/header.tsx         |  5 ++-
 3 files changed, 22 insertions(+), 18 deletions(-)

diff --git a/packages/table/src/common/utils.ts b/packages/table/src/common/utils.ts
index e37f4fda60..05de754853 100644
--- a/packages/table/src/common/utils.ts
+++ b/packages/table/src/common/utils.ts
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-const CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT = "bp-table-text-no-measure";
+// used to exclude icons from column header measure
+export const CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT = "bp-table-text-no-measure";
+// supposed width of the icons placeholder
+const EXCLUDED_ICON_PLACEHOLDER_WIDTH = 16;
 
 /**
  * Since Firefox doesn't provide a computed "font" property, we manually
@@ -350,23 +353,20 @@ export const Utils = {
  * exclude an element's text from the computation.
  */
 function measureTextContentWithExclusions(context: CanvasRenderingContext2D, element: Element): TextMetrics {
-    // We only expect one or zero excluded elements in this subtree
-    // We don't have a need for more than one, so we avoid that complexity altogether.
-    const elementToExclude = element.querySelector(`.${CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT}`);
-    let removedElementParent: Element | undefined;
-    let removedElementNextSibling: Node | undefined;
-
-    if (elementToExclude != null) {
-        removedElementParent = elementToExclude.parentElement;
-        removedElementNextSibling = elementToExclude.nextSibling;
-        removedElementParent.removeChild(elementToExclude);
+    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,
+    };
 
-    if (elementToExclude != null) {
-        removedElementParent.insertBefore(elementToExclude, removedElementNextSibling);
-    }
-
-    return metrics;
+    return metricsWithExclusions;
 }
diff --git a/packages/table/src/headers/columnHeaderCell.tsx b/packages/table/src/headers/columnHeaderCell.tsx
index 568911a529..97be63b318 100644
--- a/packages/table/src/headers/columnHeaderCell.tsx
+++ b/packages/table/src/headers/columnHeaderCell.tsx
@@ -31,6 +31,7 @@ import {
 import * as Classes from "../common/classes";
 import { columnInteractionBarContextTypes, IColumnInteractionBarContextTypes } from "../common/context";
 import { LoadableContent } from "../common/loadableContent";
+import { CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT } from "../common/utils";
 import { HeaderCell, IHeaderCellProps } from "./headerCell";
 
 export interface IColumnNameProps {
@@ -196,7 +197,7 @@ export class ColumnHeaderCell extends AbstractPureComponent2<IColumnHeaderCellPr
             return undefined;
         }
 
-        const classes = classNames(Classes.TABLE_TH_MENU_CONTAINER, {
+        const classes = classNames(Classes.TABLE_TH_MENU_CONTAINER, CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT, {
             [Classes.TABLE_TH_MENU_OPEN]: this.state.isActive,
         });
 
diff --git a/packages/table/src/headers/header.tsx b/packages/table/src/headers/header.tsx
index 86d69c6877..101622bf92 100644
--- a/packages/table/src/headers/header.tsx
+++ b/packages/table/src/headers/header.tsx
@@ -21,6 +21,7 @@ import * as React from "react";
 import { Grid } from "../common";
 import { IFocusedCellCoordinates } from "../common/cell";
 import * as Classes from "../common/classes";
+import { CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT } from "../common/utils";
 import { DragEvents } from "../interactions/dragEvents";
 import { IClientCoordinates, ICoordinateData } from "../interactions/draggable";
 import { DragReorderable, IReorderableProps } from "../interactions/reorderable";
@@ -384,7 +385,9 @@ export class Header extends React.Component<IInternalHeaderProps, IHeaderState>
             : this.wrapInDragReorderable(
                   index,
                   <div className={Classes.TABLE_REORDER_HANDLE_TARGET}>
-                      <div className={Classes.TABLE_REORDER_HANDLE}>
+                      <div
+                          className={classNames(Classes.TABLE_REORDER_HANDLE, CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT)}
+                      >
                           <Icon icon="drag-handle-vertical" />
                       </div>
                   </div>,