From 8ab3440af15ab9e029d974acaf71dd6bcdc5de27 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:08:56 -0500 Subject: [PATCH 01/17] Enable table column resizing on touch-based devices --- packages/nimble-components/src/table/index.ts | 24 +++++++++++++++++++ .../src/table/models/table-layout-manager.ts | 23 +++++++++++++----- .../nimble-components/src/table/template.ts | 6 +++-- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/packages/nimble-components/src/table/index.ts b/packages/nimble-components/src/table/index.ts index 1c57530428..0037d64934 100644 --- a/packages/nimble-components/src/table/index.ts +++ b/packages/nimble-components/src/table/index.ts @@ -557,6 +557,18 @@ export class Table< } } + /** @internal */ + public onRightDividerTouchStart( + event: TouchEvent, + columnIndex: number + ): void { + this.layoutManager.beginColumnInteractiveSize( + event.targetTouches[0]!.clientX, + this.getRightDividerIndex(columnIndex) + ); + event.preventDefault(); + } + /** @internal */ public onLeftDividerMouseDown( event: MouseEvent, @@ -570,6 +582,18 @@ export class Table< } } + /** @internal */ + public onLeftDividerTouchStart( + event: TouchEvent, + columnIndex: number + ): void { + this.layoutManager.beginColumnInteractiveSize( + event.targetTouches[0]!.clientX, + this.getLeftDividerIndex(columnIndex) + ); + event.preventDefault(); + } + /** @internal */ public getLeftDividerIndex(columnIndex: number): number { return columnIndex * 2 - 1; diff --git a/packages/nimble-components/src/table/models/table-layout-manager.ts b/packages/nimble-components/src/table/models/table-layout-manager.ts index 6c20273494..faddf66ad1 100644 --- a/packages/nimble-components/src/table/models/table-layout-manager.ts +++ b/packages/nimble-components/src/table/models/table-layout-manager.ts @@ -78,7 +78,9 @@ export class TableLayoutManager { this.initialColumnTotalWidth = this.getTotalColumnFixedWidth(); this.isColumnBeingSized = true; document.addEventListener('mousemove', this.onDividerMouseMove); - document.addEventListener('mouseup', this.onDividerMouseUp); + document.addEventListener('touchmove', this.onDividerTouchMove); + document.addEventListener('mouseup', this.onDividerMoveEnd); + document.addEventListener('touchend', this.onDividerMoveEnd); } /** @@ -96,12 +98,19 @@ export class TableLayoutManager { } private readonly onDividerMouseMove = (event: Event): void => { - const mouseEvent = event as MouseEvent; + this.onDividerMove((event as MouseEvent).clientX); + }; + + private readonly onDividerTouchMove = (event: Event): void => { + this.onDividerMove((event as TouchEvent).targetTouches[0]!.clientX); + }; + + private onDividerMove(clientX: number): void { for (let i = 0; i < this.visibleColumns.length; i++) { this.visibleColumns[i]!.columnInternals.currentPixelWidth = this.initialColumnWidths[i]?.initialPixelWidth; } this.currentTotalDelta = this.getAllowedSizeDelta( - mouseEvent.clientX - this.dragStart + clientX - this.dragStart ); this.performCascadeSizeLeft( this.leftColumnIndex!, @@ -118,11 +127,13 @@ export class TableLayoutManager { } else { this.table.tableScrollableMinWidth = this.initialTableScrollableMinWidth!; } - }; + } - private readonly onDividerMouseUp = (): void => { + private readonly onDividerMoveEnd = (): void => { document.removeEventListener('mousemove', this.onDividerMouseMove); - document.removeEventListener('mouseup', this.onDividerMouseUp); + document.removeEventListener('touchmove', this.onDividerTouchMove); + document.removeEventListener('mouseup', this.onDividerMoveEnd); + document.removeEventListener('touchend', this.onDividerMoveEnd); this.resetGridSizedColumns(); this.isColumnBeingSized = false; this.activeColumnIndex = undefined; diff --git a/packages/nimble-components/src/table/template.ts b/packages/nimble-components/src/table/template.ts index a76d990750..f7ca6d2485 100644 --- a/packages/nimble-components/src/table/template.ts +++ b/packages/nimble-components/src/table/template.ts @@ -103,7 +103,8 @@ export const template = html` ${(_, c) => `${c.parent.layoutManager.activeColumnDivider === c.parent.getLeftDividerIndex(c.index) ? 'divider-active' : ''}`} ${(_, c) => `${c.parent.layoutManager.hasResizableColumnToLeft(c.index - 1) ? 'draggable' : ''}`} " - @mousedown="${(_, c) => c.parent.onLeftDividerMouseDown(c.event as MouseEvent, c.index)}"> + @mousedown="${(_, c) => c.parent.onLeftDividerMouseDown(c.event as MouseEvent, c.index)}" + @touchstart="${(_, c) => c.parent.onLeftDividerTouchStart(c.event as TouchEvent, c.index)}"> `)} <${tableHeaderTag} @@ -128,7 +129,8 @@ export const template = html
` ${(_, c) => `${c.parent.layoutManager.activeColumnDivider === c.parent.getRightDividerIndex(c.index) ? 'divider-active' : ''}`} ${(_, c) => `${c.parent.layoutManager.hasResizableColumnToLeft(c.index) ? 'draggable' : ''}`} " - @mousedown="${(_, c) => c.parent.onRightDividerMouseDown(c.event as MouseEvent, c.index)}"> + @mousedown="${(_, c) => c.parent.onRightDividerMouseDown(c.event as MouseEvent, c.index)}" + @touchstart="${(_, c) => c.parent.onRightDividerTouchStart(c.event as TouchEvent, c.index)}"> `)} From f2cd5e90fa79030e15265c888b0b8675c6c80f29 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:09:19 -0500 Subject: [PATCH 02/17] Change files --- ...le-components-3680232d-ba1a-47f0-8cdc-dc6effd176ed.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@ni-nimble-components-3680232d-ba1a-47f0-8cdc-dc6effd176ed.json diff --git a/change/@ni-nimble-components-3680232d-ba1a-47f0-8cdc-dc6effd176ed.json b/change/@ni-nimble-components-3680232d-ba1a-47f0-8cdc-dc6effd176ed.json new file mode 100644 index 0000000000..3bfa6582d2 --- /dev/null +++ b/change/@ni-nimble-components-3680232d-ba1a-47f0-8cdc-dc6effd176ed.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Enable table column resizing on touch-based devices", + "packageName": "@ni/nimble-components", + "email": "7282195+m-akinc@users.noreply.github.com", + "dependentChangeType": "patch" +} From 7e2f460d818fdfa78bcb95578da3815e7a07af3c Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:47:12 -0500 Subject: [PATCH 03/17] Switch to using pointer events for column resizing --- packages/nimble-components/src/table/index.ts | 38 +++-------- .../src/table/models/table-layout-manager.ts | 28 +++----- .../nimble-components/src/table/styles.ts | 1 + .../nimble-components/src/table/template.ts | 6 +- .../src/table/testing/table.pageobject.ts | 64 ++++++++----------- 5 files changed, 46 insertions(+), 91 deletions(-) diff --git a/packages/nimble-components/src/table/index.ts b/packages/nimble-components/src/table/index.ts index 0037d64934..ed1d23c2a2 100644 --- a/packages/nimble-components/src/table/index.ts +++ b/packages/nimble-components/src/table/index.ts @@ -545,55 +545,33 @@ export class Table< } /** @internal */ - public onRightDividerMouseDown( - event: MouseEvent, + public onRightDividerPointerDown( + event: PointerEvent, columnIndex: number ): void { - if (event.button === 0) { + if (event.pointerType !== 'mouse' || event.button === 0) { this.layoutManager.beginColumnInteractiveSize( event.clientX, this.getRightDividerIndex(columnIndex) ); + event.preventDefault(); } } /** @internal */ - public onRightDividerTouchStart( - event: TouchEvent, + public onLeftDividerPointerDown( + event: PointerEvent, columnIndex: number ): void { - this.layoutManager.beginColumnInteractiveSize( - event.targetTouches[0]!.clientX, - this.getRightDividerIndex(columnIndex) - ); - event.preventDefault(); - } - - /** @internal */ - public onLeftDividerMouseDown( - event: MouseEvent, - columnIndex: number - ): void { - if (event.button === 0) { + if (event.pointerType !== 'mouse' || event.button === 0) { this.layoutManager.beginColumnInteractiveSize( event.clientX, this.getLeftDividerIndex(columnIndex) ); + event.preventDefault(); } } - /** @internal */ - public onLeftDividerTouchStart( - event: TouchEvent, - columnIndex: number - ): void { - this.layoutManager.beginColumnInteractiveSize( - event.targetTouches[0]!.clientX, - this.getLeftDividerIndex(columnIndex) - ); - event.preventDefault(); - } - /** @internal */ public getLeftDividerIndex(columnIndex: number): number { return columnIndex * 2 - 1; diff --git a/packages/nimble-components/src/table/models/table-layout-manager.ts b/packages/nimble-components/src/table/models/table-layout-manager.ts index faddf66ad1..2133e78780 100644 --- a/packages/nimble-components/src/table/models/table-layout-manager.ts +++ b/packages/nimble-components/src/table/models/table-layout-manager.ts @@ -77,10 +77,8 @@ export class TableLayoutManager { this.initialTableScrollableMinWidth = this.table.tableScrollableMinWidth; this.initialColumnTotalWidth = this.getTotalColumnFixedWidth(); this.isColumnBeingSized = true; - document.addEventListener('mousemove', this.onDividerMouseMove); - document.addEventListener('touchmove', this.onDividerTouchMove); - document.addEventListener('mouseup', this.onDividerMoveEnd); - document.addEventListener('touchend', this.onDividerMoveEnd); + document.addEventListener('pointermove', this.onDividerPointerMove); + document.addEventListener('pointerup', this.onDividerPointerUp); } /** @@ -97,20 +95,12 @@ export class TableLayoutManager { return this.getFirstRightResizableColumnIndex(columnIndex) !== -1; } - private readonly onDividerMouseMove = (event: Event): void => { - this.onDividerMove((event as MouseEvent).clientX); - }; - - private readonly onDividerTouchMove = (event: Event): void => { - this.onDividerMove((event as TouchEvent).targetTouches[0]!.clientX); - }; - - private onDividerMove(clientX: number): void { + private readonly onDividerPointerMove = (event: PointerEvent): void => { for (let i = 0; i < this.visibleColumns.length; i++) { this.visibleColumns[i]!.columnInternals.currentPixelWidth = this.initialColumnWidths[i]?.initialPixelWidth; } this.currentTotalDelta = this.getAllowedSizeDelta( - clientX - this.dragStart + event.clientX - this.dragStart ); this.performCascadeSizeLeft( this.leftColumnIndex!, @@ -127,13 +117,11 @@ export class TableLayoutManager { } else { this.table.tableScrollableMinWidth = this.initialTableScrollableMinWidth!; } - } + }; - private readonly onDividerMoveEnd = (): void => { - document.removeEventListener('mousemove', this.onDividerMouseMove); - document.removeEventListener('touchmove', this.onDividerTouchMove); - document.removeEventListener('mouseup', this.onDividerMoveEnd); - document.removeEventListener('touchend', this.onDividerMoveEnd); + private readonly onDividerPointerUp = (): void => { + document.removeEventListener('pointermove', this.onDividerPointerMove); + document.removeEventListener('pointerup', this.onDividerPointerUp); this.resetGridSizedColumns(); this.isColumnBeingSized = false; this.activeColumnIndex = undefined; diff --git a/packages/nimble-components/src/table/styles.ts b/packages/nimble-components/src/table/styles.ts index 6f8b73e4eb..606af6b181 100644 --- a/packages/nimble-components/src/table/styles.ts +++ b/packages/nimble-components/src/table/styles.ts @@ -92,6 +92,7 @@ export const styles = css` ); left: var(--ni-private-table-scroll-x); align-items: center; + touch-action: pan-y; } .header-row-action-container { diff --git a/packages/nimble-components/src/table/template.ts b/packages/nimble-components/src/table/template.ts index f7ca6d2485..79acb60741 100644 --- a/packages/nimble-components/src/table/template.ts +++ b/packages/nimble-components/src/table/template.ts @@ -103,8 +103,7 @@ export const template = html
` ${(_, c) => `${c.parent.layoutManager.activeColumnDivider === c.parent.getLeftDividerIndex(c.index) ? 'divider-active' : ''}`} ${(_, c) => `${c.parent.layoutManager.hasResizableColumnToLeft(c.index - 1) ? 'draggable' : ''}`} " - @mousedown="${(_, c) => c.parent.onLeftDividerMouseDown(c.event as MouseEvent, c.index)}" - @touchstart="${(_, c) => c.parent.onLeftDividerTouchStart(c.event as TouchEvent, c.index)}"> + @pointerdown="${(_, c) => c.parent.onLeftDividerPointerDown(c.event as PointerEvent, c.index)}"> `)} <${tableHeaderTag} @@ -129,8 +128,7 @@ export const template = html
` ${(_, c) => `${c.parent.layoutManager.activeColumnDivider === c.parent.getRightDividerIndex(c.index) ? 'divider-active' : ''}`} ${(_, c) => `${c.parent.layoutManager.hasResizableColumnToLeft(c.index) ? 'draggable' : ''}`} " - @mousedown="${(_, c) => c.parent.onRightDividerMouseDown(c.event as MouseEvent, c.index)}" - @touchstart="${(_, c) => c.parent.onRightDividerTouchStart(c.event as TouchEvent, c.index)}"> + @pointerdown="${(_, c) => c.parent.onRightDividerPointerDown(c.event as PointerEvent, c.index)}"> `)} diff --git a/packages/nimble-components/src/table/testing/table.pageobject.ts b/packages/nimble-components/src/table/testing/table.pageobject.ts index 24f75ae45c..812012a371 100644 --- a/packages/nimble-components/src/table/testing/table.pageobject.ts +++ b/packages/nimble-components/src/table/testing/table.pageobject.ts @@ -541,24 +541,7 @@ export class TablePageObject { 'The provided column index has no right divider associated with it.' ); } - const dividerRect = divider.getBoundingClientRect(); - let currentMouseX = (dividerRect.x + dividerRect.width) / 2; - const mouseDownEvent = new MouseEvent('mousedown', { - clientX: currentMouseX, - clientY: (dividerRect.y + dividerRect.height) / 2 - }); - divider.dispatchEvent(mouseDownEvent); - - for (const delta of deltas) { - currentMouseX += delta; - const mouseMoveEvent = new MouseEvent('mousemove', { - clientX: currentMouseX - }); - document.dispatchEvent(mouseMoveEvent); - } - - const mouseUpEvent = new MouseEvent('mouseup'); - document.dispatchEvent(mouseUpEvent); + this.dragSizeColumnByDivider(divider, deltas); } /** @@ -576,24 +559,7 @@ export class TablePageObject { 'The provided column index has no left divider associated with it.' ); } - const dividerRect = divider.getBoundingClientRect(); - let currentMouseX = (dividerRect.x + dividerRect.width) / 2; - const mouseDownEvent = new MouseEvent('mousedown', { - clientX: currentMouseX, - clientY: (dividerRect.y + dividerRect.height) / 2 - }); - divider.dispatchEvent(mouseDownEvent); - - for (const delta of deltas) { - currentMouseX += delta; - const mouseMoveEvent = new MouseEvent('mousemove', { - clientX: currentMouseX - }); - document.dispatchEvent(mouseMoveEvent); - } - - const mouseUpEvent = new MouseEvent('mouseup'); - document.dispatchEvent(mouseUpEvent); + this.dragSizeColumnByDivider(divider, deltas); } public getColumnRightDivider(index: number): HTMLElement | null { @@ -855,9 +821,33 @@ export class TablePageObject { return spinnerOrIcon; } + private dragSizeColumnByDivider( + divider: HTMLElement, + deltas: readonly number[] + ): void { + const dividerRect = divider.getBoundingClientRect(); + let currentPointerX = (dividerRect.x + dividerRect.width) / 2; + const mouseDownEvent = new PointerEvent('pointerdown', { + clientX: currentPointerX, + clientY: (dividerRect.y + dividerRect.height) / 2 + }); + divider.dispatchEvent(mouseDownEvent); + + for (const delta of deltas) { + currentPointerX += delta; + const pointerMoveEvent = new PointerEvent('pointermove', { + clientX: currentPointerX + }); + document.dispatchEvent(pointerMoveEvent); + } + + const pointerUpEvent = new PointerEvent('pointerup'); + document.dispatchEvent(pointerUpEvent); + } + private readonly isSlotElement = ( element: Node | undefined ): element is HTMLSlotElement => { - return element?.nodeName === 'SLOT' ?? false; + return element?.nodeName === 'SLOT'; }; } From ebbbf9f42ada7e53b8060d2bbdecb761161d5e49 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:53:14 -0500 Subject: [PATCH 04/17] Don't think we need to preventDefault after all --- packages/nimble-components/src/table/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/nimble-components/src/table/index.ts b/packages/nimble-components/src/table/index.ts index ed1d23c2a2..a0820ca17b 100644 --- a/packages/nimble-components/src/table/index.ts +++ b/packages/nimble-components/src/table/index.ts @@ -554,7 +554,6 @@ export class Table< event.clientX, this.getRightDividerIndex(columnIndex) ); - event.preventDefault(); } } @@ -568,7 +567,6 @@ export class Table< event.clientX, this.getLeftDividerIndex(columnIndex) ); - event.preventDefault(); } } From 64e6224dfbfe9d877f390c6cd5de6a3668bb0d21 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:14:03 -0500 Subject: [PATCH 05/17] Move touch-action setting to divider --- packages/nimble-components/src/table/styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nimble-components/src/table/styles.ts b/packages/nimble-components/src/table/styles.ts index 606af6b181..9c50583a47 100644 --- a/packages/nimble-components/src/table/styles.ts +++ b/packages/nimble-components/src/table/styles.ts @@ -92,7 +92,6 @@ export const styles = css` ); left: var(--ni-private-table-scroll-x); align-items: center; - touch-action: pan-y; } .header-row-action-container { @@ -151,6 +150,7 @@ export const styles = css` cursor: col-resize; position: absolute; z-index: ${ZIndexLevels.zIndex1}; + touch-action: pan-y; } .column-divider:hover, From a5baa253c55891a42dee2fdf83a0bcacaa9f83d5 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Mon, 14 Oct 2024 13:25:38 -0500 Subject: [PATCH 06/17] Change tests to use PointerEvent instead of MouseEvent --- .../src/table/tests/table-column-sizing.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts b/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts index 60a2d2ee1a..be742af65d 100644 --- a/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts +++ b/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts @@ -1056,11 +1056,11 @@ describe('Table Interactive Column Sizing', () => { ); const divider = dividers[value.dividerClickIndex]!; const dividerRect = divider.getBoundingClientRect(); - const mouseDownEvent = new MouseEvent('mousedown', { + const mouseDownEvent = new PointerEvent('pointerdown', { clientX: (dividerRect.x + dividerRect.width) / 2, clientY: (dividerRect.y + dividerRect.height) / 2 }); - const mouseUpEvent = new MouseEvent('mouseup'); + const mouseUpEvent = new PointerEvent('pointerup'); divider.dispatchEvent(mouseDownEvent); await waitForUpdatesAsync(); const dividerActiveDividers = []; @@ -1105,7 +1105,7 @@ describe('Table Interactive Column Sizing', () => { it('after releasing divider, it is no longer marked as active', async () => { const divider = pageObject.getColumnRightDivider(0)!; const dividerRect = divider.getBoundingClientRect(); - const mouseDownEvent = new MouseEvent('mousedown', { + const mouseDownEvent = new PointerEvent('pointerdown', { clientX: (dividerRect.x + dividerRect.width) / 2, clientY: (dividerRect.y + dividerRect.height) / 2 }); @@ -1113,7 +1113,7 @@ describe('Table Interactive Column Sizing', () => { await waitForUpdatesAsync(); expect(divider.classList.contains('divider-active')).toBeTruthy(); - const mouseUpEvent = new MouseEvent('mouseup'); + const mouseUpEvent = new PointerEvent('pointerup'); document.dispatchEvent(mouseUpEvent); await waitForUpdatesAsync(); expect(divider.classList.contains('divider-active')).toBeFalsy(); From 3539ee89fc05abc454b4140a43ef10072219773f Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:52:55 -0500 Subject: [PATCH 07/17] Use pointer capture and get rid of glass overlay div --- packages/nimble-components/src/table/index.ts | 4 + .../src/table/models/table-layout-manager.ts | 23 +- .../nimble-components/src/table/styles.ts | 7 - .../nimble-components/src/table/template.ts | 281 +++++++++--------- 4 files changed, 161 insertions(+), 154 deletions(-) diff --git a/packages/nimble-components/src/table/index.ts b/packages/nimble-components/src/table/index.ts index a0820ca17b..534b4e6477 100644 --- a/packages/nimble-components/src/table/index.ts +++ b/packages/nimble-components/src/table/index.ts @@ -551,6 +551,8 @@ export class Table< ): void { if (event.pointerType !== 'mouse' || event.button === 0) { this.layoutManager.beginColumnInteractiveSize( + event.target as HTMLElement, + event.pointerId, event.clientX, this.getRightDividerIndex(columnIndex) ); @@ -564,6 +566,8 @@ export class Table< ): void { if (event.pointerType !== 'mouse' || event.button === 0) { this.layoutManager.beginColumnInteractiveSize( + event.target as HTMLElement, + event.pointerId, event.clientX, this.getLeftDividerIndex(columnIndex) ); diff --git a/packages/nimble-components/src/table/models/table-layout-manager.ts b/packages/nimble-components/src/table/models/table-layout-manager.ts index 2133e78780..7ac07cd280 100644 --- a/packages/nimble-components/src/table/models/table-layout-manager.ts +++ b/packages/nimble-components/src/table/models/table-layout-manager.ts @@ -22,6 +22,7 @@ export class TableLayoutManager { private initialTableScrollableWidth?: number; private initialTableScrollableMinWidth?: number; private initialColumnTotalWidth?: number; + private activeColumnDividerElement?: HTMLElement; private currentTotalDelta = 0; private dragStart = 0; private leftColumnIndex?: number; @@ -56,14 +57,19 @@ export class TableLayoutManager { /** * Sets up state related to interactively sizing a column. + * @param divider The divider element that was clicked on + * @param pointerId The pointerId of the pointer that started the drag * @param dragStart The x-position from which a column size was started - * @param activeColumnDivider The divider that was clicked on + * @param activeColumnDivider The 1-based index of the divider that was clicked on */ public beginColumnInteractiveSize( + divider: HTMLElement, + pointerId: number, dragStart: number, activeColumnDivider: number ): void { this.activeColumnDivider = activeColumnDivider; + this.activeColumnDividerElement = divider; this.leftColumnIndex = this.getLeftColumnIndexFromDivider( this.activeColumnDivider ); @@ -77,8 +83,9 @@ export class TableLayoutManager { this.initialTableScrollableMinWidth = this.table.tableScrollableMinWidth; this.initialColumnTotalWidth = this.getTotalColumnFixedWidth(); this.isColumnBeingSized = true; - document.addEventListener('pointermove', this.onDividerPointerMove); - document.addEventListener('pointerup', this.onDividerPointerUp); + divider.setPointerCapture(pointerId); + divider.addEventListener('pointermove', this.onDividerPointerMove); + divider.addEventListener('pointerup', this.onDividerPointerUp); } /** @@ -120,8 +127,14 @@ export class TableLayoutManager { }; private readonly onDividerPointerUp = (): void => { - document.removeEventListener('pointermove', this.onDividerPointerMove); - document.removeEventListener('pointerup', this.onDividerPointerUp); + this.activeColumnDividerElement!.removeEventListener( + 'pointermove', + this.onDividerPointerMove + ); + this.activeColumnDividerElement!.removeEventListener( + 'pointerup', + this.onDividerPointerUp + ); this.resetGridSizedColumns(); this.isColumnBeingSized = false; this.activeColumnIndex = undefined; diff --git a/packages/nimble-components/src/table/styles.ts b/packages/nimble-components/src/table/styles.ts index 9c50583a47..9e5beec1a4 100644 --- a/packages/nimble-components/src/table/styles.ts +++ b/packages/nimble-components/src/table/styles.ts @@ -66,13 +66,6 @@ export const styles = css` cursor: var(--ni-private-table-cursor-override); } - .glass-overlay { - width: 100%; - height: 100%; - display: contents; - pointer-events: var(--ni-private-glass-overlay-pointer-events); - } - .header-row-container { position: sticky; top: 0; diff --git a/packages/nimble-components/src/table/template.ts b/packages/nimble-components/src/table/template.ts index 79acb60741..0c99888519 100644 --- a/packages/nimble-components/src/table/template.ts +++ b/packages/nimble-components/src/table/template.ts @@ -50,159 +50,156 @@ export const template = html
` --ni-private-table-row-grid-columns: ${x => (x.rowGridColumns ? x.rowGridColumns : '')}; --ni-private-table-cursor-override: ${x => (x.layoutManager.isColumnBeingSized ? 'col-resize' : 'default')}; --ni-private-table-scrollable-min-width: ${x => x.tableScrollableMinWidth}px; - --ni-private-glass-overlay-pointer-events: ${x => (x.layoutManager.isColumnBeingSized ? 'none' : 'default')}; "> -
-
-
- - ${when(x => x.showRowOperationColumn, html
` - - ${x => tableRowOperationColumnLabel.getValueFor(x)} - - `)} - ${when(x => x.selectionMode === TableRowSelectionMode.multiple, html
` - - <${checkboxTag} - ${ref('selectionCheckbox')} - ${'' /* tabindex managed dynamically by KeyboardNavigationManager */} - tabindex="-1" - class="selection-checkbox" - @change="${(x, c) => x.onAllRowsSelectionChange(c.event as CustomEvent)}" - title="${x => tableSelectAllLabel.getValueFor(x)}" - aria-label="${x => tableSelectAllLabel.getValueFor(x)}" - > - - - `)} - - <${buttonTag} - ${ref('collapseAllButton')} +
+
+ + ${when(x => x.showRowOperationColumn, html
` + + ${x => tableRowOperationColumnLabel.getValueFor(x)} + + `)} + ${when(x => x.selectionMode === TableRowSelectionMode.multiple, html
` + + <${checkboxTag} + ${ref('selectionCheckbox')} ${'' /* tabindex managed dynamically by KeyboardNavigationManager */} tabindex="-1" - class="collapse-all-button ${x => x.collapseButtonVisibility}" - content-hidden - appearance="${ButtonAppearance.ghost}" - title="${x => tableCollapseAllLabel.getValueFor(x)}" - @click="${x => x.handleCollapseAllRows()}" + class="selection-checkbox" + @change="${(x, c) => x.onAllRowsSelectionChange(c.event as CustomEvent)}" + title="${x => tableSelectAllLabel.getValueFor(x)}" + aria-label="${x => tableSelectAllLabel.getValueFor(x)}" > - <${iconTriangleTwoLinesHorizontalTag} slot="start"> - ${x => tableCollapseAllLabel.getValueFor(x)} - + + `)} + + <${buttonTag} + ${ref('collapseAllButton')} + ${'' /* tabindex managed dynamically by KeyboardNavigationManager */} + tabindex="-1" + class="collapse-all-button ${x => x.collapseButtonVisibility}" + content-hidden + appearance="${ButtonAppearance.ghost}" + title="${x => tableCollapseAllLabel.getValueFor(x)}" + @click="${x => x.handleCollapseAllRows()}" + > + <${iconTriangleTwoLinesHorizontalTag} slot="start"> + ${x => tableCollapseAllLabel.getValueFor(x)} + - - ${repeat(x => x.visibleColumns, html` -
- ${when((_, c) => c.index > 0, html` -
-
- `)} - <${tableHeaderTag} - class="header" - ${'' /* tabindex managed dynamically by KeyboardNavigationManager (if column sorting not disabled) */} - sort-direction="${x => (typeof x.columnInternals.currentSortIndex === 'number' ? x.columnInternals.currentSortDirection : TableColumnSortDirection.none)}" - ?first-sorted-column="${(x, c) => x === c.parent.firstSortedColumn}" - ?indicators-hidden="${x => x.columnInternals.hideHeaderIndicators}" - @keydown="${(x, c) => c.parent.onHeaderKeyDown(x, c.event as KeyboardEvent)}" - @click="${(x, c) => c.parent.toggleColumnSort(x, (c.event as MouseEvent).shiftKey)}" - :alignment="${x => x.columnInternals.headerAlignment}" - :isGrouped=${x => (typeof x.columnInternals.groupIndex === 'number' && !x.columnInternals.groupingDisabled)} - > - - - ${when((_, c) => c.index < c.length - 1, html` -
-
- `)} -
- `, { positioning: true })} -
-
- - -
-
-
- ${when(x => x.columns.length > 0 && x.canRenderRows, html
` - ${repeat(x => x.virtualizer.visibleItems, html` - ${when((x, c: ExecutionContext
) => c.parent.tableData[x.index]?.isGroupRow, html` - <${tableGroupRowTag} - class="group-row" - ${'' /* tabindex managed dynamically by KeyboardNavigationManager */} - tabindex="-1" - :groupRowValue="${(x, c) => c.parent.tableData[x.index]?.groupRowValue}" - ?expanded="${(x, c) => c.parent.tableData[x.index]?.isExpanded}" - :nestingLevel="${(x, c) => c.parent.tableData[x.index]?.nestingLevel}" - :immediateChildCount="${(x, c) => c.parent.tableData[x.index]?.immediateChildCount}" - :groupColumn="${(x, c) => c.parent.tableData[x.index]?.groupColumn}" - ?selectable="${(_, c) => c.parent.selectionMode === TableRowSelectionMode.multiple}" - selection-state="${(x, c) => c.parent.tableData[x.index]?.selectionState}" - :resolvedRowIndex="${x => x.index}" - ?allow-hover="${(_, c) => !c.parent.virtualizer.isScrolling}" - @focusin="${(_, c) => c.parent.onRowFocusIn(c.event as FocusEvent)}" - @blur="${(_, c) => c.parent.onRowBlur(c.event as FocusEvent)}" - @group-selection-toggle="${(x, c) => c.parent.onRowSelectionToggle(x.index, c.event as CustomEvent)}" - @group-expand-toggle="${(x, c) => c.parent.handleGroupRowExpanded(x.index, c.event)}" - > - + + + ${repeat(x => x.visibleColumns, html` +
+ ${when((_, c) => c.index > 0, html` +
+
`)} - ${when((x, c: ExecutionContext
) => !c.parent.tableData[x.index]?.isGroupRow, html` - <${tableRowTag} - class="row" - ${'' /* tabindex managed dynamically by KeyboardNavigationManager */} - tabindex="-1" - record-id="${(x, c) => c.parent.tableData[x.index]?.id}" - ?selectable="${(_, c) => c.parent.selectionMode !== TableRowSelectionMode.none}" - ?selected="${(x, c) => c.parent.tableData[x.index]?.selectionState === TableRowSelectionState.selected}" - ?expanded="${(x, c) => c.parent.tableData[x.index]?.isExpanded}" - ?hide-selection="${(_, c) => c.parent.selectionMode !== TableRowSelectionMode.multiple}" - ?reserve-collapse-space="${(_, c) => c.parent.canHaveCollapsibleRows}" - :dataRecord="${(x, c) => c.parent.tableData[x.index]?.record}" - :columns="${(_, c) => c.parent.columns}" - :isParentRow="${(x, c) => c.parent.tableData[x.index]?.isParentRow}" - :nestingLevel="${(x, c) => c.parent.tableData[x.index]?.nestingLevel}" - ?row-operation-grid-cell-hidden="${(_, c) => !c.parent.showRowOperationColumn}" - ?loading="${(x, c) => c.parent.tableData[x.index]?.isLoadingChildren}" - :resolvedRowIndex="${x => x.index}" - ?allow-hover="${(_, c) => !c.parent.virtualizer.isScrolling}" - @click="${(x, c) => c.parent.onRowClick(x.index, c.event as MouseEvent)}" - @focusin="${(_, c) => c.parent.onRowFocusIn(c.event as FocusEvent)}" - @blur="${(_, c) => c.parent.onRowBlur(c.event as FocusEvent)}" - @row-selection-toggle="${(x, c) => c.parent.onRowSelectionToggle(x.index, c.event as CustomEvent)}" - @row-action-menu-beforetoggle="${(x, c) => c.parent.onRowActionMenuBeforeToggle(x.index, c.event as CustomEvent)}" - @row-action-menu-toggle="${(_, c) => c.parent.onRowActionMenuToggle(c.event as CustomEvent)}" - @row-slots-request="${(_, c) => c.parent.onRowSlotsRequest(c.event as CustomEvent)}" - @row-expand-toggle="${(x, c) => c.parent.handleRowExpanded(x.index)}" + <${tableHeaderTag} + class="header" + ${'' /* tabindex managed dynamically by KeyboardNavigationManager (if column sorting not disabled) */} + sort-direction="${x => (typeof x.columnInternals.currentSortIndex === 'number' ? x.columnInternals.currentSortDirection : TableColumnSortDirection.none)}" + ?first-sorted-column="${(x, c) => x === c.parent.firstSortedColumn}" + ?indicators-hidden="${x => x.columnInternals.hideHeaderIndicators}" + @keydown="${(x, c) => c.parent.onHeaderKeyDown(x, c.event as KeyboardEvent)}" + @click="${(x, c) => c.parent.toggleColumnSort(x, (c.event as MouseEvent).shiftKey)}" + :alignment="${x => x.columnInternals.headerAlignment}" + :isGrouped=${x => (typeof x.columnInternals.groupIndex === 'number' && !x.columnInternals.groupingDisabled)} > - ${repeat((x, c: ExecutionContext
) => (c.parent.tableData[x.index]?.requestedSlots || []), html` - - `)} - + + + ${when((_, c) => c.index < c.length - 1, html` +
+
+ `)} + + `, { positioning: true })} +
+ + + +
+
+
+ ${when(x => x.columns.length > 0 && x.canRenderRows, html
` + ${repeat(x => x.virtualizer.visibleItems, html` + ${when((x, c: ExecutionContext
) => c.parent.tableData[x.index]?.isGroupRow, html` + <${tableGroupRowTag} + class="group-row" + ${'' /* tabindex managed dynamically by KeyboardNavigationManager */} + tabindex="-1" + :groupRowValue="${(x, c) => c.parent.tableData[x.index]?.groupRowValue}" + ?expanded="${(x, c) => c.parent.tableData[x.index]?.isExpanded}" + :nestingLevel="${(x, c) => c.parent.tableData[x.index]?.nestingLevel}" + :immediateChildCount="${(x, c) => c.parent.tableData[x.index]?.immediateChildCount}" + :groupColumn="${(x, c) => c.parent.tableData[x.index]?.groupColumn}" + ?selectable="${(_, c) => c.parent.selectionMode === TableRowSelectionMode.multiple}" + selection-state="${(x, c) => c.parent.tableData[x.index]?.selectionState}" + :resolvedRowIndex="${x => x.index}" + ?allow-hover="${(_, c) => !c.parent.virtualizer.isScrolling}" + @focusin="${(_, c) => c.parent.onRowFocusIn(c.event as FocusEvent)}" + @blur="${(_, c) => c.parent.onRowBlur(c.event as FocusEvent)}" + @group-selection-toggle="${(x, c) => c.parent.onRowSelectionToggle(x.index, c.event as CustomEvent)}" + @group-expand-toggle="${(x, c) => c.parent.handleGroupRowExpanded(x.index, c.event)}" + > + + `)} + ${when((x, c: ExecutionContext
) => !c.parent.tableData[x.index]?.isGroupRow, html` + <${tableRowTag} + class="row" + ${'' /* tabindex managed dynamically by KeyboardNavigationManager */} + tabindex="-1" + record-id="${(x, c) => c.parent.tableData[x.index]?.id}" + ?selectable="${(_, c) => c.parent.selectionMode !== TableRowSelectionMode.none}" + ?selected="${(x, c) => c.parent.tableData[x.index]?.selectionState === TableRowSelectionState.selected}" + ?expanded="${(x, c) => c.parent.tableData[x.index]?.isExpanded}" + ?hide-selection="${(_, c) => c.parent.selectionMode !== TableRowSelectionMode.multiple}" + ?reserve-collapse-space="${(_, c) => c.parent.canHaveCollapsibleRows}" + :dataRecord="${(x, c) => c.parent.tableData[x.index]?.record}" + :columns="${(_, c) => c.parent.columns}" + :isParentRow="${(x, c) => c.parent.tableData[x.index]?.isParentRow}" + :nestingLevel="${(x, c) => c.parent.tableData[x.index]?.nestingLevel}" + ?row-operation-grid-cell-hidden="${(_, c) => !c.parent.showRowOperationColumn}" + ?loading="${(x, c) => c.parent.tableData[x.index]?.isLoadingChildren}" + :resolvedRowIndex="${x => x.index}" + ?allow-hover="${(_, c) => !c.parent.virtualizer.isScrolling}" + @click="${(x, c) => c.parent.onRowClick(x.index, c.event as MouseEvent)}" + @focusin="${(_, c) => c.parent.onRowFocusIn(c.event as FocusEvent)}" + @blur="${(_, c) => c.parent.onRowBlur(c.event as FocusEvent)}" + @row-selection-toggle="${(x, c) => c.parent.onRowSelectionToggle(x.index, c.event as CustomEvent)}" + @row-action-menu-beforetoggle="${(x, c) => c.parent.onRowActionMenuBeforeToggle(x.index, c.event as CustomEvent)}" + @row-action-menu-toggle="${(_, c) => c.parent.onRowActionMenuToggle(c.event as CustomEvent)}" + @row-slots-request="${(_, c) => c.parent.onRowSlotsRequest(c.event as CustomEvent)}" + @row-expand-toggle="${(x, c) => c.parent.handleRowExpanded(x.index)}" + > + ${repeat((x, c: ExecutionContext
) => (c.parent.tableData[x.index]?.requestedSlots || []), html` + `)} + `)} `)} - + `)} From 70fcd8548861aedb9d825e7a424282fb67a294f2 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:39:31 -0500 Subject: [PATCH 08/17] Update table page object --- .../nimble-components/src/table/testing/table.pageobject.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/nimble-components/src/table/testing/table.pageobject.ts b/packages/nimble-components/src/table/testing/table.pageobject.ts index 812012a371..26f65bd378 100644 --- a/packages/nimble-components/src/table/testing/table.pageobject.ts +++ b/packages/nimble-components/src/table/testing/table.pageobject.ts @@ -827,11 +827,12 @@ export class TablePageObject { ): void { const dividerRect = divider.getBoundingClientRect(); let currentPointerX = (dividerRect.x + dividerRect.width) / 2; - const mouseDownEvent = new PointerEvent('pointerdown', { + const pointerDownEvent = new PointerEvent('pointerdown', { + pointerId: 1, clientX: currentPointerX, clientY: (dividerRect.y + dividerRect.height) / 2 }); - divider.dispatchEvent(mouseDownEvent); + divider.dispatchEvent(pointerDownEvent); for (const delta of deltas) { currentPointerX += delta; From 7cd9ef6943cbe74ec98dbb394bad7afb6b55b286 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:26:24 -0500 Subject: [PATCH 09/17] Fix tests --- .../src/table/testing/table.pageobject.ts | 4 ++-- .../table/tests/table-column-sizing.spec.ts | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/nimble-components/src/table/testing/table.pageobject.ts b/packages/nimble-components/src/table/testing/table.pageobject.ts index 26f65bd378..5546448927 100644 --- a/packages/nimble-components/src/table/testing/table.pageobject.ts +++ b/packages/nimble-components/src/table/testing/table.pageobject.ts @@ -839,11 +839,11 @@ export class TablePageObject { const pointerMoveEvent = new PointerEvent('pointermove', { clientX: currentPointerX }); - document.dispatchEvent(pointerMoveEvent); + divider.dispatchEvent(pointerMoveEvent); } const pointerUpEvent = new PointerEvent('pointerup'); - document.dispatchEvent(pointerUpEvent); + divider.dispatchEvent(pointerUpEvent); } private readonly isSlotElement = ( diff --git a/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts b/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts index be742af65d..b551ddc03f 100644 --- a/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts +++ b/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts @@ -1056,12 +1056,13 @@ describe('Table Interactive Column Sizing', () => { ); const divider = dividers[value.dividerClickIndex]!; const dividerRect = divider.getBoundingClientRect(); - const mouseDownEvent = new PointerEvent('pointerdown', { + const pointerDownEvent = new PointerEvent('pointerdown', { + pointerId: 1, clientX: (dividerRect.x + dividerRect.width) / 2, clientY: (dividerRect.y + dividerRect.height) / 2 }); - const mouseUpEvent = new PointerEvent('pointerup'); - divider.dispatchEvent(mouseDownEvent); + const pointerUpEvent = new PointerEvent('pointerup'); + divider.dispatchEvent(pointerDownEvent); await waitForUpdatesAsync(); const dividerActiveDividers = []; const columnActiveDividers = []; @@ -1073,7 +1074,7 @@ describe('Table Interactive Column Sizing', () => { columnActiveDividers.push(i); } } - document.dispatchEvent(mouseUpEvent); // clean up registered event handlers + divider.dispatchEvent(pointerUpEvent); // clean up registered event handlers expect(dividerActiveDividers.length).toEqual(1); expect(dividerActiveDividers[0]).toEqual( @@ -1105,16 +1106,17 @@ describe('Table Interactive Column Sizing', () => { it('after releasing divider, it is no longer marked as active', async () => { const divider = pageObject.getColumnRightDivider(0)!; const dividerRect = divider.getBoundingClientRect(); - const mouseDownEvent = new PointerEvent('pointerdown', { + const pointerDownEvent = new PointerEvent('pointerdown', { + pointerId: 1, clientX: (dividerRect.x + dividerRect.width) / 2, clientY: (dividerRect.y + dividerRect.height) / 2 }); - divider.dispatchEvent(mouseDownEvent); + divider.dispatchEvent(pointerDownEvent); await waitForUpdatesAsync(); expect(divider.classList.contains('divider-active')).toBeTruthy(); - const mouseUpEvent = new PointerEvent('pointerup'); - document.dispatchEvent(mouseUpEvent); + const pointerUpEvent = new PointerEvent('pointerup'); + divider.dispatchEvent(pointerUpEvent); await waitForUpdatesAsync(); expect(divider.classList.contains('divider-active')).toBeFalsy(); }); From 1dc77a510252e9e599dabcaab95dc36786476aca Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:09:15 -0500 Subject: [PATCH 10/17] Encapsulate event creation in page object --- .../src/table/testing/table.pageobject.ts | 100 ++++++++++------- .../table/tests/table-column-sizing.spec.ts | 104 +++++++++--------- packages/nimble-components/tsconfig.json | 8 +- 3 files changed, 125 insertions(+), 87 deletions(-) diff --git a/packages/nimble-components/src/table/testing/table.pageobject.ts b/packages/nimble-components/src/table/testing/table.pageobject.ts index 5546448927..28fba71faf 100644 --- a/packages/nimble-components/src/table/testing/table.pageobject.ts +++ b/packages/nimble-components/src/table/testing/table.pageobject.ts @@ -528,20 +528,14 @@ export class TablePageObject { /** * @param columnIndex The index of the column to the left of a divider being dragged. Thus, this - * can not be given a value representing the last visible column index. + * cannot be given a value representing the last visible column index. * @param deltas The series of mouse movements in the x-direction while sizing a column. */ - public dragSizeColumnByRightDivider( + public async dragSizeColumnByRightDivider( columnIndex: number, deltas: readonly number[] - ): void { - const divider = this.getColumnRightDivider(columnIndex); - if (!divider) { - throw new Error( - 'The provided column index has no right divider associated with it.' - ); - } - this.dragSizeColumnByDivider(divider, deltas); + ): Promise { + await this.dragSizeColumnByDivider(columnIndex, true, deltas); } /** @@ -549,17 +543,53 @@ export class TablePageObject { * value must be greater than 0 and less than the total number of visible columns. * @param deltas The series of mouse movements in the x-direction while sizing a column. */ - public dragSizeColumnByLeftDivider( + public async dragSizeColumnByLeftDivider( columnIndex: number, deltas: readonly number[] - ): void { - const divider = this.getColumnLeftDivider(columnIndex); + ): Promise { + await this.dragSizeColumnByDivider(columnIndex, false, deltas); + } + + /** + * @param columnIndex The index of the column whose left or right divider is clicked. + * @param rightDivider True if the right divider of the column should be clicked, otherwise + * the left divider is clicked. If true, columnIndex cannot be the last visible column index. + * If false, columnIndex must be greater than 0. + * @param runWhileClicked Optional function to run after pressing down on column divider, but + * before releasing it. + */ + public async clickAndReleaseColumnDivider( + columnIndex: number, + rightDivider: boolean, + runWhileClicked?: ( + divider: HTMLElement, + clickX: number + ) => void + ): Promise { + const divider = rightDivider + ? this.getColumnRightDivider(columnIndex) + : this.getColumnLeftDivider(columnIndex); if (!divider) { throw new Error( - 'The provided column index has no left divider associated with it.' + `The provided column index has no ${rightDivider ? 'right' : 'left'} divider associated with it.` ); } - this.dragSizeColumnByDivider(divider, deltas); + const dividerRect = divider.getBoundingClientRect(); + const clickX = (dividerRect.x + dividerRect.width) / 2; + const pointerDownEvent = new PointerEvent('pointerdown', { + pointerId: 1, + clientX: clickX, + clientY: (dividerRect.y + dividerRect.height) / 2 + }); + divider.dispatchEvent(pointerDownEvent); + await waitForUpdatesAsync(); + + if (runWhileClicked) { + runWhileClicked(divider, clickX); + } + + const pointerUpEvent = new PointerEvent('pointerup'); + divider.dispatchEvent(pointerUpEvent); } public getColumnRightDivider(index: number): HTMLElement | null { @@ -821,29 +851,25 @@ export class TablePageObject { return spinnerOrIcon; } - private dragSizeColumnByDivider( - divider: HTMLElement, + private async dragSizeColumnByDivider( + columnIndex: number, + rightDivider: boolean, deltas: readonly number[] - ): void { - const dividerRect = divider.getBoundingClientRect(); - let currentPointerX = (dividerRect.x + dividerRect.width) / 2; - const pointerDownEvent = new PointerEvent('pointerdown', { - pointerId: 1, - clientX: currentPointerX, - clientY: (dividerRect.y + dividerRect.height) / 2 - }); - divider.dispatchEvent(pointerDownEvent); - - for (const delta of deltas) { - currentPointerX += delta; - const pointerMoveEvent = new PointerEvent('pointermove', { - clientX: currentPointerX - }); - divider.dispatchEvent(pointerMoveEvent); - } - - const pointerUpEvent = new PointerEvent('pointerup'); - divider.dispatchEvent(pointerUpEvent); + ): Promise { + await this.clickAndReleaseColumnDivider( + columnIndex, + rightDivider, + (divider: HTMLElement, clickX: number): void => { + let currentPointerX = clickX; + for (const delta of deltas) { + currentPointerX += delta; + const pointerMoveEvent = new PointerEvent('pointermove', { + clientX: currentPointerX + }); + divider.dispatchEvent(pointerMoveEvent); + } + } + ); } private readonly isSlotElement = ( diff --git a/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts b/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts index b551ddc03f..ede558c7ba 100644 --- a/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts +++ b/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts @@ -528,7 +528,7 @@ describe('Table Interactive Column Sizing', () => { column.columnInternals.resizingDisabled = value.resizingDisabled[i]!; }); await waitForUpdatesAsync(); - pageObject.dragSizeColumnByRightDivider( + await pageObject.dragSizeColumnByRightDivider( value.columnDragIndex, value.dragDeltas ); @@ -602,14 +602,14 @@ describe('Table Interactive Column Sizing', () => { it('when table width is smaller than total column min width, dragging column still expands column', async () => { await pageObject.sizeTableToGivenRowWidth(100, element); await waitForUpdatesAsync(); - pageObject.dragSizeColumnByRightDivider(2, [50]); + await pageObject.dragSizeColumnByRightDivider(2, [50]); await waitForUpdatesAsync(); const cellWidth = pageObject.getCellRenderedWidth(0, 2); expect(cellWidth).toBe(100); }); it('sizing column beyond table width creates horizontal scrollbar', async () => { - pageObject.dragSizeColumnByRightDivider(2, [100]); + await pageObject.dragSizeColumnByRightDivider(2, [100]); await waitForUpdatesAsync(); expect(pageObject.isHorizontalScrollbarVisible()).toBeTrue(); }); @@ -619,7 +619,7 @@ describe('Table Interactive Column Sizing', () => { // current fractional widths to 0.8, 0.8, 2, and 0.4, which keeps the columns widths as // integers when the table is resized later in the test. Otherwise, different browsers // may have slightly different rounding behaviors. - pageObject.dragSizeColumnByRightDivider(2, [150]); + await pageObject.dragSizeColumnByRightDivider(2, [150]); // size table below threshhold of total column widths await pageObject.sizeTableToGivenRowWidth(425, element); expect(pageObject.getTotalCellRenderedWidth()).toBe(500); @@ -631,15 +631,15 @@ describe('Table Interactive Column Sizing', () => { it('after table gets horizontal scrollbar, growing right-most column to left does not remove scroll area', async () => { // create horizontal scrollbar with total column width of 450 - pageObject.dragSizeColumnByRightDivider(2, [100]); + await pageObject.dragSizeColumnByRightDivider(2, [100]); await waitForUpdatesAsync(); - pageObject.dragSizeColumnByRightDivider(2, [-100]); + await pageObject.dragSizeColumnByRightDivider(2, [-100]); await waitForUpdatesAsync(); expect(pageObject.getTotalCellRenderedWidth()).toBe(450); }); - it('sizing column results in updated currentFractionalWidths for columns', () => { - pageObject.dragSizeColumnByRightDivider(0, [150]); + it('sizing column results in updated currentFractionalWidths for columns', async () => { + await pageObject.dragSizeColumnByRightDivider(0, [150]); const updatedFractionalWidths = element.columns.map( column => column.columnInternals.currentFractionalWidth ); @@ -653,7 +653,7 @@ describe('Table Interactive Column Sizing', () => { 0, 1 ); - pageObject.dragSizeColumnByRightDivider(0, [5]); + await pageObject.dragSizeColumnByRightDivider(0, [5]); await waitForUpdatesAsync(); expect(pageObject.getCellRenderedWidth(0, 1)).toBe( secondVisibleCellWidth - 5 @@ -667,7 +667,7 @@ describe('Table Interactive Column Sizing', () => { 0, 1 ); - pageObject.dragSizeColumnByRightDivider(1, [-5]); + await pageObject.dragSizeColumnByRightDivider(1, [-5]); await waitForUpdatesAsync(); expect(pageObject.getCellRenderedWidth(0, 1)).toBe( secondVisibleCellWidth - 5 @@ -676,7 +676,7 @@ describe('Table Interactive Column Sizing', () => { it('hiding column after creating horizontal scroll space does not change scroll area', async () => { // create horizontal scrollbar with total column width of 450 - pageObject.dragSizeColumnByRightDivider(2, [100]); + await pageObject.dragSizeColumnByRightDivider(2, [100]); await waitForUpdatesAsync(); element.columns[1]!.columnHidden = true; await waitForUpdatesAsync(); @@ -814,7 +814,7 @@ describe('Table Interactive Column Sizing', () => { element.columns[columnIndex]!.columnHidden = true; }); await waitForUpdatesAsync(); - pageObject.dragSizeColumnByRightDivider( + await pageObject.dragSizeColumnByRightDivider( value.dragColumnIndex, value.dragDeltas ); @@ -956,7 +956,7 @@ describe('Table Interactive Column Sizing', () => { element.columns[columnIndex]!.columnHidden = true; }); await waitForUpdatesAsync(); - pageObject.dragSizeColumnByLeftDivider( + await pageObject.dragSizeColumnByLeftDivider( value.dragColumnIndex, value.dragDeltas ); @@ -1017,32 +1017,43 @@ describe('Table Interactive Column Sizing', () => { const dividerActiveTests = [ { name: 'click on first column right divider', + columnIndex: 0, + rightDivider: true, dividerClickIndex: 0, - leftDividerClick: false, expectedColumnActiveDividerIndexes: [0] }, { name: 'click on second column left divider', + columnIndex: 1, + rightDivider: false, dividerClickIndex: 1, expectedColumnActiveDividerIndexes: [1, 2] }, { name: 'click on second column right divider', + columnIndex: 1, + rightDivider: true, dividerClickIndex: 2, expectedColumnActiveDividerIndexes: [1, 2] }, { name: 'click on third column left divider', + columnIndex: 2, + rightDivider: false, dividerClickIndex: 3, expectedColumnActiveDividerIndexes: [3, 4] }, { name: 'click on third column right divider', + columnIndex: 2, + rightDivider: true, dividerClickIndex: 4, expectedColumnActiveDividerIndexes: [3, 4] }, { name: 'click on last column left divider', + columnIndex: 3, + rightDivider: false, dividerClickIndex: 5, expectedColumnActiveDividerIndexes: [5] } @@ -1051,30 +1062,26 @@ describe('Table Interactive Column Sizing', () => { spec( `${name} updates expected dividers as "divider-active" and "column-active"`, async () => { + const dividerActiveDividers: number[] = []; + const columnActiveDividers: number[] = []; const dividers = Array.from( element.shadowRoot!.querySelectorAll('.column-divider') ); - const divider = dividers[value.dividerClickIndex]!; - const dividerRect = divider.getBoundingClientRect(); - const pointerDownEvent = new PointerEvent('pointerdown', { - pointerId: 1, - clientX: (dividerRect.x + dividerRect.width) / 2, - clientY: (dividerRect.y + dividerRect.height) / 2 - }); - const pointerUpEvent = new PointerEvent('pointerup'); - divider.dispatchEvent(pointerDownEvent); - await waitForUpdatesAsync(); - const dividerActiveDividers = []; - const columnActiveDividers = []; - for (let i = 0; i < dividers.length; i++) { - if (dividers[i]!.classList.contains('divider-active')) { - dividerActiveDividers.push(i); + await pageObject.clickAndReleaseColumnDivider( + value.columnIndex, + value.rightDivider, + (): void => { + for (let i = 0; i < dividers.length; i++) { + const classes = dividers[i]!.classList; + if (classes.contains('divider-active')) { + dividerActiveDividers.push(i); + } + if (classes.contains('column-active')) { + columnActiveDividers.push(i); + } + } } - if (dividers[i]!.classList.contains('column-active')) { - columnActiveDividers.push(i); - } - } - divider.dispatchEvent(pointerUpEvent); // clean up registered event handlers + ); expect(dividerActiveDividers.length).toEqual(1); expect(dividerActiveDividers[0]).toEqual( @@ -1104,21 +1111,20 @@ describe('Table Interactive Column Sizing', () => { }); it('after releasing divider, it is no longer marked as active', async () => { - const divider = pageObject.getColumnRightDivider(0)!; - const dividerRect = divider.getBoundingClientRect(); - const pointerDownEvent = new PointerEvent('pointerdown', { - pointerId: 1, - clientX: (dividerRect.x + dividerRect.width) / 2, - clientY: (dividerRect.y + dividerRect.height) / 2 - }); - divider.dispatchEvent(pointerDownEvent); + let divider: HTMLElement; + let whilePressed: string[]; + await pageObject.clickAndReleaseColumnDivider( + 0, + true, + (dividerElement: HTMLElement): void => { + divider = dividerElement; + whilePressed = [...dividerElement.classList] as string[]; + } + ); await waitForUpdatesAsync(); - expect(divider.classList.contains('divider-active')).toBeTruthy(); - const pointerUpEvent = new PointerEvent('pointerup'); - divider.dispatchEvent(pointerUpEvent); - await waitForUpdatesAsync(); - expect(divider.classList.contains('divider-active')).toBeFalsy(); + expect(whilePressed!.includes('divider-active')).toBeTruthy(); + expect(divider!.classList.contains('divider-active')).toBeFalsy(); }); }); @@ -1129,7 +1135,7 @@ describe('Table Interactive Column Sizing', () => { 'column-configuration-change', spy ); - pageObject.dragSizeColumnByRightDivider(2, [1, 1, 1, 1]); + await pageObject.dragSizeColumnByRightDivider(2, [1, 1, 1, 1]); await waitForUpdatesAsync(); await listener; expect(spy).toHaveBeenCalledTimes(1); @@ -1155,7 +1161,7 @@ describe('Table Interactive Column Sizing', () => { await pageObject.sizeTableToGivenRowWidth(202, element); // columns now on half-pixel boundary const expectedColumnSizes = [50.5, 50.5, 50.5, 50.5]; expectedColumnSizes.forEach((width, i) => expect(pageObject.getCellRenderedWidth(0, i)).toBe(width)); - pageObject.dragSizeColumnByLeftDivider(3, [-1]); + await pageObject.dragSizeColumnByLeftDivider(3, [-1]); await waitForUpdatesAsync(); const totalColumnPixelWidth = pageObject.getTotalCellRenderedWidth(); expect(totalColumnPixelWidth).toBe(202); diff --git a/packages/nimble-components/tsconfig.json b/packages/nimble-components/tsconfig.json index 2997eb6bbf..bba5287b87 100644 --- a/packages/nimble-components/tsconfig.json +++ b/packages/nimble-components/tsconfig.json @@ -13,7 +13,13 @@ "resolveJsonModule": true, "sourceMap": true, "inlineSources": true, - "lib": ["DOM", "ES2015", "ES2016.Array.Include", "ES2017.Object"], + "lib": [ + "DOM", + "DOM.Iterable", + "ES2015", + "ES2016.Array.Include", + "ES2017.Object" + ], // Whitelist imported @types instead of using them all "types": ["jasmine", "webpack-env", "offscreencanvas"], // Require type only imports where possible From f5f47ce17a719d50ace2c18f30c5954f18ed1617 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:35:49 -0500 Subject: [PATCH 11/17] Avoid invalid pointerId errors in tests on Firefox --- .../src/table/models/table-layout-manager.ts | 5 ++++- .../src/table/testing/table.pageobject.ts | 8 +++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/nimble-components/src/table/models/table-layout-manager.ts b/packages/nimble-components/src/table/models/table-layout-manager.ts index 7ac07cd280..4b5075e9da 100644 --- a/packages/nimble-components/src/table/models/table-layout-manager.ts +++ b/packages/nimble-components/src/table/models/table-layout-manager.ts @@ -83,7 +83,10 @@ export class TableLayoutManager { this.initialTableScrollableMinWidth = this.table.tableScrollableMinWidth; this.initialColumnTotalWidth = this.getTotalColumnFixedWidth(); this.isColumnBeingSized = true; - divider.setPointerCapture(pointerId); + // pointerId of -1 indicates source was synthetic PointerEvent: https://w3c.github.io/pointerevents/#dom-pointerevent-pointerid + if (pointerId !== -1) { + divider.setPointerCapture(pointerId); + } divider.addEventListener('pointermove', this.onDividerPointerMove); divider.addEventListener('pointerup', this.onDividerPointerUp); } diff --git a/packages/nimble-components/src/table/testing/table.pageobject.ts b/packages/nimble-components/src/table/testing/table.pageobject.ts index 28fba71faf..7d789923f2 100644 --- a/packages/nimble-components/src/table/testing/table.pageobject.ts +++ b/packages/nimble-components/src/table/testing/table.pageobject.ts @@ -561,10 +561,7 @@ export class TablePageObject { public async clickAndReleaseColumnDivider( columnIndex: number, rightDivider: boolean, - runWhileClicked?: ( - divider: HTMLElement, - clickX: number - ) => void + runWhileClicked?: (divider: HTMLElement, clickX: number) => void ): Promise { const divider = rightDivider ? this.getColumnRightDivider(columnIndex) @@ -577,7 +574,8 @@ export class TablePageObject { const dividerRect = divider.getBoundingClientRect(); const clickX = (dividerRect.x + dividerRect.width) / 2; const pointerDownEvent = new PointerEvent('pointerdown', { - pointerId: 1, + // -1 is a value reserved for synthetic events: https://w3c.github.io/pointerevents/#dom-pointerevent-pointerid + pointerId: -1, clientX: clickX, clientY: (dividerRect.y + dividerRect.height) / 2 }); From 1d3f8bc8f7f5d3d4c41c4437e55e3222e2637bd8 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Tue, 15 Oct 2024 19:04:59 -0500 Subject: [PATCH 12/17] Add DOM.Iterable to spright and storybook, too --- packages/spright-components/tsconfig.json | 8 +++++++- packages/storybook/tsconfig.json | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/spright-components/tsconfig.json b/packages/spright-components/tsconfig.json index 83bbfbf02d..74e5313066 100644 --- a/packages/spright-components/tsconfig.json +++ b/packages/spright-components/tsconfig.json @@ -13,7 +13,13 @@ "resolveJsonModule": true, "sourceMap": true, "inlineSources": true, - "lib": ["DOM", "ES2015", "ES2016.Array.Include", "ES2017.Object"], + "lib": [ + "DOM", + "DOM.Iterable", + "ES2015", + "ES2016.Array.Include", + "ES2017.Object" + ], // Whitelist imported @types instead of using them all "types": ["jasmine", "webpack-env"], // Require type only imports where possible diff --git a/packages/storybook/tsconfig.json b/packages/storybook/tsconfig.json index 2997eb6bbf..bba5287b87 100644 --- a/packages/storybook/tsconfig.json +++ b/packages/storybook/tsconfig.json @@ -13,7 +13,13 @@ "resolveJsonModule": true, "sourceMap": true, "inlineSources": true, - "lib": ["DOM", "ES2015", "ES2016.Array.Include", "ES2017.Object"], + "lib": [ + "DOM", + "DOM.Iterable", + "ES2015", + "ES2016.Array.Include", + "ES2017.Object" + ], // Whitelist imported @types instead of using them all "types": ["jasmine", "webpack-env", "offscreencanvas"], // Require type only imports where possible From 10d209fbeeb855354822534dc25d2c58ab011659 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Tue, 15 Oct 2024 19:46:22 -0500 Subject: [PATCH 13/17] Replace un-ergonomic page object functions --- .../src/table/testing/table.pageobject.ts | 187 ++++++++++-------- .../table/tests/table-column-sizing.spec.ts | 151 +++----------- 2 files changed, 137 insertions(+), 201 deletions(-) diff --git a/packages/nimble-components/src/table/testing/table.pageobject.ts b/packages/nimble-components/src/table/testing/table.pageobject.ts index 7d789923f2..b0787d45cd 100644 --- a/packages/nimble-components/src/table/testing/table.pageobject.ts +++ b/packages/nimble-components/src/table/testing/table.pageobject.ts @@ -1,5 +1,6 @@ import type { Checkbox } from '@microsoft/fast-foundation'; import { keyShift } from '@microsoft/fast-web-utilities'; +import { parseColor } from '@microsoft/fast-colors'; import type { Table } from '..'; import type { TableHeader } from '../components/header'; import { @@ -18,6 +19,7 @@ import type { TableGroupRow } from '../components/group-row'; import type { Button } from '../../button'; import { Icon } from '../../icon-base'; import { Spinner, spinnerTag } from '../../spinner'; +import { borderHoverColor } from '../../theme-provider/design-tokens'; /** * Summary information about a column that is sorted in the table for use in the `TablePageObject`. @@ -526,16 +528,44 @@ export class TablePageObject { } } + /** + * @param columnIndex The index of the column to the left of the divider to press. + */ + public pressRightColumnDivider(columnIndex: number): void { + this.pressColumnDivider(this.getColumnDivider(columnIndex, false)); + } + + /** + * @param columnIndex The index of the column to the right of the divider to press. + */ + public pressLeftColumnDivider(columnIndex: number): void { + this.pressColumnDivider(this.getColumnDivider(columnIndex, true)); + } + + /** + * @param columnIndex The index of the column to the left of the divider to release. + */ + public releaseRightColumnDivider(columnIndex: number): void { + this.releaseColumnDivider(this.getColumnDivider(columnIndex, false)); + } + + /** + * @param columnIndex The index of the column to the right of the divider to release. + */ + public releaseLeftColumnDivider(columnIndex: number): void { + this.releaseColumnDivider(this.getColumnDivider(columnIndex, true)); + } + /** * @param columnIndex The index of the column to the left of a divider being dragged. Thus, this * cannot be given a value representing the last visible column index. * @param deltas The series of mouse movements in the x-direction while sizing a column. */ - public async dragSizeColumnByRightDivider( + public dragSizeColumnByRightDivider( columnIndex: number, deltas: readonly number[] - ): Promise { - await this.dragSizeColumnByDivider(columnIndex, true, deltas); + ): void { + this.dragSizeColumnByDivider(columnIndex, false, deltas); } /** @@ -543,73 +573,35 @@ export class TablePageObject { * value must be greater than 0 and less than the total number of visible columns. * @param deltas The series of mouse movements in the x-direction while sizing a column. */ - public async dragSizeColumnByLeftDivider( + public dragSizeColumnByLeftDivider( columnIndex: number, deltas: readonly number[] - ): Promise { - await this.dragSizeColumnByDivider(columnIndex, false, deltas); + ): void { + this.dragSizeColumnByDivider(columnIndex, true, deltas); } /** - * @param columnIndex The index of the column whose left or right divider is clicked. - * @param rightDivider True if the right divider of the column should be clicked, otherwise - * the left divider is clicked. If true, columnIndex cannot be the last visible column index. - * If false, columnIndex must be greater than 0. - * @param runWhileClicked Optional function to run after pressing down on column divider, but - * before releasing it. + * @param columnIndex The index of the column whose right divider we're checking. */ - public async clickAndReleaseColumnDivider( - columnIndex: number, - rightDivider: boolean, - runWhileClicked?: (divider: HTMLElement, clickX: number) => void - ): Promise { - const divider = rightDivider - ? this.getColumnRightDivider(columnIndex) - : this.getColumnLeftDivider(columnIndex); - if (!divider) { - throw new Error( - `The provided column index has no ${rightDivider ? 'right' : 'left'} divider associated with it.` - ); - } - const dividerRect = divider.getBoundingClientRect(); - const clickX = (dividerRect.x + dividerRect.width) / 2; - const pointerDownEvent = new PointerEvent('pointerdown', { - // -1 is a value reserved for synthetic events: https://w3c.github.io/pointerevents/#dom-pointerevent-pointerid - pointerId: -1, - clientX: clickX, - clientY: (dividerRect.y + dividerRect.height) / 2 - }); - divider.dispatchEvent(pointerDownEvent); - await waitForUpdatesAsync(); - - if (runWhileClicked) { - runWhileClicked(divider, clickX); - } - - const pointerUpEvent = new PointerEvent('pointerup'); - divider.dispatchEvent(pointerUpEvent); - } - - public getColumnRightDivider(index: number): HTMLElement | null { - const headerContainers = this.tableElement.shadowRoot!.querySelectorAll('.header-container'); - if (index < 0 || index >= headerContainers.length) { - throw new Error( - 'Invalid column index. Index must be greater than or equal to 0 and less than the number of visible columns.' - ); - } - - return headerContainers[index]!.querySelector('.column-divider.right'); + public columnRightDividerHasActiveStyling(columnIndex: number): boolean { + const divider = this.getColumnDivider(columnIndex, false); + const currentColor = parseColor( + window.getComputedStyle(divider).borderColor + ); + const hoverColor = parseColor(borderHoverColor.getValueFor(divider))!; + return currentColor?.equalValue(hoverColor) ?? false; } - public getColumnLeftDivider(index: number): HTMLElement | null { - const headerContainers = this.tableElement.shadowRoot!.querySelectorAll('.header-container'); - if (index < 0 || index >= headerContainers.length) { - throw new Error( - 'Invalid column index. Index must be greater than or equal to 0 and less than the number of visible columns.' - ); - } - - return headerContainers[index]!.querySelector('.column-divider.left'); + /** + * @param columnIndex The index of the column whose left divider we're checking. + */ + public columnLeftDividerHasActiveStyling(columnIndex: number): boolean { + const divider = this.getColumnDivider(columnIndex, true); + const currentColor = parseColor( + window.getComputedStyle(divider).borderColor + ); + const hoverColor = parseColor(borderHoverColor.getValueFor(divider))!; + return currentColor?.equalValue(hoverColor) ?? false; } public isVerticalScrollbarVisible(): boolean { @@ -849,25 +841,62 @@ export class TablePageObject { return spinnerOrIcon; } - private async dragSizeColumnByDivider( + private getColumnDivider( columnIndex: number, - rightDivider: boolean, - deltas: readonly number[] - ): Promise { - await this.clickAndReleaseColumnDivider( - columnIndex, - rightDivider, - (divider: HTMLElement, clickX: number): void => { - let currentPointerX = clickX; - for (const delta of deltas) { - currentPointerX += delta; - const pointerMoveEvent = new PointerEvent('pointermove', { - clientX: currentPointerX - }); - divider.dispatchEvent(pointerMoveEvent); - } - } + leftDivider: boolean + ): HTMLElement { + const headerContainers = this.tableElement.shadowRoot!.querySelectorAll('.header-container'); + if (columnIndex < 0 || columnIndex >= headerContainers.length) { + throw new Error( + 'Invalid column index. Index must be greater than or equal to 0 and less than the number of visible columns.' + ); + } + const side = leftDivider ? 'left' : 'right'; + const divider = headerContainers[columnIndex]!.querySelector( + `.column-divider.${side}` ); + if (!divider) { + throw new Error( + `The provided column index has no ${side} divider associated with it.` + ); + } + + return divider as HTMLElement; + } + + private pressColumnDivider(divider: HTMLElement): number { + const dividerRect = divider.getBoundingClientRect(); + const clickX = (dividerRect.x + dividerRect.width) / 2; + const pointerDownEvent = new PointerEvent('pointerdown', { + // -1 is a value reserved for synthetic events: https://w3c.github.io/pointerevents/#dom-pointerevent-pointerid + pointerId: -1, + clientX: clickX, + clientY: (dividerRect.y + dividerRect.height) / 2 + }); + divider.dispatchEvent(pointerDownEvent); + + return clickX; + } + + private releaseColumnDivider(divider: HTMLElement): void { + divider.dispatchEvent(new PointerEvent('pointerup')); + } + + private dragSizeColumnByDivider( + columnIndex: number, + leftDivider: boolean, + deltas: readonly number[] + ): void { + const divider = this.getColumnDivider(columnIndex, leftDivider); + let currentPointerX = this.pressColumnDivider(divider); + for (const delta of deltas) { + currentPointerX += delta; + const pointerMoveEvent = new PointerEvent('pointermove', { + clientX: currentPointerX + }); + divider.dispatchEvent(pointerMoveEvent); + } + this.releaseColumnDivider(divider); } private readonly isSlotElement = ( diff --git a/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts b/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts index ede558c7ba..78e5ab2eb9 100644 --- a/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts +++ b/packages/nimble-components/src/table/tests/table-column-sizing.spec.ts @@ -528,7 +528,7 @@ describe('Table Interactive Column Sizing', () => { column.columnInternals.resizingDisabled = value.resizingDisabled[i]!; }); await waitForUpdatesAsync(); - await pageObject.dragSizeColumnByRightDivider( + pageObject.dragSizeColumnByRightDivider( value.columnDragIndex, value.dragDeltas ); @@ -602,14 +602,14 @@ describe('Table Interactive Column Sizing', () => { it('when table width is smaller than total column min width, dragging column still expands column', async () => { await pageObject.sizeTableToGivenRowWidth(100, element); await waitForUpdatesAsync(); - await pageObject.dragSizeColumnByRightDivider(2, [50]); + pageObject.dragSizeColumnByRightDivider(2, [50]); await waitForUpdatesAsync(); const cellWidth = pageObject.getCellRenderedWidth(0, 2); expect(cellWidth).toBe(100); }); it('sizing column beyond table width creates horizontal scrollbar', async () => { - await pageObject.dragSizeColumnByRightDivider(2, [100]); + pageObject.dragSizeColumnByRightDivider(2, [100]); await waitForUpdatesAsync(); expect(pageObject.isHorizontalScrollbarVisible()).toBeTrue(); }); @@ -619,7 +619,7 @@ describe('Table Interactive Column Sizing', () => { // current fractional widths to 0.8, 0.8, 2, and 0.4, which keeps the columns widths as // integers when the table is resized later in the test. Otherwise, different browsers // may have slightly different rounding behaviors. - await pageObject.dragSizeColumnByRightDivider(2, [150]); + pageObject.dragSizeColumnByRightDivider(2, [150]); // size table below threshhold of total column widths await pageObject.sizeTableToGivenRowWidth(425, element); expect(pageObject.getTotalCellRenderedWidth()).toBe(500); @@ -631,15 +631,15 @@ describe('Table Interactive Column Sizing', () => { it('after table gets horizontal scrollbar, growing right-most column to left does not remove scroll area', async () => { // create horizontal scrollbar with total column width of 450 - await pageObject.dragSizeColumnByRightDivider(2, [100]); + pageObject.dragSizeColumnByRightDivider(2, [100]); await waitForUpdatesAsync(); - await pageObject.dragSizeColumnByRightDivider(2, [-100]); + pageObject.dragSizeColumnByRightDivider(2, [-100]); await waitForUpdatesAsync(); expect(pageObject.getTotalCellRenderedWidth()).toBe(450); }); - it('sizing column results in updated currentFractionalWidths for columns', async () => { - await pageObject.dragSizeColumnByRightDivider(0, [150]); + it('sizing column results in updated currentFractionalWidths for columns', () => { + pageObject.dragSizeColumnByRightDivider(0, [150]); const updatedFractionalWidths = element.columns.map( column => column.columnInternals.currentFractionalWidth ); @@ -653,7 +653,7 @@ describe('Table Interactive Column Sizing', () => { 0, 1 ); - await pageObject.dragSizeColumnByRightDivider(0, [5]); + pageObject.dragSizeColumnByRightDivider(0, [5]); await waitForUpdatesAsync(); expect(pageObject.getCellRenderedWidth(0, 1)).toBe( secondVisibleCellWidth - 5 @@ -667,7 +667,7 @@ describe('Table Interactive Column Sizing', () => { 0, 1 ); - await pageObject.dragSizeColumnByRightDivider(1, [-5]); + pageObject.dragSizeColumnByRightDivider(1, [-5]); await waitForUpdatesAsync(); expect(pageObject.getCellRenderedWidth(0, 1)).toBe( secondVisibleCellWidth - 5 @@ -676,7 +676,7 @@ describe('Table Interactive Column Sizing', () => { it('hiding column after creating horizontal scroll space does not change scroll area', async () => { // create horizontal scrollbar with total column width of 450 - await pageObject.dragSizeColumnByRightDivider(2, [100]); + pageObject.dragSizeColumnByRightDivider(2, [100]); await waitForUpdatesAsync(); element.columns[1]!.columnHidden = true; await waitForUpdatesAsync(); @@ -814,7 +814,7 @@ describe('Table Interactive Column Sizing', () => { element.columns[columnIndex]!.columnHidden = true; }); await waitForUpdatesAsync(); - await pageObject.dragSizeColumnByRightDivider( + pageObject.dragSizeColumnByRightDivider( value.dragColumnIndex, value.dragDeltas ); @@ -956,7 +956,7 @@ describe('Table Interactive Column Sizing', () => { element.columns[columnIndex]!.columnHidden = true; }); await waitForUpdatesAsync(); - await pageObject.dragSizeColumnByLeftDivider( + pageObject.dragSizeColumnByLeftDivider( value.dragColumnIndex, value.dragDeltas ); @@ -1013,118 +1013,25 @@ describe('Table Interactive Column Sizing', () => { ); }); - describe('active divider tests', () => { - const dividerActiveTests = [ - { - name: 'click on first column right divider', - columnIndex: 0, - rightDivider: true, - dividerClickIndex: 0, - expectedColumnActiveDividerIndexes: [0] - }, - { - name: 'click on second column left divider', - columnIndex: 1, - rightDivider: false, - dividerClickIndex: 1, - expectedColumnActiveDividerIndexes: [1, 2] - }, - { - name: 'click on second column right divider', - columnIndex: 1, - rightDivider: true, - dividerClickIndex: 2, - expectedColumnActiveDividerIndexes: [1, 2] - }, - { - name: 'click on third column left divider', - columnIndex: 2, - rightDivider: false, - dividerClickIndex: 3, - expectedColumnActiveDividerIndexes: [3, 4] - }, - { - name: 'click on third column right divider', - columnIndex: 2, - rightDivider: true, - dividerClickIndex: 4, - expectedColumnActiveDividerIndexes: [3, 4] - }, - { - name: 'click on last column left divider', - columnIndex: 3, - rightDivider: false, - dividerClickIndex: 5, - expectedColumnActiveDividerIndexes: [5] - } - ] as const; - parameterizeSpec(dividerActiveTests, (spec, name, value) => { - spec( - `${name} updates expected dividers as "divider-active" and "column-active"`, - async () => { - const dividerActiveDividers: number[] = []; - const columnActiveDividers: number[] = []; - const dividers = Array.from( - element.shadowRoot!.querySelectorAll('.column-divider') - ); - await pageObject.clickAndReleaseColumnDivider( - value.columnIndex, - value.rightDivider, - (): void => { - for (let i = 0; i < dividers.length; i++) { - const classes = dividers[i]!.classList; - if (classes.contains('divider-active')) { - dividerActiveDividers.push(i); - } - if (classes.contains('column-active')) { - columnActiveDividers.push(i); - } - } - } - ); - - expect(dividerActiveDividers.length).toEqual(1); - expect(dividerActiveDividers[0]).toEqual( - value.dividerClickIndex - ); - expect(columnActiveDividers).toEqual( - value.expectedColumnActiveDividerIndexes - ); - } - ); - }); - - it('first column only has right divider', () => { - const rightDivider = pageObject.getColumnRightDivider(0); - const leftDivider = pageObject.getColumnLeftDivider(0); - - expect(rightDivider).not.toBeNull(); - expect(leftDivider).toBeNull(); - }); - - it('last column only has left divider', () => { - const rightDivider = pageObject.getColumnRightDivider(3); - const leftDivider = pageObject.getColumnLeftDivider(3); + describe('active divider styling', () => { + it('is applied during press', async () => { + const hasActiveStylingBefore = pageObject.columnRightDividerHasActiveStyling(0); + pageObject.pressRightColumnDivider(0); + await waitForUpdatesAsync(); + const hasActiveStylingAfter = pageObject.columnRightDividerHasActiveStyling(0); - expect(rightDivider).toBeNull(); - expect(leftDivider).not.toBeNull(); + expect(hasActiveStylingBefore).toBeFalse(); + expect(hasActiveStylingAfter).toBeTrue(); }); - it('after releasing divider, it is no longer marked as active', async () => { - let divider: HTMLElement; - let whilePressed: string[]; - await pageObject.clickAndReleaseColumnDivider( - 0, - true, - (dividerElement: HTMLElement): void => { - divider = dividerElement; - whilePressed = [...dividerElement.classList] as string[]; - } - ); + it('is removed after release', async () => { + pageObject.pressRightColumnDivider(0); + await waitForUpdatesAsync(); + pageObject.releaseRightColumnDivider(0); await waitForUpdatesAsync(); + const hasActiveStyling = pageObject.columnRightDividerHasActiveStyling(0); - expect(whilePressed!.includes('divider-active')).toBeTruthy(); - expect(divider!.classList.contains('divider-active')).toBeFalsy(); + expect(hasActiveStyling).toBeFalse(); }); }); @@ -1135,7 +1042,7 @@ describe('Table Interactive Column Sizing', () => { 'column-configuration-change', spy ); - await pageObject.dragSizeColumnByRightDivider(2, [1, 1, 1, 1]); + pageObject.dragSizeColumnByRightDivider(2, [1, 1, 1, 1]); await waitForUpdatesAsync(); await listener; expect(spy).toHaveBeenCalledTimes(1); @@ -1161,7 +1068,7 @@ describe('Table Interactive Column Sizing', () => { await pageObject.sizeTableToGivenRowWidth(202, element); // columns now on half-pixel boundary const expectedColumnSizes = [50.5, 50.5, 50.5, 50.5]; expectedColumnSizes.forEach((width, i) => expect(pageObject.getCellRenderedWidth(0, i)).toBe(width)); - await pageObject.dragSizeColumnByLeftDivider(3, [-1]); + pageObject.dragSizeColumnByLeftDivider(3, [-1]); await waitForUpdatesAsync(); const totalColumnPixelWidth = pageObject.getTotalCellRenderedWidth(); expect(totalColumnPixelWidth).toBe(202); From 4fea2768df67a02ff5c42301fd859c79bf032e54 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:17:23 -0500 Subject: [PATCH 14/17] Change files --- ...ht-components-b5fad4b3-b842-42db-9302-e191ec567e1a.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@ni-spright-components-b5fad4b3-b842-42db-9302-e191ec567e1a.json diff --git a/change/@ni-spright-components-b5fad4b3-b842-42db-9302-e191ec567e1a.json b/change/@ni-spright-components-b5fad4b3-b842-42db-9302-e191ec567e1a.json new file mode 100644 index 0000000000..bb1a24293f --- /dev/null +++ b/change/@ni-spright-components-b5fad4b3-b842-42db-9302-e191ec567e1a.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Add DOM.Iterable to the build", + "packageName": "@ni/spright-components", + "email": "7282195+m-akinc@users.noreply.github.com", + "dependentChangeType": "patch" +} From 40524a9eef329730821712c7fecdc69b203d0f37 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:38:13 -0500 Subject: [PATCH 15/17] Remove unused page object methods --- .../src/table/testing/table.pageobject.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/nimble-components/src/table/testing/table.pageobject.ts b/packages/nimble-components/src/table/testing/table.pageobject.ts index b0787d45cd..1bdea229f7 100644 --- a/packages/nimble-components/src/table/testing/table.pageobject.ts +++ b/packages/nimble-components/src/table/testing/table.pageobject.ts @@ -535,13 +535,6 @@ export class TablePageObject { this.pressColumnDivider(this.getColumnDivider(columnIndex, false)); } - /** - * @param columnIndex The index of the column to the right of the divider to press. - */ - public pressLeftColumnDivider(columnIndex: number): void { - this.pressColumnDivider(this.getColumnDivider(columnIndex, true)); - } - /** * @param columnIndex The index of the column to the left of the divider to release. */ @@ -549,13 +542,6 @@ export class TablePageObject { this.releaseColumnDivider(this.getColumnDivider(columnIndex, false)); } - /** - * @param columnIndex The index of the column to the right of the divider to release. - */ - public releaseLeftColumnDivider(columnIndex: number): void { - this.releaseColumnDivider(this.getColumnDivider(columnIndex, true)); - } - /** * @param columnIndex The index of the column to the left of a divider being dragged. Thus, this * cannot be given a value representing the last visible column index. From 42dfd43bc718477661e87b74efb2a82434c4e5e8 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:42:29 -0500 Subject: [PATCH 16/17] Feedback --- .../src/table/models/table-layout-manager.ts | 19 +++++++++++++------ .../src/table/testing/table.pageobject.ts | 14 +------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/nimble-components/src/table/models/table-layout-manager.ts b/packages/nimble-components/src/table/models/table-layout-manager.ts index 4b5075e9da..d1e0480d96 100644 --- a/packages/nimble-components/src/table/models/table-layout-manager.ts +++ b/packages/nimble-components/src/table/models/table-layout-manager.ts @@ -57,19 +57,19 @@ export class TableLayoutManager { /** * Sets up state related to interactively sizing a column. - * @param divider The divider element that was clicked on + * @param activeColumnDividerElement The divider element that was clicked on * @param pointerId The pointerId of the pointer that started the drag * @param dragStart The x-position from which a column size was started * @param activeColumnDivider The 1-based index of the divider that was clicked on */ public beginColumnInteractiveSize( - divider: HTMLElement, + activeColumnDividerElement: HTMLElement, pointerId: number, dragStart: number, activeColumnDivider: number ): void { this.activeColumnDivider = activeColumnDivider; - this.activeColumnDividerElement = divider; + this.activeColumnDividerElement = activeColumnDividerElement; this.leftColumnIndex = this.getLeftColumnIndexFromDivider( this.activeColumnDivider ); @@ -85,10 +85,16 @@ export class TableLayoutManager { this.isColumnBeingSized = true; // pointerId of -1 indicates source was synthetic PointerEvent: https://w3c.github.io/pointerevents/#dom-pointerevent-pointerid if (pointerId !== -1) { - divider.setPointerCapture(pointerId); + activeColumnDividerElement.setPointerCapture(pointerId); } - divider.addEventListener('pointermove', this.onDividerPointerMove); - divider.addEventListener('pointerup', this.onDividerPointerUp); + activeColumnDividerElement.addEventListener( + 'pointermove', + this.onDividerPointerMove + ); + activeColumnDividerElement.addEventListener( + 'pointerup', + this.onDividerPointerUp + ); } /** @@ -142,6 +148,7 @@ export class TableLayoutManager { this.isColumnBeingSized = false; this.activeColumnIndex = undefined; this.activeColumnDivider = undefined; + this.activeColumnDividerElement = undefined; this.visibleColumns = []; }; diff --git a/packages/nimble-components/src/table/testing/table.pageobject.ts b/packages/nimble-components/src/table/testing/table.pageobject.ts index 1bdea229f7..c6f8326938 100644 --- a/packages/nimble-components/src/table/testing/table.pageobject.ts +++ b/packages/nimble-components/src/table/testing/table.pageobject.ts @@ -575,19 +575,7 @@ export class TablePageObject { window.getComputedStyle(divider).borderColor ); const hoverColor = parseColor(borderHoverColor.getValueFor(divider))!; - return currentColor?.equalValue(hoverColor) ?? false; - } - - /** - * @param columnIndex The index of the column whose left divider we're checking. - */ - public columnLeftDividerHasActiveStyling(columnIndex: number): boolean { - const divider = this.getColumnDivider(columnIndex, true); - const currentColor = parseColor( - window.getComputedStyle(divider).borderColor - ); - const hoverColor = parseColor(borderHoverColor.getValueFor(divider))!; - return currentColor?.equalValue(hoverColor) ?? false; + return currentColor!.equalValue(hoverColor); } public isVerticalScrollbarVisible(): boolean { From 8904d679b34a487f32b1848f21a3dfd5b023ad1d Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Wed, 16 Oct 2024 18:15:45 -0500 Subject: [PATCH 17/17] Fix page object function --- .../nimble-components/src/table/testing/table.pageobject.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nimble-components/src/table/testing/table.pageobject.ts b/packages/nimble-components/src/table/testing/table.pageobject.ts index c6f8326938..089d1208e7 100644 --- a/packages/nimble-components/src/table/testing/table.pageobject.ts +++ b/packages/nimble-components/src/table/testing/table.pageobject.ts @@ -572,7 +572,7 @@ export class TablePageObject { public columnRightDividerHasActiveStyling(columnIndex: number): boolean { const divider = this.getColumnDivider(columnIndex, false); const currentColor = parseColor( - window.getComputedStyle(divider).borderColor + window.getComputedStyle(divider).borderLeftColor ); const hoverColor = parseColor(borderHoverColor.getValueFor(divider))!; return currentColor!.equalValue(hoverColor);