From d30b74b51f03b4f8d76e25759ba82ea4c0eae6eb Mon Sep 17 00:00:00 2001 From: eliza Date: Fri, 15 Sep 2023 15:01:06 -0700 Subject: [PATCH 001/108] feat(tabs, tab-nav, tab-title, tab): make component responsive --- .../src/components/tab-nav/tab-nav.scss | 1 + .../src/components/tab-nav/tab-nav.tsx | 16 ++++++++++++++-- .../src/components/tabs/tabs.scss | 2 +- .../calcite-components/src/utils/responsive.ts | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.scss b/packages/calcite-components/src/components/tab-nav/tab-nav.scss index b5d60b51fcb..f1c9fae3678 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.scss +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.scss @@ -18,6 +18,7 @@ @apply flex w-full justify-start + whitespace-nowrap overflow-auto; } diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 831bc3ee7cc..44f488506c3 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -17,6 +17,7 @@ import { FocusElementInGroupDestination, getElementDir, } from "../../utils/dom"; +import { Breakpoints, getBreakpoints } from "../../utils/responsive"; import { createObserver } from "../../utils/observers"; import { Scale } from "../interfaces"; import { TabChangeEventDetail, TabCloseEventDetail } from "../tab/interfaces"; @@ -125,12 +126,13 @@ export class TabNav { this.resizeObserver?.disconnect(); } - componentWillLoad(): void { + async componentWillLoad(): Promise { const storageKey = `calcite-tab-nav-${this.storageId}`; if (localStorage && this.storageId && localStorage.getItem(storageKey)) { const storedTab = JSON.parse(localStorage.getItem(storageKey)); this.selectedTabId = storedTab; } + this.breakpoints = await getBreakpoints(); } componentWillRender(): void { @@ -166,6 +168,10 @@ export class TabNav { const width = `${this.indicatorWidth}px`; const offset = `${this.indicatorOffset}px`; const indicatorStyle = dir !== "rtl" ? { width, left: offset } : { width, right: offset }; + + // const widthBreakpoints = this.breakpoints.width; + // const { elWidth } = this; + return (
{ + private breakpoints: Breakpoints; + + @State() elWidth: number; + + resizeObserver = createObserver("resize", (entries) => { if (!this.activeIndicatorEl) { return; } @@ -314,6 +324,8 @@ export class TabNav { this.activeIndicatorEl.style.transitionDuration = "0s"; this.updateActiveWidth(); this.updateOffsetPosition(); + + this.elWidth = entries[0].contentRect.width; }); //-------------------------------------------------------------------------- diff --git a/packages/calcite-components/src/components/tabs/tabs.scss b/packages/calcite-components/src/components/tabs/tabs.scss index 8a64e20321d..67fb095024e 100644 --- a/packages/calcite-components/src/components/tabs/tabs.scss +++ b/packages/calcite-components/src/components/tabs/tabs.scss @@ -1,5 +1,5 @@ :host { - @apply flex flex-col; + @apply flex flex-col w-full; } :host([bordered]) { diff --git a/packages/calcite-components/src/utils/responsive.ts b/packages/calcite-components/src/utils/responsive.ts index 4c331ddd68b..4aeb4db37c2 100644 --- a/packages/calcite-components/src/utils/responsive.ts +++ b/packages/calcite-components/src/utils/responsive.ts @@ -1,4 +1,4 @@ -interface Breakpoints { +export interface Breakpoints { width: { large: number; medium: number; From 58e56d7498f95a1b277d701851b65e247ccd0bab Mon Sep 17 00:00:00 2001 From: eliza Date: Sat, 16 Sep 2023 02:21:08 -0700 Subject: [PATCH 002/108] add getOverflowIcons() to detect overflow at right and left edges of nav and use it to render the chevron icons accordingly --- .../src/components/tab-nav/resources.ts | 4 +++ .../src/components/tab-nav/tab-nav.scss | 13 +++++++++ .../src/components/tab-nav/tab-nav.tsx | 28 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 packages/calcite-components/src/components/tab-nav/resources.ts diff --git a/packages/calcite-components/src/components/tab-nav/resources.ts b/packages/calcite-components/src/components/tab-nav/resources.ts new file mode 100644 index 00000000000..d26a010440e --- /dev/null +++ b/packages/calcite-components/src/components/tab-nav/resources.ts @@ -0,0 +1,4 @@ +export const ICONS = { + arrowRight: "chevron-right", + arrowLeft: "chevron-left", +}; diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.scss b/packages/calcite-components/src/components/tab-nav/tab-nav.scss index f1c9fae3678..e09b4923b24 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.scss +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.scss @@ -42,6 +42,19 @@ ease-out; } +calcite-icon { + @apply absolute; + inset-block-start: 50%; + transform: translateY(-50%); +} +calcite-icon[icon="chevron-right"] { + inset-inline-end: 0; +} + +calcite-icon[icon="chevron-left"] { + inset-inline-start: 0; +} + :host([layout="center"]) .tab-nav { @apply justify-evenly; } diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 44f488506c3..d42bda1a37f 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -19,6 +19,7 @@ import { } from "../../utils/dom"; import { Breakpoints, getBreakpoints } from "../../utils/responsive"; import { createObserver } from "../../utils/observers"; +import { ICONS } from "./resources"; import { Scale } from "../interfaces"; import { TabChangeEventDetail, TabCloseEventDetail } from "../tab/interfaces"; import { TabID, TabLayout, TabPosition } from "../tabs/interfaces"; @@ -193,6 +194,7 @@ export class TabNav { ref={(el) => (this.activeIndicatorEl = el as HTMLElement)} />
+ {this.getOverflowIcons()}
); @@ -326,6 +328,8 @@ export class TabNav { this.updateOffsetPosition(); this.elWidth = entries[0].contentRect.width; + + this.getOverflowIcons(); }); //-------------------------------------------------------------------------- @@ -334,6 +338,30 @@ export class TabNav { // //-------------------------------------------------------------------------- + private getOverflowIcons() { + const tabNavWidth = this.el.offsetWidth; + + const tabTitles = Array.from(this.el.querySelectorAll("calcite-tab-title")); + + const firstTitle = tabTitles[0].getBoundingClientRect(); + const lastTitle = tabTitles[tabTitles.length - 1].getBoundingClientRect(); + + const isOverflowingRight = lastTitle.right > tabNavWidth; + const isOverflowingLeft = firstTitle.left < 0; + const rightArrow = ( + + ); + const leftArrow = ( + + ); + + return isOverflowingRight && !isOverflowingLeft + ? rightArrow + : isOverflowingLeft && !isOverflowingRight + ? leftArrow + : [rightArrow, leftArrow]; + } + handleTabFocus = ( event: CustomEvent, el: HTMLCalciteTabTitleElement, From cc10a030f438abd5faf7e6b944c0b0ce3099de82 Mon Sep 17 00:00:00 2001 From: eliza Date: Sun, 17 Sep 2023 20:34:01 -0700 Subject: [PATCH 003/108] work out scrollToNextTabTitles callback on right chevron click --- .../src/components/tab-nav/tab-nav.tsx | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index d42bda1a37f..d91b49cd33f 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -17,7 +17,7 @@ import { FocusElementInGroupDestination, getElementDir, } from "../../utils/dom"; -import { Breakpoints, getBreakpoints } from "../../utils/responsive"; +// import { Breakpoints, getBreakpoints } from "../../utils/responsive"; import { createObserver } from "../../utils/observers"; import { ICONS } from "./resources"; import { Scale } from "../interfaces"; @@ -133,7 +133,7 @@ export class TabNav { const storedTab = JSON.parse(localStorage.getItem(storageKey)); this.selectedTabId = storedTab; } - this.breakpoints = await getBreakpoints(); + // this.breakpoints = await getBreakpoints(); } componentWillRender(): void { @@ -313,7 +313,7 @@ export class TabNav { animationActiveDuration = 0.3; - private breakpoints: Breakpoints; + // private breakpoints: Breakpoints; @State() elWidth: number; @@ -349,7 +349,11 @@ export class TabNav { const isOverflowingRight = lastTitle.right > tabNavWidth; const isOverflowingLeft = firstTitle.left < 0; const rightArrow = ( - + ); const leftArrow = ( @@ -362,6 +366,40 @@ export class TabNav { : [rightArrow, leftArrow]; } + private scrollToNextTabTitles = (): void => { + const tabTitles = this.el.querySelectorAll("calcite-tab-title"); + const mobilePageWidth = window.innerWidth; + + let lastVisibleTabTitleIndex = -1; + let scrollAmount = 0; + + // Find the index of the last tab title visible within the mobile-sized tab nav + for (let i = 0; i < tabTitles.length; i++) { + const tabTitle = tabTitles[i]; + const tabTitleRect = tabTitle.getBoundingClientRect(); + + if (tabTitleRect.right <= mobilePageWidth) { + lastVisibleTabTitleIndex = i; + } else { + break; + } + } + + // Calculate the scroll amount to bring the next set of tab titles into view + if (lastVisibleTabTitleIndex !== -1) { + const nextTabTitleIndex = lastVisibleTabTitleIndex + 1; + const nextTabTitle = tabTitles[nextTabTitleIndex]; + if (nextTabTitle) { + const nextTabTitleRect = nextTabTitle.getBoundingClientRect(); + scrollAmount = nextTabTitleRect.left - this.el.getBoundingClientRect().left; + } + } + + requestAnimationFrame(() => { + this.tabNavEl.scrollLeft += scrollAmount; + }); + }; + handleTabFocus = ( event: CustomEvent, el: HTMLCalciteTabTitleElement, From 9f100f2adab36bdc007ce5390cc96f7faf2fc2be Mon Sep 17 00:00:00 2001 From: eliza Date: Sun, 17 Sep 2023 21:09:01 -0700 Subject: [PATCH 004/108] work out scrollToPreviousTabTitles callback to onClick for left chevron arrow button --- .../src/components/tab-nav/tab-nav.tsx | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index d91b49cd33f..b6ea389563a 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -356,7 +356,11 @@ export class TabNav { /> ); const leftArrow = ( - + ); return isOverflowingRight && !isOverflowingLeft @@ -400,6 +404,39 @@ export class TabNav { }); }; + private scrollToPreviousTabTitles = (): void => { + const tabTitles = this.el.querySelectorAll("calcite-tab-title"); + const mobilePageWidth = window.innerWidth; + + let firstVisibleTabTitleIndex = -1; + let scrollAmount = 0; + + // Find the index of the first tab title visible within the mobile-sized tab nav + for (let i = 0; i < tabTitles.length; i++) { + const tabTitle = tabTitles[i]; + const tabTitleRect = tabTitle.getBoundingClientRect(); + + if (tabTitleRect.left >= 0) { + firstVisibleTabTitleIndex = i; + break; + } + } + + // Calculate the scroll amount to bring the previous set of tab titles into view + if (firstVisibleTabTitleIndex !== 0) { + const previousTabTitleIndex = firstVisibleTabTitleIndex - 1; + const previousTabTitle = tabTitles[previousTabTitleIndex]; + if (previousTabTitle) { + const previousTabTitleRect = previousTabTitle.getBoundingClientRect(); + scrollAmount = previousTabTitleRect.right - mobilePageWidth; + } + } + + requestAnimationFrame(() => { + this.tabNavEl.scrollLeft += scrollAmount; + }); + }; + handleTabFocus = ( event: CustomEvent, el: HTMLCalciteTabTitleElement, From fa57cc06d06cd539db292cfb2eaa08947cb6b618 Mon Sep 17 00:00:00 2001 From: eliza Date: Tue, 19 Sep 2023 18:50:14 -0700 Subject: [PATCH 005/108] fix overflow logic and only show overflow icons for the inline version --- .../calcite-components/src/components/tab-nav/tab-nav.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 1aa890371b1..23fb1f953e3 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -194,7 +194,7 @@ export class TabNav { ref={(el) => (this.activeIndicatorEl = el as HTMLElement)} /> - {this.getOverflowIcons()} + {this.layout === "inline" && this.getOverflowIcons()} ); @@ -364,9 +364,11 @@ export class TabNav { /> ); - return isOverflowingRight && !isOverflowingLeft + return !isOverflowingRight && !isOverflowingLeft + ? null + : isOverflowingRight && !isOverflowingLeft ? rightArrow - : isOverflowingLeft && !isOverflowingRight + : !isOverflowingRight && isOverflowingLeft ? leftArrow : [rightArrow, leftArrow]; } From a704b4e3ab6f74b99712b0279d6e47daf6981a06 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 20 Sep 2023 13:55:16 -0700 Subject: [PATCH 006/108] wire up t9n, LocalizedComponent --- .../assets/tab-nav/t9n/messages_en.json | 4 + .../src/components/tab-nav/tab-nav.e2e.ts | 6 +- .../src/components/tab-nav/tab-nav.tsx | 94 ++++++++++++++----- 3 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages_en.json diff --git a/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages_en.json b/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages_en.json new file mode 100644 index 00000000000..fd2979c410b --- /dev/null +++ b/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages_en.json @@ -0,0 +1,4 @@ +{ + "nextTabTitles": "Next Tab Titles", + "previousTabsTitles": "Previous Tab Titles" +} diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index 72c8a967f62..10d1d755529 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -1,5 +1,5 @@ import { newE2EPage } from "@stencil/core/testing"; -import { accessible, renders, hidden } from "../../tests/commonTests"; +import { accessible, renders, hidden, t9n } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; describe("calcite-tab-nav", () => { @@ -17,6 +17,10 @@ describe("calcite-tab-nav", () => { accessible(tabNavHtml); }); + describe("translation support", () => { + t9n("tab-nav"); + }); + it("emits on user interaction", async () => { const page = await newE2EPage(); await page.setContent(html` diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 23fb1f953e3..bc0e37ad91e 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -11,6 +11,13 @@ import { VNode, Watch, } from "@stencil/core"; +import { + connectMessages, + disconnectMessages, + setUpMessages, + T9nComponent, + updateMessages, +} from "../../utils/t9n"; import { filterDirectChildren, focusElementInGroup, @@ -19,10 +26,11 @@ import { } from "../../utils/dom"; // import { Breakpoints, getBreakpoints } from "../../utils/responsive"; import { createObserver } from "../../utils/observers"; -import { ICONS } from "./resources"; import { Scale } from "../interfaces"; import { TabChangeEventDetail, TabCloseEventDetail } from "../tab/interfaces"; import { TabID, TabLayout, TabPosition } from "../tabs/interfaces"; +import { LocalizedComponent, connectLocalized, disconnectLocalized } from "../../utils/locale"; +import { TabNavMessages } from "./assets/tab-nav/t9n"; /** * @slot - A slot for adding `calcite-tab-title`s. @@ -31,8 +39,9 @@ import { TabID, TabLayout, TabPosition } from "../tabs/interfaces"; tag: "calcite-tab-nav", styleUrl: "tab-nav.scss", shadow: true, + assetsDirs: ["assets"], }) -export class TabNav { +export class TabNav implements LocalizedComponent, T9nComponent { //-------------------------------------------------------------------------- // // Properties @@ -86,6 +95,25 @@ export class TabNav { */ @Prop({ mutable: true }) indicatorWidth: number; + /** + * Use this property to override individual strings used by the component. + */ + // eslint-disable-next-line @stencil-community/strict-mutable -- updated by t9n module + @Prop({ mutable: true }) messageOverrides: Partial; + + @Watch("messageOverrides") + onMessagesChange(): void { + /* wired up by t9n util */ + } + + /** + * Made into a prop for testing purposes only. + * + * @internal + */ + // eslint-disable-next-line @stencil-community/strict-mutable -- updated by t9n module + @Prop({ mutable: true }) messages: TabNavMessages; + @Watch("selectedTabId") async selectedTabIdChanged(): Promise { if ( @@ -121,10 +149,8 @@ export class TabNav { connectedCallback(): void { this.parentTabsEl = this.el.closest("calcite-tabs"); this.resizeObserver?.observe(this.el); - } - - disconnectedCallback(): void { - this.resizeObserver?.disconnect(); + connectLocalized(this); + connectMessages(this); } async componentWillLoad(): Promise { @@ -133,7 +159,7 @@ export class TabNav { const storedTab = JSON.parse(localStorage.getItem(storageKey)); this.selectedTabId = storedTab; } - // this.breakpoints = await getBreakpoints(); + await setUpMessages(this); } componentWillRender(): void { @@ -164,6 +190,34 @@ export class TabNav { } } + disconnectedCallback(): void { + this.resizeObserver?.disconnect(); + disconnectLocalized(this); + disconnectMessages(this); + } + + //-------------------------------------------------------------------------- + // + // Render Methods + // + //-------------------------------------------------------------------------- + + renderOverflowIcons(overflowDirection: string): VNode { + const { messages } = this; + const dirChevron: string = overflowDirection === "right" ? "chevron-right" : "chevron-left"; + const dirText: string = + overflowDirection === "right" ? messages.nextTabTitles : messages.previousTabsTitles; + + return ( + + ); + } + render(): VNode { const dir = getElementDir(this.el); const width = `${this.indicatorWidth}px`; @@ -314,7 +368,14 @@ export class TabNav { animationActiveDuration = 0.3; - // private breakpoints: Breakpoints; + @State() defaultMessages: TabNavMessages; + + @State() effectiveLocale = ""; + + @Watch("effectiveLocale") + effectiveLocaleChange(): void { + updateMessages(this, this.effectiveLocale); + } @State() elWidth: number; @@ -349,20 +410,9 @@ export class TabNav { const isOverflowingRight = lastTitle.right > tabNavWidth; const isOverflowingLeft = firstTitle.left < 0; - const rightArrow = ( - - ); - const leftArrow = ( - - ); + + const rightArrow: VNode = this.renderOverflowIcons("right"); + const leftArrow: VNode = this.renderOverflowIcons("left"); return !isOverflowingRight && !isOverflowingLeft ? null From 4242f5261bccf192a4c7d5accf230c729b4be935 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 20 Sep 2023 16:01:05 -0700 Subject: [PATCH 007/108] subsitute icon with action button --- .../src/components/tab-nav/tab-nav.tsx | 96 +++++++++++-------- 1 file changed, 55 insertions(+), 41 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index bc0e37ad91e..82b4ae14c05 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -202,22 +202,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { // //-------------------------------------------------------------------------- - renderOverflowIcons(overflowDirection: string): VNode { - const { messages } = this; - const dirChevron: string = overflowDirection === "right" ? "chevron-right" : "chevron-left"; - const dirText: string = - overflowDirection === "right" ? messages.nextTabTitles : messages.previousTabsTitles; - - return ( - - ); - } - render(): VNode { const dir = getElementDir(this.el); const width = `${this.indicatorWidth}px`; @@ -248,7 +232,8 @@ export class TabNav implements LocalizedComponent, T9nComponent { ref={(el) => (this.activeIndicatorEl = el as HTMLElement)} /> - {this.layout === "inline" && this.getOverflowIcons()} + {console.log(this.renderOverflowIcons())} + {this.layout === "inline" && this.renderOverflowIcons()} ); @@ -391,7 +376,7 @@ export class TabNav implements LocalizedComponent, T9nComponent { this.elWidth = entries[0].contentRect.width; - this.getOverflowIcons(); + this.renderOverflowIcons(); }); //-------------------------------------------------------------------------- @@ -400,29 +385,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { // //-------------------------------------------------------------------------- - private getOverflowIcons() { - const tabNavWidth = this.el.offsetWidth; - - const tabTitles = Array.from(this.el.querySelectorAll("calcite-tab-title")); - - const firstTitle = tabTitles[0].getBoundingClientRect(); - const lastTitle = tabTitles[tabTitles.length - 1].getBoundingClientRect(); - - const isOverflowingRight = lastTitle.right > tabNavWidth; - const isOverflowingLeft = firstTitle.left < 0; - - const rightArrow: VNode = this.renderOverflowIcons("right"); - const leftArrow: VNode = this.renderOverflowIcons("left"); - - return !isOverflowingRight && !isOverflowingLeft - ? null - : isOverflowingRight && !isOverflowingLeft - ? rightArrow - : !isOverflowingRight && isOverflowingLeft - ? leftArrow - : [rightArrow, leftArrow]; - } - private scrollToNextTabTitles = (): void => { const tabTitles = this.el.querySelectorAll("calcite-tab-title"); const mobilePageWidth = window.innerWidth; @@ -573,4 +535,56 @@ export class TabNav implements LocalizedComponent, T9nComponent { tabTitles[this.selectedTabId].focus(); }); } + + private renderOverflowIcons(): VNode | VNode[] { + const { messages } = this; + console.log("getOverflowIcons function is running"); + const tabNavWidth = this.el.offsetWidth; + + const tabTitles = Array.from(this.el.querySelectorAll("calcite-tab-title")); + + const firstTitle = tabTitles[0].getBoundingClientRect(); + const lastTitle = tabTitles[tabTitles.length - 1].getBoundingClientRect(); + + const isOverflowingRight = lastTitle.right > tabNavWidth; + const isOverflowingLeft = firstTitle.left < 0; + + const getActionChevronDirection = (): VNode => { + const dirChevronIcon: string = isOverflowingRight ? "chevron-right" : "chevron-left"; + + const dirText: string = isOverflowingRight + ? messages.nextTabTitles + : messages.previousTabsTitles; + + const dirScroll = isOverflowingRight + ? this.scrollToNextTabTitles + : this.scrollToPreviousTabTitles; + + return ( + dirScroll} + onKeyDown={() => dirScroll} + text={dirText} + /> + ); + }; + + const showRightArrow: VNode = getActionChevronDirection(); + const showLeftArrow: VNode = getActionChevronDirection(); + + console.log("isOverflowingRight", isOverflowingRight); + + const action = + !isOverflowingRight && !isOverflowingLeft + ? null + : isOverflowingRight && !isOverflowingLeft + ? showRightArrow + : !isOverflowingRight && isOverflowingLeft + ? showLeftArrow + : [showRightArrow, showLeftArrow]; + + console.log("action returned from getOverflowIcons", action); + return action; + } } From 14136e829654a7d25a05fd487445462d6286aa33 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 20 Sep 2023 16:38:44 -0700 Subject: [PATCH 008/108] adapt css to action button instead of icon --- .../src/components/tab-nav/resources.ts | 7 ++++- .../src/components/tab-nav/tab-nav.scss | 6 ++-- .../src/components/tab-nav/tab-nav.tsx | 30 ++++++++++--------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/resources.ts b/packages/calcite-components/src/components/tab-nav/resources.ts index d26a010440e..7c5d8e037c3 100644 --- a/packages/calcite-components/src/components/tab-nav/resources.ts +++ b/packages/calcite-components/src/components/tab-nav/resources.ts @@ -1,4 +1,9 @@ -export const ICONS = { +export const ICON = { + arrowRight: "chevron-right", + arrowLeft: "chevron-left", +}; + +export const CSS = { arrowRight: "chevron-right", arrowLeft: "chevron-left", }; diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.scss b/packages/calcite-components/src/components/tab-nav/tab-nav.scss index e09b4923b24..bfdde85598a 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.scss +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.scss @@ -42,16 +42,16 @@ ease-out; } -calcite-icon { +calcite-action { @apply absolute; inset-block-start: 50%; transform: translateY(-50%); } -calcite-icon[icon="chevron-right"] { +.chevron-right { inset-inline-end: 0; } -calcite-icon[icon="chevron-left"] { +.chevron-left { inset-inline-start: 0; } diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 82b4ae14c05..fd79e0a2bec 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -31,6 +31,7 @@ import { TabChangeEventDetail, TabCloseEventDetail } from "../tab/interfaces"; import { TabID, TabLayout, TabPosition } from "../tabs/interfaces"; import { LocalizedComponent, connectLocalized, disconnectLocalized } from "../../utils/locale"; import { TabNavMessages } from "./assets/tab-nav/t9n"; +import { ICON, CSS } from "./resources"; /** * @slot - A slot for adding `calcite-tab-title`s. @@ -232,8 +233,8 @@ export class TabNav implements LocalizedComponent, T9nComponent { ref={(el) => (this.activeIndicatorEl = el as HTMLElement)} /> - {console.log(this.renderOverflowIcons())} - {this.layout === "inline" && this.renderOverflowIcons()} + {console.log(this.getOverflowIcons())} + {this.layout === "inline" && this.getOverflowIcons()} ); @@ -376,7 +377,7 @@ export class TabNav implements LocalizedComponent, T9nComponent { this.elWidth = entries[0].contentRect.width; - this.renderOverflowIcons(); + this.getOverflowIcons(); }); //-------------------------------------------------------------------------- @@ -536,7 +537,7 @@ export class TabNav implements LocalizedComponent, T9nComponent { }); } - private renderOverflowIcons(): VNode | VNode[] { + private getOverflowIcons(): VNode | VNode[] { const { messages } = this; console.log("getOverflowIcons function is running"); const tabNavWidth = this.el.offsetWidth; @@ -549,19 +550,20 @@ export class TabNav implements LocalizedComponent, T9nComponent { const isOverflowingRight = lastTitle.right > tabNavWidth; const isOverflowingLeft = firstTitle.left < 0; - const getActionChevronDirection = (): VNode => { - const dirChevronIcon: string = isOverflowingRight ? "chevron-right" : "chevron-left"; + const getActionChevronDirection = (overflowDirection: string): VNode => { + const dirActionClass: string = overflowDirection === "right" ? CSS.arrowRight : CSS.arrowLeft; + const dirChevronIcon: string = + overflowDirection === "right" ? ICON.arrowRight : ICON.arrowLeft; - const dirText: string = isOverflowingRight - ? messages.nextTabTitles - : messages.previousTabsTitles; + const dirText: string = + overflowDirection === "right" ? messages.nextTabTitles : messages.previousTabsTitles; - const dirScroll = isOverflowingRight - ? this.scrollToNextTabTitles - : this.scrollToPreviousTabTitles; + const dirScroll = + overflowDirection === "right" ? this.scrollToNextTabTitles : this.scrollToPreviousTabTitles; return ( dirScroll} onKeyDown={() => dirScroll} @@ -570,8 +572,8 @@ export class TabNav implements LocalizedComponent, T9nComponent { ); }; - const showRightArrow: VNode = getActionChevronDirection(); - const showLeftArrow: VNode = getActionChevronDirection(); + const showRightArrow: VNode = getActionChevronDirection("right"); + const showLeftArrow: VNode = getActionChevronDirection("left"); console.log("isOverflowingRight", isOverflowingRight); From fb4d4f680ff13edc0f7fdd803f1c9f35047e42b5 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 20 Sep 2023 20:04:42 -0700 Subject: [PATCH 009/108] pas in overflowDirection to scrollDir action callback --- .../src/components/tab-nav/tab-nav.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index fd79e0a2bec..61268938f36 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -558,15 +558,17 @@ export class TabNav implements LocalizedComponent, T9nComponent { const dirText: string = overflowDirection === "right" ? messages.nextTabTitles : messages.previousTabsTitles; - const dirScroll = - overflowDirection === "right" ? this.scrollToNextTabTitles : this.scrollToPreviousTabTitles; + const dirScroll = (overflowDirection: string) => + overflowDirection === "right" + ? this.scrollToNextTabTitles() + : this.scrollToPreviousTabTitles(); return ( dirScroll} - onKeyDown={() => dirScroll} + onClick={() => dirScroll(overflowDirection)} + onKeyDown={() => dirScroll(overflowDirection)} text={dirText} /> ); From 2c9f93b8e3dbb174b683864c9063932d90474df7 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 20 Sep 2023 20:46:44 -0700 Subject: [PATCH 010/108] cleanup --- .../calcite-components/src/components/tab-nav/tab-nav.scss | 1 + packages/calcite-components/src/components/tab-nav/tab-nav.tsx | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.scss b/packages/calcite-components/src/components/tab-nav/tab-nav.scss index bfdde85598a..72ff7bcfd5b 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.scss +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.scss @@ -47,6 +47,7 @@ calcite-action { inset-block-start: 50%; transform: translateY(-50%); } + .chevron-right { inset-inline-end: 0; } diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 61268938f36..cf1bfa69158 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -209,9 +209,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { const offset = `${this.indicatorOffset}px`; const indicatorStyle = dir !== "rtl" ? { width, left: offset } : { width, right: offset }; - // const widthBreakpoints = this.breakpoints.width; - // const { elWidth } = this; - return (
Date: Fri, 22 Sep 2023 14:02:20 -0700 Subject: [PATCH 011/108] add a story and cleanup --- .../src/components/tab-nav/tab-nav.tsx | 12 ++----- .../src/components/tabs/tabs.stories.ts | 35 ++++++++++++++++++- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index cf1bfa69158..10d5ef01077 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -24,7 +24,6 @@ import { FocusElementInGroupDestination, getElementDir, } from "../../utils/dom"; -// import { Breakpoints, getBreakpoints } from "../../utils/responsive"; import { createObserver } from "../../utils/observers"; import { Scale } from "../interfaces"; import { TabChangeEventDetail, TabCloseEventDetail } from "../tab/interfaces"; @@ -230,7 +229,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { ref={(el) => (this.activeIndicatorEl = el as HTMLElement)} />
- {console.log(this.getOverflowIcons())} {this.layout === "inline" && this.getOverflowIcons()}
@@ -385,7 +383,7 @@ export class TabNav implements LocalizedComponent, T9nComponent { private scrollToNextTabTitles = (): void => { const tabTitles = this.el.querySelectorAll("calcite-tab-title"); - const mobilePageWidth = window.innerWidth; + const mobilePageWidth = this.el.getBoundingClientRect().width; let lastVisibleTabTitleIndex = -1; let scrollAmount = 0; @@ -419,7 +417,7 @@ export class TabNav implements LocalizedComponent, T9nComponent { private scrollToPreviousTabTitles = (): void => { const tabTitles = this.el.querySelectorAll("calcite-tab-title"); - const mobilePageWidth = window.innerWidth; + const mobilePageWidth = this.el.getBoundingClientRect().width; let firstVisibleTabTitleIndex = -1; let scrollAmount = 0; @@ -436,7 +434,7 @@ export class TabNav implements LocalizedComponent, T9nComponent { } // Calculate the scroll amount to bring the previous set of tab titles into view - if (firstVisibleTabTitleIndex !== 0) { + if (firstVisibleTabTitleIndex !== -1) { const previousTabTitleIndex = firstVisibleTabTitleIndex - 1; const previousTabTitle = tabTitles[previousTabTitleIndex]; if (previousTabTitle) { @@ -536,7 +534,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { private getOverflowIcons(): VNode | VNode[] { const { messages } = this; - console.log("getOverflowIcons function is running"); const tabNavWidth = this.el.offsetWidth; const tabTitles = Array.from(this.el.querySelectorAll("calcite-tab-title")); @@ -574,8 +571,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { const showRightArrow: VNode = getActionChevronDirection("right"); const showLeftArrow: VNode = getActionChevronDirection("left"); - console.log("isOverflowingRight", isOverflowingRight); - const action = !isOverflowingRight && !isOverflowingLeft ? null @@ -585,7 +580,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { ? showLeftArrow : [showRightArrow, showLeftArrow]; - console.log("action returned from getOverflowIcons", action); return action; } } diff --git a/packages/calcite-components/src/components/tabs/tabs.stories.ts b/packages/calcite-components/src/components/tabs/tabs.stories.ts index f7e8ac5ef76..25db5e55746 100644 --- a/packages/calcite-components/src/components/tabs/tabs.stories.ts +++ b/packages/calcite-components/src/components/tabs/tabs.stories.ts @@ -1,7 +1,8 @@ import { select } from "@storybook/addon-knobs"; import { boolean, iconNames, storyFilters } from "../../../.storybook/helpers"; import { placeholderImage } from "../../../.storybook/placeholderImage"; -import { modesDarkDefault } from "../../../.storybook/utils"; +import { createBreakpointStories, modesDarkDefault } from "../../../.storybook/utils"; +import { locales } from "../../utils/locale"; import { html } from "../../../support/formatting"; import readme3 from "../tab-nav/readme.md"; import readme4 from "../tab-title/readme.md"; @@ -300,3 +301,35 @@ export const updateIndicatorOffset_TestOnly = (): string => html` updateIndicatorOffset_TestOnly.parameters = { chromatic: { delay: 1000 }, }; + +export const responsiveTabs = (): string => + createBreakpointStories(html` + + + + Tab 1 Title + Tab 2 Title + An Ultramarathon of a Tab Title, why not. + Tab 4 Title + Tab 5 Title + Tab 6 Title + Tab 7 Title + Tab 8 Title + + Tab 1 Content + Tab 2 Content + Tab 3 Content + Tab 4 Content + Tab 5 Content + Tab 6 Content + Tab 7 Content + Tab 8 Content + + + `); From 19c2fac9d9e2f0384ea87be5ae13eabe981f17a4 Mon Sep 17 00:00:00 2001 From: eliza Date: Fri, 22 Sep 2023 14:21:37 -0700 Subject: [PATCH 012/108] slim down scrollToNextTabTitles function --- .../src/components/tab-nav/tab-nav.tsx | 73 ++++++++----------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 10d5ef01077..06b28b92008 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -381,65 +381,42 @@ export class TabNav implements LocalizedComponent, T9nComponent { // //-------------------------------------------------------------------------- - private scrollToNextTabTitles = (): void => { - const tabTitles = this.el.querySelectorAll("calcite-tab-title"); + private findVisibleTabTitleIndex = (tabTitles: NodeListOf, isNext: boolean): number => { const mobilePageWidth = this.el.getBoundingClientRect().width; + let visibleTabTitleIndex = -1; - let lastVisibleTabTitleIndex = -1; - let scrollAmount = 0; - - // Find the index of the last tab title visible within the mobile-sized tab nav for (let i = 0; i < tabTitles.length; i++) { const tabTitle = tabTitles[i]; const tabTitleRect = tabTitle.getBoundingClientRect(); - if (tabTitleRect.right <= mobilePageWidth) { - lastVisibleTabTitleIndex = i; - } else { - break; - } - } - - // Calculate the scroll amount to bring the next set of tab titles into view - if (lastVisibleTabTitleIndex !== -1) { - const nextTabTitleIndex = lastVisibleTabTitleIndex + 1; - const nextTabTitle = tabTitles[nextTabTitleIndex]; - if (nextTabTitle) { - const nextTabTitleRect = nextTabTitle.getBoundingClientRect(); - scrollAmount = nextTabTitleRect.left - this.el.getBoundingClientRect().left; + if ( + (isNext && tabTitleRect.right <= mobilePageWidth) || + (!isNext && tabTitleRect.left >= 0) + ) { + visibleTabTitleIndex = i; + if (!isNext) { + break; + } } } - requestAnimationFrame(() => { - this.tabNavEl.scrollLeft += scrollAmount; - }); + return visibleTabTitleIndex; }; - private scrollToPreviousTabTitles = (): void => { + private scrollToTabTitles = (isNext: boolean): void => { const tabTitles = this.el.querySelectorAll("calcite-tab-title"); - const mobilePageWidth = this.el.getBoundingClientRect().width; - - let firstVisibleTabTitleIndex = -1; + const visibleTabTitleIndex = this.findVisibleTabTitleIndex(tabTitles, isNext); let scrollAmount = 0; - // Find the index of the first tab title visible within the mobile-sized tab nav - for (let i = 0; i < tabTitles.length; i++) { - const tabTitle = tabTitles[i]; - const tabTitleRect = tabTitle.getBoundingClientRect(); - - if (tabTitleRect.left >= 0) { - firstVisibleTabTitleIndex = i; - break; - } - } + if (visibleTabTitleIndex !== -1) { + const targetTabTitleIndex = isNext ? visibleTabTitleIndex + 1 : visibleTabTitleIndex - 1; + const targetTabTitle = tabTitles[targetTabTitleIndex]; - // Calculate the scroll amount to bring the previous set of tab titles into view - if (firstVisibleTabTitleIndex !== -1) { - const previousTabTitleIndex = firstVisibleTabTitleIndex - 1; - const previousTabTitle = tabTitles[previousTabTitleIndex]; - if (previousTabTitle) { - const previousTabTitleRect = previousTabTitle.getBoundingClientRect(); - scrollAmount = previousTabTitleRect.right - mobilePageWidth; + if (targetTabTitle) { + const targetTabTitleRect = targetTabTitle.getBoundingClientRect(); + scrollAmount = isNext + ? targetTabTitleRect.left - this.el.getBoundingClientRect().left + : targetTabTitleRect.right - this.el.getBoundingClientRect().width; } } @@ -448,6 +425,14 @@ export class TabNav implements LocalizedComponent, T9nComponent { }); }; + private scrollToNextTabTitles = (): void => { + this.scrollToTabTitles(true); + }; + + private scrollToPreviousTabTitles = (): void => { + this.scrollToTabTitles(false); + }; + handleTabFocus = ( event: CustomEvent, el: HTMLCalciteTabTitleElement, From 6ad662c07b40fc4c97961f068f47206aca0c4dab Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 27 Sep 2023 17:32:25 -0700 Subject: [PATCH 013/108] WIP: e2e test for overflowScenarios = [right, left, both] --- .../src/components/tab-nav/tab-nav.e2e.ts | 138 ++++++++++++++++-- 1 file changed, 122 insertions(+), 16 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index 10d1d755529..53fa66c6428 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -1,6 +1,7 @@ -import { newE2EPage } from "@stencil/core/testing"; +import { newE2EPage, E2EPage } from "@stencil/core/testing"; import { accessible, renders, hidden, t9n } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; +import { CSS } from "./resources"; describe("calcite-tab-nav", () => { const tabNavHtml = ""; @@ -97,11 +98,11 @@ describe("calcite-tab-nav", () => { it("should render with small scale", async () => { const page = await newE2EPage({ html: ` - Tab 1 Title - Tab 2 Title - Tab 3 Title - Tab 4 Title - `, + Tab 1 Title + Tab 2 Title + Tab 3 Title + Tab 4 Title +
`, }); const element = await page.find("calcite-tab-nav"); expect(await (await element.getComputedStyle())["minHeight"]).toEqual("24px"); @@ -113,11 +114,11 @@ describe("calcite-tab-nav", () => { it("should render with medium scale", async () => { const page = await newE2EPage({ html: ` - Tab 1 Title - Tab 2 Title - Tab 3 Title - Tab 4 Title - `, + Tab 1 Title + Tab 2 Title + Tab 3 Title + Tab 4 Title + `, }); const element = await page.find("calcite-tab-nav"); expect(await (await element.getComputedStyle())["minHeight"]).toEqual("32px"); @@ -129,11 +130,11 @@ describe("calcite-tab-nav", () => { it("should render with medium scale", async () => { const page = await newE2EPage({ html: ` - Tab 1 Title - Tab 2 Title - Tab 3 Title - Tab 4 Title - `, + Tab 1 Title + Tab 2 Title + Tab 3 Title + Tab 4 Title + `, }); const element = await page.find("calcite-tab-nav"); expect(await (await element.getComputedStyle())["minHeight"]).toEqual("44px"); @@ -196,4 +197,109 @@ describe("calcite-tab-nav", () => { await page.keyboard.press("Home"); expect(await page.evaluate(() => document.activeElement.id)).toBe("tab1"); }); + + const inlineTabsWithVariedTitleLength = html` + + + Tab 1 Title + Tab 2 Title + An Ultramarathon of a Tab Title, why not. + Tab 4 Title + Tab 5 Title + Tab 6 Title + + Tab 1 Content + Tab 2 Content + Tab 3 Content + Tab 4 Content + Tab 5 Content + Tab 6 Content + + `; + + describe("responsive tabs for inline layout", () => { + const overflowScenarios = ["right", "left", "both"]; + let page: E2EPage; + + beforeEach(async () => { + page = await newE2EPage(); + await page.setContent(inlineTabsWithVariedTitleLength); + }); + + it("should overflow tab-titles that don't fit within the bounds of tab-nav", async () => { + const { tabTitlesTotalWidth, tabNavWidth } = await page.evaluate(() => { + const tabNav = document.getElementById("testSubjectNav") as HTMLCalciteTabNavElement; + const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")) as HTMLCalciteTabTitleElement[]; + + const tabNavWidth = tabNav.offsetWidth; + const tabTitlesTotalWidth = tabTitles.reduce((sum, tabTitle: HTMLElement) => { + return sum + tabTitle.offsetWidth; + }, 0); + + return { tabTitlesTotalWidth, tabNavWidth }; + }); + + expect(tabTitlesTotalWidth).toBeGreaterThan(tabNavWidth); + }); + + overflowScenarios.forEach(async (overflowScenario) => { + if (overflowScenario === "right") { + it("should show action buttons with correct chevrons for overflow to the right", async () => { + const isOverflowingRight = await page.evaluate(() => { + const tabNav = document.getElementById("testSubjectNav") as HTMLCalciteTabNavElement; + const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")); + + tabNav.scrollLeft = 0; + const mobilePageWidth = tabNav.getBoundingClientRect().width; + + const visibleTabTitles = tabTitles.filter((tabTitle) => { + const tabTitleRect = tabTitle.getBoundingClientRect(); + return tabTitleRect.left >= 0 && tabTitleRect.right <= mobilePageWidth; + }); + const firstRightOverflowItem = tabTitles[visibleTabTitles.length]; + + const isOverflowingRight = firstRightOverflowItem.getBoundingClientRect().right > mobilePageWidth; + return isOverflowingRight; + }); + expect(isOverflowingRight).toBe(true); + + expect(await page.find(`#testSubjectNav >>> .${CSS.arrowRight}`)).not.toBe(null); + expect(await page.find(`#testSubjectNav >>> .${CSS.arrowLeft}`)).toBe(null); + }); + } else if (overflowScenario === "left") { + it("should show action buttons with correct chevrons for overflow to the left", async () => { + const { isOverflowingLeft } = await page.evaluate(async () => { + const tabNav = document.getElementById("testSubjectNav") as HTMLCalciteTabNavElement; + const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")); + + const mobilePageWidth = tabNav.getBoundingClientRect().width; + + tabNav.scrollLeft += tabTitles[tabTitles.length - 1].getBoundingClientRect().right; + + const visibleTabTitles = tabTitles.filter((tabTitle) => { + const tabTitleRect = tabTitle.getBoundingClientRect(); + return tabTitleRect.left >= 0 && tabTitleRect.right <= mobilePageWidth; + }); + + const firstLeftOverflowItem = tabTitles[visibleTabTitles.length]; + const isOverflowingLeft = firstLeftOverflowItem.getBoundingClientRect().right < mobilePageWidth; + + return { isOverflowingLeft }; + }); + + expect(isOverflowingLeft).toBe(true); + + expect(await page.find(`#testSubjectNav >>> .${CSS.arrowRight}`)).toBe(null); + expect(await page.find(`#testSubjectNav >>> .${CSS.arrowLeft}`)).not.toBe(null); + }); + } else if (overflowScenario === "both") { + it("should show action buttons with correct chevrons for overflow to both sides", async () => { + await page.evaluate(() => { + const tabNav = document.querySelector("calcite-tab-nav") as HTMLElement; + tabNav.scrollLeft = tabNav.scrollWidth / 2; + }); + }); + } + }); + }); }); From f505684dcb3710000a5e17e6a71dda5670b15b93 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 27 Sep 2023 18:13:20 -0700 Subject: [PATCH 014/108] WIP: work out leaner combined test for both scenarios --- .../src/components/tab-nav/tab-nav.e2e.ts | 68 ++++++++++++++----- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index 53fa66c6428..d0bb87a92cb 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -1,3 +1,4 @@ +/* eslint-disable jest/no-conditional-expect */ import { newE2EPage, E2EPage } from "@stencil/core/testing"; import { accessible, renders, hidden, t9n } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; @@ -257,8 +258,8 @@ describe("calcite-tab-nav", () => { return tabTitleRect.left >= 0 && tabTitleRect.right <= mobilePageWidth; }); const firstRightOverflowItem = tabTitles[visibleTabTitles.length]; - const isOverflowingRight = firstRightOverflowItem.getBoundingClientRect().right > mobilePageWidth; + return isOverflowingRight; }); expect(isOverflowingRight).toBe(true); @@ -268,7 +269,7 @@ describe("calcite-tab-nav", () => { }); } else if (overflowScenario === "left") { it("should show action buttons with correct chevrons for overflow to the left", async () => { - const { isOverflowingLeft } = await page.evaluate(async () => { + const isOverflowingLeft = await page.evaluate(async () => { const tabNav = document.getElementById("testSubjectNav") as HTMLCalciteTabNavElement; const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")); @@ -276,15 +277,9 @@ describe("calcite-tab-nav", () => { tabNav.scrollLeft += tabTitles[tabTitles.length - 1].getBoundingClientRect().right; - const visibleTabTitles = tabTitles.filter((tabTitle) => { - const tabTitleRect = tabTitle.getBoundingClientRect(); - return tabTitleRect.left >= 0 && tabTitleRect.right <= mobilePageWidth; - }); - - const firstLeftOverflowItem = tabTitles[visibleTabTitles.length]; - const isOverflowingLeft = firstLeftOverflowItem.getBoundingClientRect().right < mobilePageWidth; + const isOverflowingLeft = tabTitles[tabTitles.length - 1].getBoundingClientRect().right < mobilePageWidth; - return { isOverflowingLeft }; + return isOverflowingLeft; }); expect(isOverflowingLeft).toBe(true); @@ -292,14 +287,53 @@ describe("calcite-tab-nav", () => { expect(await page.find(`#testSubjectNav >>> .${CSS.arrowRight}`)).toBe(null); expect(await page.find(`#testSubjectNav >>> .${CSS.arrowLeft}`)).not.toBe(null); }); - } else if (overflowScenario === "both") { - it("should show action buttons with correct chevrons for overflow to both sides", async () => { - await page.evaluate(() => { - const tabNav = document.querySelector("calcite-tab-nav") as HTMLElement; - tabNav.scrollLeft = tabNav.scrollWidth / 2; - }); - }); } }); + + // overflowScenarios.forEach(async (overflowScenario) => { + // it(`should show action buttons with correct chevrons for overflow to the ${overflowScenario}`, async () => { + // const isOverflowingRight = overflowScenario === "right"; + // const isOverflowingLeft = overflowScenario === "left"; + + // await page.evaluate( + // (isOverflowingRight, isOverflowingLeft) => { + // const tabNav = document.getElementById("testSubjectNav") as HTMLCalciteTabNavElement; + // const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")); + + // tabNav.scrollLeft = isOverflowingRight ? 0 : tabTitles[tabTitles.length - 1].getBoundingClientRect().right; + + // const mobilePageWidth = tabNav.getBoundingClientRect().width; + + // const visibleTabTitles = tabTitles.filter((tabTitle) => { + // const tabTitleRect = tabTitle.getBoundingClientRect(); + // return tabTitleRect.left >= 0 && tabTitleRect.right <= mobilePageWidth; + // }); + + // const firstOverflowItem = isOverflowingRight + // ? tabTitles[visibleTabTitles.length] + // : tabTitles[tabTitles.length - visibleTabTitles.length - 1]; + + // const isOverflowing = isOverflowingRight + // ? firstOverflowItem.getBoundingClientRect().right > mobilePageWidth + // : firstOverflowItem.getBoundingClientRect().right < mobilePageWidth; + + // return isOverflowing; + // }, + // isOverflowingRight, + // isOverflowingLeft + // ); + + // expect(isOverflowingRight).toBe(true); + // expect(isOverflowingLeft).toBe(true); + + // if (isOverflowingRight) { + // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowRight}`)).not.toBe(null); + // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowLeft}`)).toBe(null); + // } else if (isOverflowingLeft) { + // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowRight}`)).toBe(null); + // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowLeft}`)).not.toBe(null); + // } + // }); + // }); }); }); From 91e0b62dbec2c014eaa57b1dc913b5183585e0b9 Mon Sep 17 00:00:00 2001 From: eliza Date: Thu, 28 Sep 2023 16:47:17 -0700 Subject: [PATCH 015/108] call clientWidth instead of getBoundingClientRect.width, use for..of instead for iteration and remove unused state --- .../src/components/tab-nav/tab-nav.tsx | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 06b28b92008..07c85bdf203 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -358,9 +358,7 @@ export class TabNav implements LocalizedComponent, T9nComponent { updateMessages(this, this.effectiveLocale); } - @State() elWidth: number; - - resizeObserver = createObserver("resize", (entries) => { + resizeObserver = createObserver("resize", () => { if (!this.activeIndicatorEl) { return; } @@ -370,8 +368,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { this.updateActiveWidth(); this.updateOffsetPosition(); - this.elWidth = entries[0].contentRect.width; - this.getOverflowIcons(); }); @@ -382,24 +378,24 @@ export class TabNav implements LocalizedComponent, T9nComponent { //-------------------------------------------------------------------------- private findVisibleTabTitleIndex = (tabTitles: NodeListOf, isNext: boolean): number => { - const mobilePageWidth = this.el.getBoundingClientRect().width; + const elWidth = this.el.clientWidth; let visibleTabTitleIndex = -1; - for (let i = 0; i < tabTitles.length; i++) { - const tabTitle = tabTitles[i]; - const tabTitleRect = tabTitle.getBoundingClientRect(); - - if ( - (isNext && tabTitleRect.right <= mobilePageWidth) || - (!isNext && tabTitleRect.left >= 0) - ) { - visibleTabTitleIndex = i; - if (!isNext) { - break; + function findIndex() { + for (const [index, tabTitle] of Array.from(tabTitles).entries()) { + const tabTitleRect = tabTitle.getBoundingClientRect(); + + if ((isNext && tabTitleRect.right <= elWidth) || (!isNext && tabTitleRect.left >= 0)) { + visibleTabTitleIndex = index; + if (!isNext) { + return true; + } } } } + findIndex(); + return visibleTabTitleIndex; }; @@ -416,7 +412,7 @@ export class TabNav implements LocalizedComponent, T9nComponent { const targetTabTitleRect = targetTabTitle.getBoundingClientRect(); scrollAmount = isNext ? targetTabTitleRect.left - this.el.getBoundingClientRect().left - : targetTabTitleRect.right - this.el.getBoundingClientRect().width; + : targetTabTitleRect.right - this.el.clientWidth; } } From 2fc31b197ed2e825ea92d85e935d4a494b3b257a Mon Sep 17 00:00:00 2001 From: eliza Date: Fri, 29 Sep 2023 15:05:04 -0700 Subject: [PATCH 016/108] adapt to tab order change for ltr/rtl bidirecitonal design --- .../src/components/tab-nav/resources.ts | 8 ++-- .../src/components/tab-nav/tab-nav.e2e.ts | 26 +++++------ .../src/components/tab-nav/tab-nav.scss | 4 +- .../src/components/tab-nav/tab-nav.tsx | 46 ++++++++++++------- 4 files changed, 49 insertions(+), 35 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/resources.ts b/packages/calcite-components/src/components/tab-nav/resources.ts index 7c5d8e037c3..192e8fd5e78 100644 --- a/packages/calcite-components/src/components/tab-nav/resources.ts +++ b/packages/calcite-components/src/components/tab-nav/resources.ts @@ -1,9 +1,9 @@ export const ICON = { - arrowRight: "chevron-right", - arrowLeft: "chevron-left", + chevronRight: "chevron-right", + chevronLeft: "chevron-left", }; export const CSS = { - arrowRight: "chevron-right", - arrowLeft: "chevron-left", + arrowEnd: "arrow-end", + arrowStart: "arrow-start", }; diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index d0bb87a92cb..b9ced958605 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -219,7 +219,7 @@ describe("calcite-tab-nav", () => { `; describe("responsive tabs for inline layout", () => { - const overflowScenarios = ["right", "left", "both"]; + const overflowScenarios = ["end", "start", "both"]; let page: E2EPage; beforeEach(async () => { @@ -244,8 +244,8 @@ describe("calcite-tab-nav", () => { }); overflowScenarios.forEach(async (overflowScenario) => { - if (overflowScenario === "right") { - it("should show action buttons with correct chevrons for overflow to the right", async () => { + if (overflowScenario === "end") { + it("should show action buttons with correct chevrons for overflow to the end", async () => { const isOverflowingRight = await page.evaluate(() => { const tabNav = document.getElementById("testSubjectNav") as HTMLCalciteTabNavElement; const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")); @@ -264,8 +264,8 @@ describe("calcite-tab-nav", () => { }); expect(isOverflowingRight).toBe(true); - expect(await page.find(`#testSubjectNav >>> .${CSS.arrowRight}`)).not.toBe(null); - expect(await page.find(`#testSubjectNav >>> .${CSS.arrowLeft}`)).toBe(null); + expect(await page.find(`#testSubjectNav >>> .${CSS.arrowEnd}`)).not.toBe(null); + expect(await page.find(`#testSubjectNav >>> .${CSS.arrowStart}`)).toBe(null); }); } else if (overflowScenario === "left") { it("should show action buttons with correct chevrons for overflow to the left", async () => { @@ -284,16 +284,16 @@ describe("calcite-tab-nav", () => { expect(isOverflowingLeft).toBe(true); - expect(await page.find(`#testSubjectNav >>> .${CSS.arrowRight}`)).toBe(null); - expect(await page.find(`#testSubjectNav >>> .${CSS.arrowLeft}`)).not.toBe(null); + expect(await page.find(`#testSubjectNav >>> .${CSS.arrowEnd}`)).toBe(null); + expect(await page.find(`#testSubjectNav >>> .${CSS.arrowStart}`)).not.toBe(null); }); } }); // overflowScenarios.forEach(async (overflowScenario) => { // it(`should show action buttons with correct chevrons for overflow to the ${overflowScenario}`, async () => { - // const isOverflowingRight = overflowScenario === "right"; - // const isOverflowingLeft = overflowScenario === "left"; + // const isOverflowingRight = overflowScenario === "end"; + // const isOverflowingLeft = overflowScenario === "start"; // await page.evaluate( // (isOverflowingRight, isOverflowingLeft) => { @@ -327,11 +327,11 @@ describe("calcite-tab-nav", () => { // expect(isOverflowingLeft).toBe(true); // if (isOverflowingRight) { - // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowRight}`)).not.toBe(null); - // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowLeft}`)).toBe(null); + // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowEnd}`)).not.toBe(null); + // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowStart}`)).toBe(null); // } else if (isOverflowingLeft) { - // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowRight}`)).toBe(null); - // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowLeft}`)).not.toBe(null); + // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowEnd}`)).toBe(null); + // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowRight}`)).not.toBe(null); // } // }); // }); diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.scss b/packages/calcite-components/src/components/tab-nav/tab-nav.scss index 72ff7bcfd5b..4c677905243 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.scss +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.scss @@ -48,11 +48,11 @@ calcite-action { transform: translateY(-50%); } -.chevron-right { +.arrow-end { inset-inline-end: 0; } -.chevron-left { +.arrow-start { inset-inline-start: 0; } diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 07c85bdf203..d4255ca18ff 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -514,6 +514,7 @@ export class TabNav implements LocalizedComponent, T9nComponent { } private getOverflowIcons(): VNode | VNode[] { + const dir = getElementDir(this.el); const { messages } = this; const tabNavWidth = this.el.offsetWidth; @@ -522,19 +523,33 @@ export class TabNav implements LocalizedComponent, T9nComponent { const firstTitle = tabTitles[0].getBoundingClientRect(); const lastTitle = tabTitles[tabTitles.length - 1].getBoundingClientRect(); - const isOverflowingRight = lastTitle.right > tabNavWidth; - const isOverflowingLeft = firstTitle.left < 0; + let isOverflowingEnd: boolean; + let isOverflowingStart: boolean; + + if (dir !== "rtl") { + isOverflowingEnd = lastTitle.right > tabNavWidth; + isOverflowingStart = firstTitle.left < 0; + } else if (dir === "rtl") { + isOverflowingEnd = lastTitle.right < tabNavWidth; + isOverflowingStart = firstTitle.left < 0; + } const getActionChevronDirection = (overflowDirection: string): VNode => { - const dirActionClass: string = overflowDirection === "right" ? CSS.arrowRight : CSS.arrowLeft; - const dirChevronIcon: string = - overflowDirection === "right" ? ICON.arrowRight : ICON.arrowLeft; + const dirActionClass: string = overflowDirection === "end" ? CSS.arrowEnd : CSS.arrowStart; + + let dirChevronIcon: string; + + if (dir !== "rtl") { + dirChevronIcon = overflowDirection === "end" ? ICON.chevronRight : ICON.chevronLeft; + } else if (dir === "rtl") { + dirChevronIcon = overflowDirection === "end" ? ICON.chevronLeft : ICON.chevronRight; + } const dirText: string = - overflowDirection === "right" ? messages.nextTabTitles : messages.previousTabsTitles; + overflowDirection === "end" ? messages.nextTabTitles : messages.previousTabsTitles; const dirScroll = (overflowDirection: string) => - overflowDirection === "right" + overflowDirection === "end" ? this.scrollToNextTabTitles() : this.scrollToPreviousTabTitles(); @@ -543,23 +558,22 @@ export class TabNav implements LocalizedComponent, T9nComponent { class={dirActionClass} icon={dirChevronIcon} onClick={() => dirScroll(overflowDirection)} - onKeyDown={() => dirScroll(overflowDirection)} text={dirText} /> ); }; - const showRightArrow: VNode = getActionChevronDirection("right"); - const showLeftArrow: VNode = getActionChevronDirection("left"); + const showEndAction = getActionChevronDirection("end"); + const showStartAction = getActionChevronDirection("start"); const action = - !isOverflowingRight && !isOverflowingLeft + !isOverflowingEnd && !isOverflowingStart ? null - : isOverflowingRight && !isOverflowingLeft - ? showRightArrow - : !isOverflowingRight && isOverflowingLeft - ? showLeftArrow - : [showRightArrow, showLeftArrow]; + : isOverflowingEnd && !isOverflowingStart + ? showEndAction + : !isOverflowingEnd && isOverflowingStart + ? showStartAction + : [showEndAction, showStartAction]; return action; } From f35d11191f720153562e643cbd6d6a535a68890c Mon Sep 17 00:00:00 2001 From: eliza Date: Fri, 29 Sep 2023 18:04:25 -0700 Subject: [PATCH 017/108] styling of the action button with box-shadow --- .../calcite-components/src/components/tab-nav/tab-nav.scss | 4 ++++ .../calcite-components/src/components/tab-nav/tab-nav.tsx | 2 ++ 2 files changed, 6 insertions(+) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.scss b/packages/calcite-components/src/components/tab-nav/tab-nav.scss index 4c677905243..d85d0ce1c9d 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.scss +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.scss @@ -49,11 +49,15 @@ calcite-action { } .arrow-end { + @apply h-full overflow-hidden; inset-inline-end: 0; + box-shadow: -10px 0 10px -2px white; } .arrow-start { + @apply h-full overflow-hidden; inset-inline-start: 0; + box-shadow: 10px 0 10px -2px white; } :host([layout="center"]) .tab-nav { diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index d4255ca18ff..be0b3e66f12 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -557,7 +557,9 @@ export class TabNav implements LocalizedComponent, T9nComponent { dirScroll(overflowDirection)} + scale={this.scale} text={dirText} /> ); From 4a22ae997f1485a1d997fe6c215a7588da875172 Mon Sep 17 00:00:00 2001 From: eliza Date: Sun, 1 Oct 2023 16:57:34 -0700 Subject: [PATCH 018/108] simplify arrow navigation logic --- .../src/components/tab-nav/tab-nav.tsx | 82 +++++++------------ 1 file changed, 28 insertions(+), 54 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index be0b3e66f12..bbe7a5895cd 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -402,7 +402,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { private scrollToTabTitles = (isNext: boolean): void => { const tabTitles = this.el.querySelectorAll("calcite-tab-title"); const visibleTabTitleIndex = this.findVisibleTabTitleIndex(tabTitles, isNext); - let scrollAmount = 0; if (visibleTabTitleIndex !== -1) { const targetTabTitleIndex = isNext ? visibleTabTitleIndex + 1 : visibleTabTitleIndex - 1; @@ -410,15 +409,16 @@ export class TabNav implements LocalizedComponent, T9nComponent { if (targetTabTitle) { const targetTabTitleRect = targetTabTitle.getBoundingClientRect(); - scrollAmount = isNext + + const scrollAmount = isNext ? targetTabTitleRect.left - this.el.getBoundingClientRect().left : targetTabTitleRect.right - this.el.clientWidth; + + requestAnimationFrame(() => { + this.tabNavEl.scrollLeft += scrollAmount; + }); } } - - requestAnimationFrame(() => { - this.tabNavEl.scrollLeft += scrollAmount; - }); }; private scrollToNextTabTitles = (): void => { @@ -485,11 +485,10 @@ export class TabNav implements LocalizedComponent, T9nComponent { private handleTabTitleClose(closedTabTitleEl: HTMLCalciteTabTitleElement): void { const { tabTitles } = this; - const visibleTabTitlesIndices = tabTitles.reduce( - (tabTitleIndices, tabTitle, index) => - !tabTitle.closed ? [...tabTitleIndices, index] : tabTitleIndices, - [] - ); + const visibleTabTitlesIndices = tabTitles + .filter((tabTitle) => !tabTitle.closed) + .map((_, index) => index); + const totalVisibleTabTitles = visibleTabTitlesIndices.length; if (totalVisibleTabTitles === 1 && tabTitles[visibleTabTitlesIndices[0]].closable) { @@ -497,13 +496,13 @@ export class TabNav implements LocalizedComponent, T9nComponent { this.selectedTabId = visibleTabTitlesIndices[0]; } else if (totalVisibleTabTitles > 1) { const closedTabTitleIndex = tabTitles.findIndex((el) => el === closedTabTitleEl); + const nextTabTitleIndex = visibleTabTitlesIndices.find( (value) => value > closedTabTitleIndex ); - if (this.selectedTabId === closedTabTitleIndex) { - this.selectedTabId = nextTabTitleIndex ? nextTabTitleIndex : totalVisibleTabTitles - 1; - } + this.selectedTabId = + nextTabTitleIndex !== undefined ? nextTabTitleIndex : totalVisibleTabTitles - 1; } requestAnimationFrame(() => { @@ -513,52 +512,32 @@ export class TabNav implements LocalizedComponent, T9nComponent { }); } - private getOverflowIcons(): VNode | VNode[] { + private getOverflowIcons(): (VNode | VNode[]) | null { const dir = getElementDir(this.el); const { messages } = this; const tabNavWidth = this.el.offsetWidth; - const tabTitles = Array.from(this.el.querySelectorAll("calcite-tab-title")); - const firstTitle = tabTitles[0].getBoundingClientRect(); const lastTitle = tabTitles[tabTitles.length - 1].getBoundingClientRect(); - let isOverflowingEnd: boolean; - let isOverflowingStart: boolean; - - if (dir !== "rtl") { - isOverflowingEnd = lastTitle.right > tabNavWidth; - isOverflowingStart = firstTitle.left < 0; - } else if (dir === "rtl") { - isOverflowingEnd = lastTitle.right < tabNavWidth; - isOverflowingStart = firstTitle.left < 0; - } + const isOverflowingEnd = + dir !== "rtl" ? lastTitle.right > tabNavWidth : lastTitle.right < tabNavWidth; + const isOverflowingStart = firstTitle.left < 0; const getActionChevronDirection = (overflowDirection: string): VNode => { - const dirActionClass: string = overflowDirection === "end" ? CSS.arrowEnd : CSS.arrowStart; + const isEnd = overflowDirection === "end"; + const dirActionClass: string = isEnd ? CSS.arrowEnd : CSS.arrowStart; + const dirChevronIcon: string = isEnd && dir !== "rtl" ? ICON.chevronRight : ICON.chevronLeft; + const dirText: string = isEnd ? messages.nextTabTitles : messages.previousTabsTitles; - let dirChevronIcon: string; - - if (dir !== "rtl") { - dirChevronIcon = overflowDirection === "end" ? ICON.chevronRight : ICON.chevronLeft; - } else if (dir === "rtl") { - dirChevronIcon = overflowDirection === "end" ? ICON.chevronLeft : ICON.chevronRight; - } - - const dirText: string = - overflowDirection === "end" ? messages.nextTabTitles : messages.previousTabsTitles; - - const dirScroll = (overflowDirection: string) => - overflowDirection === "end" - ? this.scrollToNextTabTitles() - : this.scrollToPreviousTabTitles(); + const dirScroll = () => + isEnd ? this.scrollToNextTabTitles() : this.scrollToPreviousTabTitles(); return ( dirScroll(overflowDirection)} + onClick={() => dirScroll()} scale={this.scale} text={dirText} /> @@ -568,15 +547,10 @@ export class TabNav implements LocalizedComponent, T9nComponent { const showEndAction = getActionChevronDirection("end"); const showStartAction = getActionChevronDirection("start"); - const action = - !isOverflowingEnd && !isOverflowingStart - ? null - : isOverflowingEnd && !isOverflowingStart - ? showEndAction - : !isOverflowingEnd && isOverflowingStart - ? showStartAction - : [showEndAction, showStartAction]; + const action = (isOverflowingEnd ? [showEndAction] : []).concat( + isOverflowingStart ? [showStartAction] : [] + ); - return action; + return action.length > 0 ? action : null; } } From cc4b6df6d431f43e9f89764ef78262e7c3546ff3 Mon Sep 17 00:00:00 2001 From: eliza Date: Sun, 1 Oct 2023 17:20:18 -0700 Subject: [PATCH 019/108] cleanup --- .../src/components/tab-nav/tab-nav.e2e.ts | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index b9ced958605..63f13e7347a 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -289,51 +289,5 @@ describe("calcite-tab-nav", () => { }); } }); - - // overflowScenarios.forEach(async (overflowScenario) => { - // it(`should show action buttons with correct chevrons for overflow to the ${overflowScenario}`, async () => { - // const isOverflowingRight = overflowScenario === "end"; - // const isOverflowingLeft = overflowScenario === "start"; - - // await page.evaluate( - // (isOverflowingRight, isOverflowingLeft) => { - // const tabNav = document.getElementById("testSubjectNav") as HTMLCalciteTabNavElement; - // const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")); - - // tabNav.scrollLeft = isOverflowingRight ? 0 : tabTitles[tabTitles.length - 1].getBoundingClientRect().right; - - // const mobilePageWidth = tabNav.getBoundingClientRect().width; - - // const visibleTabTitles = tabTitles.filter((tabTitle) => { - // const tabTitleRect = tabTitle.getBoundingClientRect(); - // return tabTitleRect.left >= 0 && tabTitleRect.right <= mobilePageWidth; - // }); - - // const firstOverflowItem = isOverflowingRight - // ? tabTitles[visibleTabTitles.length] - // : tabTitles[tabTitles.length - visibleTabTitles.length - 1]; - - // const isOverflowing = isOverflowingRight - // ? firstOverflowItem.getBoundingClientRect().right > mobilePageWidth - // : firstOverflowItem.getBoundingClientRect().right < mobilePageWidth; - - // return isOverflowing; - // }, - // isOverflowingRight, - // isOverflowingLeft - // ); - - // expect(isOverflowingRight).toBe(true); - // expect(isOverflowingLeft).toBe(true); - - // if (isOverflowingRight) { - // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowEnd}`)).not.toBe(null); - // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowStart}`)).toBe(null); - // } else if (isOverflowingLeft) { - // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowEnd}`)).toBe(null); - // expect(await page.find(`#testSubjectNav >>> .${CSS.arrowRight}`)).not.toBe(null); - // } - // }); - // }); }); }); From ab70c423e2ebbe1e88e4d79ac259fadddf813390 Mon Sep 17 00:00:00 2001 From: eliza Date: Sun, 1 Oct 2023 20:59:10 -0700 Subject: [PATCH 020/108] WIP: fix failing tests --- .../tab-nav/assets/tab-nav/t9n/messages.json | 4 ++ .../assets/tab-nav/t9n/messages_en.json | 2 +- .../src/components/tab-nav/tab-nav.e2e.ts | 46 +++++-------------- .../src/components/tab-nav/tab-nav.tsx | 18 ++++---- t9nmanifest.txt | 1 + 5 files changed, 26 insertions(+), 45 deletions(-) create mode 100644 packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages.json diff --git a/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages.json b/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages.json new file mode 100644 index 00000000000..d38eee8a67a --- /dev/null +++ b/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages.json @@ -0,0 +1,4 @@ +{ + "nextTabTitles": "Next Tab Titles", + "previousTabTitles": "Previous Tab Titles" +} diff --git a/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages_en.json b/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages_en.json index fd2979c410b..d38eee8a67a 100644 --- a/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages_en.json +++ b/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages_en.json @@ -1,4 +1,4 @@ { "nextTabTitles": "Next Tab Titles", - "previousTabsTitles": "Previous Tab Titles" + "previousTabTitles": "Previous Tab Titles" } diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index 63f13e7347a..eeb12aacece 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -1,24 +1,12 @@ /* eslint-disable jest/no-conditional-expect */ import { newE2EPage, E2EPage } from "@stencil/core/testing"; -import { accessible, renders, hidden, t9n } from "../../tests/commonTests"; +import { t9n } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; import { CSS } from "./resources"; describe("calcite-tab-nav", () => { const tabNavHtml = ""; - describe("renders", () => { - renders(tabNavHtml, { display: "flex" }); - }); - - describe("honors hidden attribute", () => { - hidden("calcite-tab-nav"); - }); - - describe("accessible: checked", () => { - accessible(tabNavHtml); - }); - describe("translation support", () => { t9n("tab-nav"); }); @@ -85,16 +73,6 @@ describe("calcite-tab-nav", () => { }); describe("scale property", () => { - describe("default", () => { - it("should render without scale", async () => { - const page = await newE2EPage({ - html: `${tabNavHtml}`, - }); - const element = await page.find("calcite-tab-nav"); - expect(element).not.toHaveAttribute("scale"); - }); - }); - describe("when scale is small", () => { it("should render with small scale", async () => { const page = await newE2EPage({ @@ -145,30 +123,28 @@ describe("calcite-tab-nav", () => { describe("when nested within tabs parent", () => { it("should render with default medium scale", async () => { - const page = await newE2EPage({ - html: `${tabNavHtml}`, - }); + const page = await newE2EPage(); + await page.setContent(html`${tabNavHtml}`); + const element = await page.find("calcite-tab-nav"); - expect(element).toEqualAttribute("scale", "m"); + expect(await element.getProperty("scale")).toBe("m"); }); describe("when tabs scale is small", () => { it("should render with small scale", async () => { - const page = await newE2EPage({ - html: `${tabNavHtml}`, - }); + const page = await newE2EPage(); + await page.setContent(html`${tabNavHtml}`); const element = await page.find("calcite-tab-nav"); - expect(element).toEqualAttribute("scale", "s"); + expect(await element.getProperty("scale")).toBe("s"); }); }); describe("when tabs scale is large", () => { it("should render with large scale", async () => { - const page = await newE2EPage({ - html: `${tabNavHtml}`, - }); + const page = await newE2EPage(); + await page.setContent(html`${tabNavHtml}`); const element = await page.find("calcite-tab-nav"); - expect(element).toEqualAttribute("scale", "l"); + expect(await element.getProperty("scale")).toBe("l"); }); }); }); diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index bbe7a5895cd..1467faf5e44 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -95,6 +95,14 @@ export class TabNav implements LocalizedComponent, T9nComponent { */ @Prop({ mutable: true }) indicatorWidth: number; + /** + * Made into a prop for testing purposes only. + * + * @internal + */ + // eslint-disable-next-line @stencil-community/strict-mutable -- updated by t9n module + @Prop({ mutable: true }) messages: TabNavMessages; + /** * Use this property to override individual strings used by the component. */ @@ -106,14 +114,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { /* wired up by t9n util */ } - /** - * Made into a prop for testing purposes only. - * - * @internal - */ - // eslint-disable-next-line @stencil-community/strict-mutable -- updated by t9n module - @Prop({ mutable: true }) messages: TabNavMessages; - @Watch("selectedTabId") async selectedTabIdChanged(): Promise { if ( @@ -528,7 +528,7 @@ export class TabNav implements LocalizedComponent, T9nComponent { const isEnd = overflowDirection === "end"; const dirActionClass: string = isEnd ? CSS.arrowEnd : CSS.arrowStart; const dirChevronIcon: string = isEnd && dir !== "rtl" ? ICON.chevronRight : ICON.chevronLeft; - const dirText: string = isEnd ? messages.nextTabTitles : messages.previousTabsTitles; + const dirText: string = isEnd ? messages.previousTabTitles : messages.nextTabTitles; const dirScroll = () => isEnd ? this.scrollToNextTabTitles() : this.scrollToPreviousTabTitles(); diff --git a/t9nmanifest.txt b/t9nmanifest.txt index c7c12358e45..e5f0b9f0f44 100644 --- a/t9nmanifest.txt +++ b/t9nmanifest.txt @@ -35,6 +35,7 @@ packages\calcite-components\src\components\scrim\assets\scrim\t9n packages\calcite-components\src\components\shell-panel\assets\shell-panel\t9n packages\calcite-components\src\components\stepper\assets\stepper\t9n packages\calcite-components\src\components\stepper-item\assets\stepper-item\t9n +packages\calcite-components\src\components\tab-nav\assets\tab-nav\t9n packages\calcite-components\src\components\tab-title\assets\tab-title\t9n packages\calcite-components\src\components\table\assets\table\t9n packages\calcite-components\src\components\table-cell\assets\table-cell\t9n From e19073a45711cd8011bf626398f921f92a98ca53 Mon Sep 17 00:00:00 2001 From: eliza Date: Mon, 2 Oct 2023 04:03:59 -0700 Subject: [PATCH 021/108] use Element.scrollIntoView for the horizontal scrolling instead of the component handling it --- .../tab-nav/assets/tab-nav/t9n/messages.json | 4 +- .../assets/tab-nav/t9n/messages_en.json | 4 +- .../src/components/tab-nav/tab-nav.tsx | 57 +++++++++++-------- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages.json b/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages.json index d38eee8a67a..329f78c8ace 100644 --- a/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages.json +++ b/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages.json @@ -1,4 +1,4 @@ { - "nextTabTitles": "Next Tab Titles", - "previousTabTitles": "Previous Tab Titles" + "nextTabTitles": "Next tab titles", + "previousTabTitles": "Previous tab titles" } diff --git a/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages_en.json b/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages_en.json index d38eee8a67a..329f78c8ace 100644 --- a/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages_en.json +++ b/packages/calcite-components/src/components/tab-nav/assets/tab-nav/t9n/messages_en.json @@ -1,4 +1,4 @@ { - "nextTabTitles": "Next Tab Titles", - "previousTabTitles": "Previous Tab Titles" + "nextTabTitles": "Next tab titles", + "previousTabTitles": "Previous tab titles" } diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 1467faf5e44..60f1f4a7e1f 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -377,56 +377,67 @@ export class TabNav implements LocalizedComponent, T9nComponent { // //-------------------------------------------------------------------------- - private findVisibleTabTitleIndex = (tabTitles: NodeListOf, isNext: boolean): number => { + private findVisibleTabTitleIndices = ( + tabTitles: NodeListOf, + direction: "forward" | "backward" + ): Map => { const elWidth = this.el.clientWidth; - let visibleTabTitleIndex = -1; + const tabTitlesMap = new Map(); function findIndex() { for (const [index, tabTitle] of Array.from(tabTitles).entries()) { const tabTitleRect = tabTitle.getBoundingClientRect(); - if ((isNext && tabTitleRect.right <= elWidth) || (!isNext && tabTitleRect.left >= 0)) { - visibleTabTitleIndex = index; - if (!isNext) { - return true; - } + if ( + (direction === "forward" && tabTitleRect.right <= elWidth) || + (direction === "backward" && tabTitleRect.left >= 0) + ) { + tabTitlesMap.set(tabTitle, index); } } } findIndex(); - return visibleTabTitleIndex; + return tabTitlesMap; }; - private scrollToTabTitles = (isNext: boolean): void => { + private scrollToTabTitles = (direction: "forward" | "backward"): void => { const tabTitles = this.el.querySelectorAll("calcite-tab-title"); - const visibleTabTitleIndex = this.findVisibleTabTitleIndex(tabTitles, isNext); + const visibleTabTitleIndices = this.findVisibleTabTitleIndices(tabTitles, direction); - if (visibleTabTitleIndex !== -1) { - const targetTabTitleIndex = isNext ? visibleTabTitleIndex + 1 : visibleTabTitleIndex - 1; - const targetTabTitle = tabTitles[targetTabTitleIndex]; + let lastValue: number; + const tabTitlesArray = Array.from(tabTitles); - if (targetTabTitle) { - const targetTabTitleRect = targetTabTitle.getBoundingClientRect(); + for (const value of visibleTabTitleIndices.values()) { + lastValue = value; + } - const scrollAmount = isNext - ? targetTabTitleRect.left - this.el.getBoundingClientRect().left - : targetTabTitleRect.right - this.el.clientWidth; + const valuesIterator = visibleTabTitleIndices.values(); + const firstValue: number = valuesIterator.next().value; - requestAnimationFrame(() => { - this.tabNavEl.scrollLeft += scrollAmount; + requestAnimationFrame(() => { + if (direction === "forward") { + tabTitlesArray[lastValue + 1].scrollIntoView({ + behavior: "smooth", + inline: "start", }); } - } + if (direction === "backward") { + tabTitlesArray[firstValue - 1].scrollIntoView({ + behavior: "smooth", + inline: "end", + }); + } + }); }; private scrollToNextTabTitles = (): void => { - this.scrollToTabTitles(true); + this.scrollToTabTitles("forward"); }; private scrollToPreviousTabTitles = (): void => { - this.scrollToTabTitles(false); + this.scrollToTabTitles("backward"); }; handleTabFocus = ( From 05ad7099e55280bc901d1ef09364ef2ecd51e13b Mon Sep 17 00:00:00 2001 From: eliza Date: Mon, 2 Oct 2023 10:15:18 -0700 Subject: [PATCH 022/108] cleanup --- .../src/components/tab-nav/tab-nav.e2e.ts | 10 +++++----- .../src/components/tab-nav/tab-nav.tsx | 20 +++++++------------ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index eeb12aacece..37b1ad249e1 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -195,7 +195,7 @@ describe("calcite-tab-nav", () => { `; describe("responsive tabs for inline layout", () => { - const overflowScenarios = ["end", "start", "both"]; + const overflowScenarios: string[] = ["end", "start", "both"]; let page: E2EPage; beforeEach(async () => { @@ -227,14 +227,14 @@ describe("calcite-tab-nav", () => { const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")); tabNav.scrollLeft = 0; - const mobilePageWidth = tabNav.getBoundingClientRect().width; + const tabNavWidth = tabNav.getBoundingClientRect().width; const visibleTabTitles = tabTitles.filter((tabTitle) => { const tabTitleRect = tabTitle.getBoundingClientRect(); - return tabTitleRect.left >= 0 && tabTitleRect.right <= mobilePageWidth; + return tabTitleRect.left >= 0 && tabTitleRect.right <= tabNavWidth; }); const firstRightOverflowItem = tabTitles[visibleTabTitles.length]; - const isOverflowingRight = firstRightOverflowItem.getBoundingClientRect().right > mobilePageWidth; + const isOverflowingRight = firstRightOverflowItem.getBoundingClientRect().right > tabNavWidth; return isOverflowingRight; }); @@ -243,7 +243,7 @@ describe("calcite-tab-nav", () => { expect(await page.find(`#testSubjectNav >>> .${CSS.arrowEnd}`)).not.toBe(null); expect(await page.find(`#testSubjectNav >>> .${CSS.arrowStart}`)).toBe(null); }); - } else if (overflowScenario === "left") { + } else if (overflowScenario === "start") { it("should show action buttons with correct chevrons for overflow to the left", async () => { const isOverflowingLeft = await page.evaluate(async () => { const tabNav = document.getElementById("testSubjectNav") as HTMLCalciteTabNavElement; diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 60f1f4a7e1f..304534dd668 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -417,16 +417,13 @@ export class TabNav implements LocalizedComponent, T9nComponent { const firstValue: number = valuesIterator.next().value; requestAnimationFrame(() => { - if (direction === "forward") { - tabTitlesArray[lastValue + 1].scrollIntoView({ - behavior: "smooth", - inline: "start", - }); - } - if (direction === "backward") { - tabTitlesArray[firstValue - 1].scrollIntoView({ + const targetIndex = direction === "forward" ? lastValue + 1 : firstValue - 1; + const scrollInline = direction === "forward" ? "start" : "end"; + + if (tabTitlesArray[targetIndex]) { + tabTitlesArray[targetIndex].scrollIntoView({ behavior: "smooth", - inline: "end", + inline: scrollInline, }); } }); @@ -541,14 +538,11 @@ export class TabNav implements LocalizedComponent, T9nComponent { const dirChevronIcon: string = isEnd && dir !== "rtl" ? ICON.chevronRight : ICON.chevronLeft; const dirText: string = isEnd ? messages.previousTabTitles : messages.nextTabTitles; - const dirScroll = () => - isEnd ? this.scrollToNextTabTitles() : this.scrollToPreviousTabTitles(); - return ( dirScroll()} + onClick={dirText ? this.scrollToNextTabTitles : this.scrollToPreviousTabTitles} scale={this.scale} text={dirText} /> From 0d5824c71217c1099d3e8687d2f1a4fc6d5a9604 Mon Sep 17 00:00:00 2001 From: eliza Date: Mon, 2 Oct 2023 13:53:15 -0700 Subject: [PATCH 023/108] WIP: fix scroll calculation to apply correctly on RTL --- .../src/components/tab-nav/tab-nav.tsx | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 304534dd668..b92daefd438 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -402,9 +402,19 @@ export class TabNav implements LocalizedComponent, T9nComponent { return tabTitlesMap; }; + private flipScrollDirection(direction: "forward" | "backward"): "forward" | "backward" { + const dir = getElementDir(this.el); + const isRtl = dir === "rtl"; + const updatedDirection = isRtl ? (direction === "forward" ? "backward" : "forward") : direction; + return updatedDirection; + } + private scrollToTabTitles = (direction: "forward" | "backward"): void => { const tabTitles = this.el.querySelectorAll("calcite-tab-title"); - const visibleTabTitleIndices = this.findVisibleTabTitleIndices(tabTitles, direction); + const updatedDirection = this.flipScrollDirection(direction); + + const visibleTabTitleIndices = this.findVisibleTabTitleIndices(tabTitles, updatedDirection); + console.log("visibleTabTitleIndices", visibleTabTitleIndices); let lastValue: number; const tabTitlesArray = Array.from(tabTitles); @@ -416,6 +426,8 @@ export class TabNav implements LocalizedComponent, T9nComponent { const valuesIterator = visibleTabTitleIndices.values(); const firstValue: number = valuesIterator.next().value; + console.log("last value", lastValue); + requestAnimationFrame(() => { const targetIndex = direction === "forward" ? lastValue + 1 : firstValue - 1; const scrollInline = direction === "forward" ? "start" : "end"; @@ -525,12 +537,14 @@ export class TabNav implements LocalizedComponent, T9nComponent { const { messages } = this; const tabNavWidth = this.el.offsetWidth; const tabTitles = Array.from(this.el.querySelectorAll("calcite-tab-title")); + console.log("tabTitles", tabTitles); const firstTitle = tabTitles[0].getBoundingClientRect(); const lastTitle = tabTitles[tabTitles.length - 1].getBoundingClientRect(); - const isOverflowingEnd = - dir !== "rtl" ? lastTitle.right > tabNavWidth : lastTitle.right < tabNavWidth; - const isOverflowingStart = firstTitle.left < 0; + // const visibleTabTitleIndices = this.findVisibleTabTitleIndices(tabTitles, updatedDirection); + + const isOverflowingEnd = dir === "ltr" ? lastTitle.right > tabNavWidth : lastTitle.left < 0; + const isOverflowingStart = dir === "ltr" ? firstTitle.left < 0 : lastTitle.right > tabNavWidth; const getActionChevronDirection = (overflowDirection: string): VNode => { const isEnd = overflowDirection === "end"; @@ -538,11 +552,14 @@ export class TabNav implements LocalizedComponent, T9nComponent { const dirChevronIcon: string = isEnd && dir !== "rtl" ? ICON.chevronRight : ICON.chevronLeft; const dirText: string = isEnd ? messages.previousTabTitles : messages.nextTabTitles; + const dirScroll = () => + isEnd ? this.scrollToNextTabTitles() : this.scrollToPreviousTabTitles(); + return ( dirScroll()} scale={this.scale} text={dirText} /> @@ -552,10 +569,15 @@ export class TabNav implements LocalizedComponent, T9nComponent { const showEndAction = getActionChevronDirection("end"); const showStartAction = getActionChevronDirection("start"); - const action = (isOverflowingEnd ? [showEndAction] : []).concat( - isOverflowingStart ? [showStartAction] : [] - ); + const action = + isOverflowingEnd && isOverflowingStart + ? [showEndAction, showStartAction] + : isOverflowingEnd + ? showEndAction + : showStartAction; + + console.log("action", action); - return action.length > 0 ? action : null; + return (action as VNode) !== null || (action as VNode[]).length > 0 ? action : null; } } From 7497b822761849957daf361824824c1538157ecf Mon Sep 17 00:00:00 2001 From: eliza Date: Mon, 2 Oct 2023 16:08:12 -0700 Subject: [PATCH 024/108] revert to ltr only option --- .../src/components/tab-nav/tab-nav.tsx | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index b92daefd438..17c0901ef91 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -402,19 +402,10 @@ export class TabNav implements LocalizedComponent, T9nComponent { return tabTitlesMap; }; - private flipScrollDirection(direction: "forward" | "backward"): "forward" | "backward" { - const dir = getElementDir(this.el); - const isRtl = dir === "rtl"; - const updatedDirection = isRtl ? (direction === "forward" ? "backward" : "forward") : direction; - return updatedDirection; - } - private scrollToTabTitles = (direction: "forward" | "backward"): void => { const tabTitles = this.el.querySelectorAll("calcite-tab-title"); - const updatedDirection = this.flipScrollDirection(direction); - const visibleTabTitleIndices = this.findVisibleTabTitleIndices(tabTitles, updatedDirection); - console.log("visibleTabTitleIndices", visibleTabTitleIndices); + const visibleTabTitleIndices = this.findVisibleTabTitleIndices(tabTitles, direction); let lastValue: number; const tabTitlesArray = Array.from(tabTitles); @@ -426,8 +417,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { const valuesIterator = visibleTabTitleIndices.values(); const firstValue: number = valuesIterator.next().value; - console.log("last value", lastValue); - requestAnimationFrame(() => { const targetIndex = direction === "forward" ? lastValue + 1 : firstValue - 1; const scrollInline = direction === "forward" ? "start" : "end"; @@ -537,19 +526,17 @@ export class TabNav implements LocalizedComponent, T9nComponent { const { messages } = this; const tabNavWidth = this.el.offsetWidth; const tabTitles = Array.from(this.el.querySelectorAll("calcite-tab-title")); - console.log("tabTitles", tabTitles); + const firstTitle = tabTitles[0].getBoundingClientRect(); const lastTitle = tabTitles[tabTitles.length - 1].getBoundingClientRect(); - // const visibleTabTitleIndices = this.findVisibleTabTitleIndices(tabTitles, updatedDirection); - - const isOverflowingEnd = dir === "ltr" ? lastTitle.right > tabNavWidth : lastTitle.left < 0; - const isOverflowingStart = dir === "ltr" ? firstTitle.left < 0 : lastTitle.right > tabNavWidth; + const isOverflowingEnd = dir === "ltr" ? lastTitle.right > tabNavWidth : null; + const isOverflowingStart = dir === "ltr" ? firstTitle.left < 0 : null; const getActionChevronDirection = (overflowDirection: string): VNode => { const isEnd = overflowDirection === "end"; const dirActionClass: string = isEnd ? CSS.arrowEnd : CSS.arrowStart; - const dirChevronIcon: string = isEnd && dir !== "rtl" ? ICON.chevronRight : ICON.chevronLeft; + const dirChevronIcon: string = isEnd ? ICON.chevronRight : ICON.chevronLeft; const dirText: string = isEnd ? messages.previousTabTitles : messages.nextTabTitles; const dirScroll = () => From 817d643acfc9d9d86142b2534c4f2736fb8655a9 Mon Sep 17 00:00:00 2001 From: eliza Date: Mon, 2 Oct 2023 16:31:16 -0700 Subject: [PATCH 025/108] test cleanup --- .../src/components/tab-nav/tab-nav.e2e.ts | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index 37b1ad249e1..d294c02fa3d 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -1,12 +1,24 @@ /* eslint-disable jest/no-conditional-expect */ import { newE2EPage, E2EPage } from "@stencil/core/testing"; -import { t9n } from "../../tests/commonTests"; +import { accessible, hidden, renders, t9n } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; import { CSS } from "./resources"; describe("calcite-tab-nav", () => { const tabNavHtml = ""; + describe("accessible: checked", () => { + accessible(tabNavHtml); + }); + + describe("renders", () => { + renders(tabNavHtml, { display: "flex" }); + }); + + describe("honors hidden attribute", () => { + hidden("calcite-tab-nav"); + }); + describe("translation support", () => { t9n("tab-nav"); }); @@ -222,7 +234,7 @@ describe("calcite-tab-nav", () => { overflowScenarios.forEach(async (overflowScenario) => { if (overflowScenario === "end") { it("should show action buttons with correct chevrons for overflow to the end", async () => { - const isOverflowingRight = await page.evaluate(() => { + const isOverflowingEnd = await page.evaluate(() => { const tabNav = document.getElementById("testSubjectNav") as HTMLCalciteTabNavElement; const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")); @@ -234,34 +246,28 @@ describe("calcite-tab-nav", () => { return tabTitleRect.left >= 0 && tabTitleRect.right <= tabNavWidth; }); const firstRightOverflowItem = tabTitles[visibleTabTitles.length]; - const isOverflowingRight = firstRightOverflowItem.getBoundingClientRect().right > tabNavWidth; + const isOverflowingEnd = firstRightOverflowItem.getBoundingClientRect().right > tabNavWidth; - return isOverflowingRight; + return isOverflowingEnd; }); - expect(isOverflowingRight).toBe(true); + expect(isOverflowingEnd).toBe(true); expect(await page.find(`#testSubjectNav >>> .${CSS.arrowEnd}`)).not.toBe(null); expect(await page.find(`#testSubjectNav >>> .${CSS.arrowStart}`)).toBe(null); }); } else if (overflowScenario === "start") { - it("should show action buttons with correct chevrons for overflow to the left", async () => { - const isOverflowingLeft = await page.evaluate(async () => { - const tabNav = document.getElementById("testSubjectNav") as HTMLCalciteTabNavElement; + it("should show action buttons with correct chevrons for overflow to the start", async () => { + const isOverflowingStart = await page.evaluate(async () => { const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")); - const mobilePageWidth = tabNav.getBoundingClientRect().width; - - tabNav.scrollLeft += tabTitles[tabTitles.length - 1].getBoundingClientRect().right; + tabTitles[5].scrollIntoView(); + const isOverflowingStart = tabTitles[0].getBoundingClientRect().right <= 0; - const isOverflowingLeft = tabTitles[tabTitles.length - 1].getBoundingClientRect().right < mobilePageWidth; - - return isOverflowingLeft; + return isOverflowingStart; }); - expect(isOverflowingLeft).toBe(true); - - expect(await page.find(`#testSubjectNav >>> .${CSS.arrowEnd}`)).toBe(null); - expect(await page.find(`#testSubjectNav >>> .${CSS.arrowStart}`)).not.toBe(null); + expect(isOverflowingStart).toBe(true); + expect(await page.find(`#testSubjectNav >>> .${CSS.arrowStart}`)).toBeDefined(); }); } }); From 07b8f6973af53823d366780177a55666415e7217 Mon Sep 17 00:00:00 2001 From: eliza Date: Tue, 3 Oct 2023 16:44:16 -0700 Subject: [PATCH 026/108] feat(tab-nav): add support for built-in translations --- .../src/components/tab-nav/tab-nav.e2e.ts | 6 ++- .../src/components/tab-nav/tab-nav.tsx | 47 ++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index 72c8a967f62..c3f6827c25f 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -1,5 +1,5 @@ import { newE2EPage } from "@stencil/core/testing"; -import { accessible, renders, hidden } from "../../tests/commonTests"; +import { accessible, hidden, renders, t9n } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; describe("calcite-tab-nav", () => { @@ -17,6 +17,10 @@ describe("calcite-tab-nav", () => { accessible(tabNavHtml); }); + describe("translation support", () => { + t9n("tab-nav"); + }); + it("emits on user interaction", async () => { const page = await newE2EPage(); await page.setContent(html` diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 9f2199d5dd1..e2902a91407 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -21,6 +21,15 @@ import { createObserver } from "../../utils/observers"; import { Scale } from "../interfaces"; import { TabChangeEventDetail, TabCloseEventDetail } from "../tab/interfaces"; import { TabID, TabLayout, TabPosition } from "../tabs/interfaces"; +import { connectLocalized, disconnectLocalized, LocalizedComponent } from "../../utils/locale"; +import { + connectMessages, + disconnectMessages, + setUpMessages, + T9nComponent, + updateMessages, +} from "../../utils/t9n"; +import { TabNavMessages } from "./assets/tab-nav/t9n"; /** * @slot - A slot for adding `calcite-tab-title`s. @@ -29,8 +38,9 @@ import { TabID, TabLayout, TabPosition } from "../tabs/interfaces"; tag: "calcite-tab-nav", styleUrl: "tab-nav.scss", shadow: true, + assetsDirs: ["assets"], }) -export class TabNav { +export class TabNav implements LocalizedComponent, T9nComponent { //-------------------------------------------------------------------------- // // Properties @@ -84,6 +94,25 @@ export class TabNav { */ @Prop({ mutable: true }) indicatorWidth: number; + /** + * Made into a prop for testing purposes only. + * + * @internal + */ + // eslint-disable-next-line @stencil-community/strict-mutable -- updated by t9n module + @Prop({ mutable: true }) messages: TabNavMessages; + + /** + * Use this property to override individual strings used by the component. + */ + // eslint-disable-next-line @stencil-community/strict-mutable -- updated by t9n module + @Prop({ mutable: true }) messageOverrides: Partial; + + @Watch("messageOverrides") + onMessagesChange(): void { + /* wired up by t9n util */ + } + @Watch("selectedTabId") async selectedTabIdChanged(): Promise { if ( @@ -119,18 +148,23 @@ export class TabNav { connectedCallback(): void { this.parentTabsEl = this.el.closest("calcite-tabs"); this.resizeObserver?.observe(this.el); + connectLocalized(this); + connectMessages(this); } disconnectedCallback(): void { this.resizeObserver?.disconnect(); + disconnectLocalized(this); + disconnectMessages(this); } - componentWillLoad(): void { + async componentWillLoad(): Promise { const storageKey = `calcite-tab-nav-${this.storageId}`; if (localStorage && this.storageId && localStorage.getItem(storageKey)) { const storedTab = JSON.parse(localStorage.getItem(storageKey)); this.selectedTabId = storedTab; } + await setUpMessages(this); } componentWillRender(): void { @@ -306,6 +340,15 @@ export class TabNav { animationActiveDuration = 0.3; + @State() defaultMessages: TabNavMessages; + + @State() effectiveLocale = ""; + + @Watch("effectiveLocale") + effectiveLocaleChange(): void { + updateMessages(this, this.effectiveLocale); + } + resizeObserver = createObserver("resize", () => { if (!this.activeIndicatorEl) { return; From 555f3dd1d4931695bf75a56abfc54f1faacf5e17 Mon Sep 17 00:00:00 2001 From: eliza Date: Tue, 3 Oct 2023 16:58:38 -0700 Subject: [PATCH 027/108] remove localization and translations and create a seperate PR --- .../src/components/tab-nav/tab-nav.e2e.ts | 6 +-- .../src/components/tab-nav/tab-nav.tsx | 49 ++----------------- 2 files changed, 4 insertions(+), 51 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index d294c02fa3d..0e3880b47d1 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -1,6 +1,6 @@ /* eslint-disable jest/no-conditional-expect */ import { newE2EPage, E2EPage } from "@stencil/core/testing"; -import { accessible, hidden, renders, t9n } from "../../tests/commonTests"; +import { accessible, hidden, renders } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; import { CSS } from "./resources"; @@ -19,10 +19,6 @@ describe("calcite-tab-nav", () => { hidden("calcite-tab-nav"); }); - describe("translation support", () => { - t9n("tab-nav"); - }); - it("emits on user interaction", async () => { const page = await newE2EPage(); await page.setContent(html` diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 17c0901ef91..ccf7886fb0d 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -11,13 +11,7 @@ import { VNode, Watch, } from "@stencil/core"; -import { - connectMessages, - disconnectMessages, - setUpMessages, - T9nComponent, - updateMessages, -} from "../../utils/t9n"; + import { filterDirectChildren, focusElementInGroup, @@ -28,8 +22,6 @@ import { createObserver } from "../../utils/observers"; import { Scale } from "../interfaces"; import { TabChangeEventDetail, TabCloseEventDetail } from "../tab/interfaces"; import { TabID, TabLayout, TabPosition } from "../tabs/interfaces"; -import { LocalizedComponent, connectLocalized, disconnectLocalized } from "../../utils/locale"; -import { TabNavMessages } from "./assets/tab-nav/t9n"; import { ICON, CSS } from "./resources"; /** @@ -41,7 +33,7 @@ import { ICON, CSS } from "./resources"; shadow: true, assetsDirs: ["assets"], }) -export class TabNav implements LocalizedComponent, T9nComponent { +export class TabNav { //-------------------------------------------------------------------------- // // Properties @@ -95,25 +87,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { */ @Prop({ mutable: true }) indicatorWidth: number; - /** - * Made into a prop for testing purposes only. - * - * @internal - */ - // eslint-disable-next-line @stencil-community/strict-mutable -- updated by t9n module - @Prop({ mutable: true }) messages: TabNavMessages; - - /** - * Use this property to override individual strings used by the component. - */ - // eslint-disable-next-line @stencil-community/strict-mutable -- updated by t9n module - @Prop({ mutable: true }) messageOverrides: Partial; - - @Watch("messageOverrides") - onMessagesChange(): void { - /* wired up by t9n util */ - } - @Watch("selectedTabId") async selectedTabIdChanged(): Promise { if ( @@ -149,8 +122,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { connectedCallback(): void { this.parentTabsEl = this.el.closest("calcite-tabs"); this.resizeObserver?.observe(this.el); - connectLocalized(this); - connectMessages(this); } async componentWillLoad(): Promise { @@ -159,7 +130,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { const storedTab = JSON.parse(localStorage.getItem(storageKey)); this.selectedTabId = storedTab; } - await setUpMessages(this); } componentWillRender(): void { @@ -192,8 +162,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { disconnectedCallback(): void { this.resizeObserver?.disconnect(); - disconnectLocalized(this); - disconnectMessages(this); } //-------------------------------------------------------------------------- @@ -349,15 +317,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { animationActiveDuration = 0.3; - @State() defaultMessages: TabNavMessages; - - @State() effectiveLocale = ""; - - @Watch("effectiveLocale") - effectiveLocaleChange(): void { - updateMessages(this, this.effectiveLocale); - } - resizeObserver = createObserver("resize", () => { if (!this.activeIndicatorEl) { return; @@ -523,7 +482,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { private getOverflowIcons(): (VNode | VNode[]) | null { const dir = getElementDir(this.el); - const { messages } = this; const tabNavWidth = this.el.offsetWidth; const tabTitles = Array.from(this.el.querySelectorAll("calcite-tab-title")); @@ -537,7 +495,6 @@ export class TabNav implements LocalizedComponent, T9nComponent { const isEnd = overflowDirection === "end"; const dirActionClass: string = isEnd ? CSS.arrowEnd : CSS.arrowStart; const dirChevronIcon: string = isEnd ? ICON.chevronRight : ICON.chevronLeft; - const dirText: string = isEnd ? messages.previousTabTitles : messages.nextTabTitles; const dirScroll = () => isEnd ? this.scrollToNextTabTitles() : this.scrollToPreviousTabTitles(); @@ -548,7 +505,7 @@ export class TabNav implements LocalizedComponent, T9nComponent { icon={dirChevronIcon} onClick={() => dirScroll()} scale={this.scale} - text={dirText} + text="" // add this line to fix the error /> ); }; From 128f82bd9e7f98654e4c88a4910403e8fd1d6fe1 Mon Sep 17 00:00:00 2001 From: eliza Date: Tue, 3 Oct 2023 22:49:00 -0700 Subject: [PATCH 028/108] cleanup --- .../src/components/tab-nav/tab-nav.e2e.ts | 2 +- .../src/components/tab-nav/tab-nav.tsx | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index 0e3880b47d1..4af4c3dcd7b 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -150,7 +150,7 @@ describe("calcite-tab-nav", () => { describe("when tabs scale is large", () => { it("should render with large scale", async () => { const page = await newE2EPage(); - await page.setContent(html`${tabNavHtml}`); + await page.setContent(html`${tabNavHtml}`); const element = await page.find("calcite-tab-nav"); expect(await element.getProperty("scale")).toBe("l"); }); diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index ccf7886fb0d..a34bec18b28 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -512,15 +512,16 @@ export class TabNav { const showEndAction = getActionChevronDirection("end"); const showStartAction = getActionChevronDirection("start"); - - const action = - isOverflowingEnd && isOverflowingStart - ? [showEndAction, showStartAction] - : isOverflowingEnd - ? showEndAction - : showStartAction; - - console.log("action", action); + let action: VNode | VNode[]; + + if (dir === "ltr") { + action = + isOverflowingEnd && isOverflowingStart + ? [showEndAction, showStartAction] + : isOverflowingEnd + ? showEndAction + : showStartAction; + } return (action as VNode) !== null || (action as VNode[]).length > 0 ? action : null; } From b2ffd3d428f01a6f67ea8824ef38abad37e1bc09 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 4 Oct 2023 10:42:17 -0700 Subject: [PATCH 029/108] fix failing commonTests --- .../calcite-components/src/components/tab-nav/tab-nav.tsx | 2 +- packages/calcite-components/src/components/tabs/tabs.e2e.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index a34bec18b28..aff8b0978ec 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -505,7 +505,7 @@ export class TabNav { icon={dirChevronIcon} onClick={() => dirScroll()} scale={this.scale} - text="" // add this line to fix the error + text="Placeholder" /> ); }; diff --git a/packages/calcite-components/src/components/tabs/tabs.e2e.ts b/packages/calcite-components/src/components/tabs/tabs.e2e.ts index 8dbe5ff3f59..77fcf2efa8c 100644 --- a/packages/calcite-components/src/components/tabs/tabs.e2e.ts +++ b/packages/calcite-components/src/components/tabs/tabs.e2e.ts @@ -259,6 +259,9 @@ describe("calcite-tabs", () => { const wrapper = document.querySelector(wrapperName); wrapper.shadowRoot.querySelector("#title-2").click(); + + await new Promise((resolve) => requestAnimationFrame(() => resolve())); + await new Promise((resolve) => requestAnimationFrame(() => resolve())); await new Promise((resolve) => requestAnimationFrame(() => resolve())); await new Promise((resolve) => requestAnimationFrame(() => resolve())); @@ -268,6 +271,7 @@ describe("calcite-tabs", () => { }, [wrappedTabTemplateHTML] ); + expect(finalSelectedItem.tabTitle).toBe("title-2"); expect(finalSelectedItem.tab).toBe("tab-2"); }); From 7d5d87eaee7594b6b0a6fc98d41e038582dc1c87 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 4 Oct 2023 13:22:48 -0700 Subject: [PATCH 030/108] cleanups to fix some failing tests --- .../src/components/tab-nav/tab-nav.e2e.ts | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index 4af4c3dcd7b..803780e41d5 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -5,7 +5,14 @@ import { html } from "../../../support/formatting"; import { CSS } from "./resources"; describe("calcite-tab-nav", () => { - const tabNavHtml = ""; + const tabNavHtml = html``; + const tabTitles = html` + Tab 1 Title + Tab 2 Title + Tab 3 Title + Tab 4 Title + `; + const nestedTabTitles = html`${tabTitles}`; describe("accessible: checked", () => { accessible(tabNavHtml); @@ -39,13 +46,6 @@ describe("calcite-tab-nav", () => { }); describe("selected indicator", () => { - const tabTitles = html` - Tab 1 Title - Tab 2 Title - Tab 3 Title - Tab 4 Title - `; - it("has its active indicator positioned from left if LTR", async () => { const page = await newE2EPage(); await page.setContent(`${tabTitles}`); @@ -132,8 +132,7 @@ describe("calcite-tab-nav", () => { describe("when nested within tabs parent", () => { it("should render with default medium scale", async () => { const page = await newE2EPage(); - await page.setContent(html`${tabNavHtml}`); - + await page.setContent(html`${nestedTabTitles}`); const element = await page.find("calcite-tab-nav"); expect(await element.getProperty("scale")).toBe("m"); }); @@ -141,7 +140,7 @@ describe("calcite-tab-nav", () => { describe("when tabs scale is small", () => { it("should render with small scale", async () => { const page = await newE2EPage(); - await page.setContent(html`${tabNavHtml}`); + await page.setContent(html`${nestedTabTitles}`); const element = await page.find("calcite-tab-nav"); expect(await element.getProperty("scale")).toBe("s"); }); @@ -150,7 +149,7 @@ describe("calcite-tab-nav", () => { describe("when tabs scale is large", () => { it("should render with large scale", async () => { const page = await newE2EPage(); - await page.setContent(html`${tabNavHtml}`); + await page.setContent(html`${nestedTabTitles}`); const element = await page.find("calcite-tab-nav"); expect(await element.getProperty("scale")).toBe("l"); }); @@ -234,8 +233,11 @@ describe("calcite-tab-nav", () => { const tabNav = document.getElementById("testSubjectNav") as HTMLCalciteTabNavElement; const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")); - tabNav.scrollLeft = 0; - const tabNavWidth = tabNav.getBoundingClientRect().width; + let tabNavWidth: number; + if (tabNav) { + tabNav.scrollLeft = 0; + tabNavWidth = tabNav.getBoundingClientRect().width; + } const visibleTabTitles = tabTitles.filter((tabTitle) => { const tabTitleRect = tabTitle.getBoundingClientRect(); @@ -255,8 +257,9 @@ describe("calcite-tab-nav", () => { it("should show action buttons with correct chevrons for overflow to the start", async () => { const isOverflowingStart = await page.evaluate(async () => { const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")); - - tabTitles[5].scrollIntoView(); + if (tabTitles && tabTitles.length > 5) { + tabTitles[5].scrollIntoView(); + } const isOverflowingStart = tabTitles[0].getBoundingClientRect().right <= 0; return isOverflowingStart; From 3bfc5a0f80c5387afab7b701aa7957d8239e1604 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 4 Oct 2023 13:52:35 -0700 Subject: [PATCH 031/108] add checks to avoid uncaught errors --- .../src/components/tab-nav/tab-nav.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index aff8b0978ec..67ae39281dd 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -483,13 +483,15 @@ export class TabNav { private getOverflowIcons(): (VNode | VNode[]) | null { const dir = getElementDir(this.el); const tabNavWidth = this.el.offsetWidth; - const tabTitles = Array.from(this.el.querySelectorAll("calcite-tab-title")); + const tabTitles = Array.from( + this.el.querySelectorAll("calcite-tab-title") + ); - const firstTitle = tabTitles[0].getBoundingClientRect(); - const lastTitle = tabTitles[tabTitles.length - 1].getBoundingClientRect(); + const firstTitleRect = tabTitles?.[0]?.getBoundingClientRect(); + const lastTitleRect = tabTitles?.[tabTitles.length - 1]?.getBoundingClientRect(); - const isOverflowingEnd = dir === "ltr" ? lastTitle.right > tabNavWidth : null; - const isOverflowingStart = dir === "ltr" ? firstTitle.left < 0 : null; + const isOverflowingEnd = lastTitleRect?.right ?? 0 > tabNavWidth; + const isOverflowingStart = firstTitleRect?.left ?? 0 < 0; const getActionChevronDirection = (overflowDirection: string): VNode => { const isEnd = overflowDirection === "end"; From b255279f2b34b323dd46a7ca16f846dcabcbe36e Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 4 Oct 2023 15:25:59 -0700 Subject: [PATCH 032/108] cleanup --- .../src/components/tab-nav/tab-nav.e2e.ts | 5 ++-- .../src/components/tab-nav/tab-nav.tsx | 6 ++--- .../src/components/tabs/tabs.e2e.ts | 26 +++++++++---------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index 803780e41d5..688c440f5a9 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -5,7 +5,6 @@ import { html } from "../../../support/formatting"; import { CSS } from "./resources"; describe("calcite-tab-nav", () => { - const tabNavHtml = html``; const tabTitles = html` Tab 1 Title Tab 2 Title @@ -15,11 +14,11 @@ describe("calcite-tab-nav", () => { const nestedTabTitles = html`${tabTitles}`; describe("accessible: checked", () => { - accessible(tabNavHtml); + accessible(nestedTabTitles); }); describe("renders", () => { - renders(tabNavHtml, { display: "flex" }); + renders(nestedTabTitles, { display: "flex" }); }); describe("honors hidden attribute", () => { diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 67ae39281dd..eb8c716f8d6 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -495,16 +495,14 @@ export class TabNav { const getActionChevronDirection = (overflowDirection: string): VNode => { const isEnd = overflowDirection === "end"; - const dirActionClass: string = isEnd ? CSS.arrowEnd : CSS.arrowStart; - const dirChevronIcon: string = isEnd ? ICON.chevronRight : ICON.chevronLeft; const dirScroll = () => isEnd ? this.scrollToNextTabTitles() : this.scrollToPreviousTabTitles(); return ( dirScroll()} scale={this.scale} text="Placeholder" diff --git a/packages/calcite-components/src/components/tabs/tabs.e2e.ts b/packages/calcite-components/src/components/tabs/tabs.e2e.ts index 77fcf2efa8c..a37fbaf748a 100644 --- a/packages/calcite-components/src/components/tabs/tabs.e2e.ts +++ b/packages/calcite-components/src/components/tabs/tabs.e2e.ts @@ -4,7 +4,7 @@ import { accessible, defaults, hidden, renders } from "../../tests/commonTests"; import { GlobalTestProps } from "../../tests/utils"; describe("calcite-tabs", () => { - const tabsContent = ` + const tabsContent = html` Tab 1 Title Tab 2 Title @@ -16,14 +16,14 @@ describe("calcite-tabs", () => { Tab 3 Content Tab 4 Content `; - const tabsSnippet = `${tabsContent}`; + const tabsSnippet = html`${tabsContent}`; describe("renders", () => { renders(tabsSnippet, { display: "flex" }); }); describe("honors hidden attribute", () => { - hidden("calcite-tabs"); + hidden(tabsSnippet); }); describe("defaults", () => { @@ -35,7 +35,7 @@ describe("calcite-tabs", () => { }); describe("accessible: checked", () => { - accessible(`${tabsContent}`); + accessible(tabsSnippet); }); it("sets up basic aria attributes", async () => { @@ -135,9 +135,9 @@ describe("calcite-tabs", () => { html: `${tabsContent}`, }); expect(await page.find("calcite-tabs")).toEqualAttribute("scale", "s"); - expect(await page.find("calcite-tab-nav")).toEqualAttribute("scale", "s"); - expect(await page.find("calcite-tab-title")).toEqualAttribute("scale", "s"); - expect(await page.find("calcite-tab")).toEqualAttribute("scale", "s"); + expect(await (await page.find("calcite-tab-nav")).getProperty("scale")).toBe("s"); + expect(await (await page.find("calcite-tab")).getProperty("scale")).toBe("s"); + expect(await (await page.find("calcite-tab-title")).getProperty("scale")).toBe("s"); }); it("should render itself and child tab elements with corresponding scale (medium)", async () => { @@ -145,9 +145,9 @@ describe("calcite-tabs", () => { html: `${tabsContent}`, }); expect(await page.find("calcite-tabs")).toEqualAttribute("scale", "m"); - expect(await page.find("calcite-tab-nav")).toEqualAttribute("scale", "m"); - expect(await page.find("calcite-tab-title")).toEqualAttribute("scale", "m"); - expect(await page.find("calcite-tab")).toEqualAttribute("scale", "m"); + expect(await (await page.find("calcite-tab-nav")).getProperty("scale")).toBe("m"); + expect(await (await page.find("calcite-tab")).getProperty("scale")).toBe("m"); + expect(await (await page.find("calcite-tab-title")).getProperty("scale")).toBe("m"); }); it("should render itself and child tab elements with corresponding scale (large)", async () => { @@ -155,9 +155,9 @@ describe("calcite-tabs", () => { html: `${tabsContent}`, }); expect(await page.find("calcite-tabs")).toEqualAttribute("scale", "l"); - expect(await page.find("calcite-tab-nav")).toEqualAttribute("scale", "l"); - expect(await page.find("calcite-tab-title")).toEqualAttribute("scale", "l"); - expect(await page.find("calcite-tab")).toEqualAttribute("scale", "l"); + expect(await (await page.find("calcite-tab-nav")).getProperty("scale")).toBe("l"); + expect(await (await page.find("calcite-tab")).getProperty("scale")).toBe("l"); + expect(await (await page.find("calcite-tab-title")).getProperty("scale")).toBe("l"); }); }); From 5474a9f18b66ecd24a3e73a0db1cd7b2cb5d7b1a Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 4 Oct 2023 23:33:28 -0700 Subject: [PATCH 033/108] removed the need for .getBoundingClientRect() --- .../src/components/tab-nav/tab-nav.e2e.ts | 8 +- .../src/components/tab-nav/tab-nav.tsx | 145 +++++++++++------- 2 files changed, 96 insertions(+), 57 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index 688c440f5a9..fffb42387a7 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -215,7 +215,7 @@ describe("calcite-tab-nav", () => { const tabTitles = Array.from(document.querySelectorAll("calcite-tab-title")) as HTMLCalciteTabTitleElement[]; const tabNavWidth = tabNav.offsetWidth; - const tabTitlesTotalWidth = tabTitles.reduce((sum, tabTitle: HTMLElement) => { + const tabTitlesTotalWidth = tabTitles.reduce((sum, tabTitle) => { return sum + tabTitle.offsetWidth; }, 0); @@ -235,15 +235,15 @@ describe("calcite-tab-nav", () => { let tabNavWidth: number; if (tabNav) { tabNav.scrollLeft = 0; - tabNavWidth = tabNav.getBoundingClientRect().width; + tabNavWidth = tabNav.clientWidth; } const visibleTabTitles = tabTitles.filter((tabTitle) => { const tabTitleRect = tabTitle.getBoundingClientRect(); return tabTitleRect.left >= 0 && tabTitleRect.right <= tabNavWidth; }); - const firstRightOverflowItem = tabTitles[visibleTabTitles.length]; - const isOverflowingEnd = firstRightOverflowItem.getBoundingClientRect().right > tabNavWidth; + const firstEndOverflowItem = tabTitles[visibleTabTitles.length]; + const isOverflowingEnd = firstEndOverflowItem.getBoundingClientRect().right > tabNavWidth; return isOverflowingEnd; }); diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index eb8c716f8d6..4399cb4b0b5 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -134,6 +134,7 @@ export class TabNav { componentWillRender(): void { const { parentTabsEl } = this; + // const tabTitles = this.el.querySelectorAll("calcite-tab-title"); this.layout = parentTabsEl?.layout; this.position = parentTabsEl?.position; @@ -143,6 +144,9 @@ export class TabNav { if (this.selectedTitle) { this.updateOffsetPosition(); } + // if (getElementDir(this.el) === "ltr" && this.layout === "inline") { + // this.findVisibleTabTitleIndices(tabTitles); + // } } componentDidRender(): void { @@ -330,56 +334,63 @@ export class TabNav { this.getOverflowIcons(); }); + private visibleTabTitleIndices; + //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- - private findVisibleTabTitleIndices = ( - tabTitles: NodeListOf, - direction: "forward" | "backward" - ): Map => { + private findVisibleTabTitleIndices = (tabTitles: NodeListOf): Map => { const elWidth = this.el.clientWidth; + console.log("elWidth", elWidth); const tabTitlesMap = new Map(); function findIndex() { - for (const [index, tabTitle] of Array.from(tabTitles).entries()) { - const tabTitleRect = tabTitle.getBoundingClientRect(); + tabTitles.forEach((tabTitle, index) => { + const tabTitleRect = tabTitle?.getBoundingClientRect(); + console.log("tabTitleRect", tabTitleRect.x); - if ( - (direction === "forward" && tabTitleRect.right <= elWidth) || - (direction === "backward" && tabTitleRect.left >= 0) - ) { + if (tabTitleRect?.x >= 0 && tabTitleRect?.x <= elWidth) { tabTitlesMap.set(tabTitle, index); } - } + }); } findIndex(); + console.log("tabTitlesMap", tabTitlesMap); + return tabTitlesMap; }; private scrollToTabTitles = (direction: "forward" | "backward"): void => { const tabTitles = this.el.querySelectorAll("calcite-tab-title"); - const visibleTabTitleIndices = this.findVisibleTabTitleIndices(tabTitles, direction); + let { visibleTabTitleIndices } = this; + + visibleTabTitleIndices = this.findVisibleTabTitleIndices(tabTitles); + console.log("visibleTabTitleIndices in scrollToTabTitles", visibleTabTitleIndices); - let lastValue: number; const tabTitlesArray = Array.from(tabTitles); - for (const value of visibleTabTitleIndices.values()) { + let lastValue: number; + for (const value of visibleTabTitleIndices?.values()) { lastValue = value; } + console.log("lastValue", lastValue); - const valuesIterator = visibleTabTitleIndices.values(); - const firstValue: number = valuesIterator.next().value; + const valuesIterator = visibleTabTitleIndices?.values(); + const firstValue: number = valuesIterator?.next().value; requestAnimationFrame(() => { - const targetIndex = direction === "forward" ? lastValue + 1 : firstValue - 1; + const targetIndex = direction === "forward" ? lastValue : firstValue - 1; const scrollInline = direction === "forward" ? "start" : "end"; + console.log("targetIndex", targetIndex); + console.log("tabTitlesArray", tabTitlesArray); + if (tabTitlesArray[targetIndex]) { tabTitlesArray[targetIndex].scrollIntoView({ behavior: "smooth", @@ -390,6 +401,7 @@ export class TabNav { }; private scrollToNextTabTitles = (): void => { + console.log("scrollToNextTabTitles"); this.scrollToTabTitles("forward"); }; @@ -480,49 +492,76 @@ export class TabNav { }); } - private getOverflowIcons(): (VNode | VNode[]) | null { + getOverflowDirection(): { + isOverflowingStart: boolean; + isOverflowingEnd: boolean; + } { const dir = getElementDir(this.el); - const tabNavWidth = this.el.offsetWidth; - const tabTitles = Array.from( - this.el.querySelectorAll("calcite-tab-title") - ); + const tabTitles = this.el.querySelectorAll("calcite-tab-title"); + const visibleTabTitleIndices = this.findVisibleTabTitleIndices(tabTitles); - const firstTitleRect = tabTitles?.[0]?.getBoundingClientRect(); - const lastTitleRect = tabTitles?.[tabTitles.length - 1]?.getBoundingClientRect(); + let isOverflowingEnd = false; + let isOverflowingStart = false; - const isOverflowingEnd = lastTitleRect?.right ?? 0 > tabNavWidth; - const isOverflowingStart = firstTitleRect?.left ?? 0 < 0; + if (dir === "ltr" && visibleTabTitleIndices.size > 0) { + const keyIterator = visibleTabTitleIndices.keys(); - const getActionChevronDirection = (overflowDirection: string): VNode => { - const isEnd = overflowDirection === "end"; + let lastKey; + for (const key of keyIterator) { + lastKey = key; + } - const dirScroll = () => - isEnd ? this.scrollToNextTabTitles() : this.scrollToPreviousTabTitles(); + if (keyIterator.next().value !== 0) { + isOverflowingStart = true; + } - return ( - dirScroll()} - scale={this.scale} - text="Placeholder" - /> - ); - }; - - const showEndAction = getActionChevronDirection("end"); - const showStartAction = getActionChevronDirection("start"); - let action: VNode | VNode[]; - - if (dir === "ltr") { - action = - isOverflowingEnd && isOverflowingStart - ? [showEndAction, showStartAction] - : isOverflowingEnd - ? showEndAction - : showStartAction; + if (visibleTabTitleIndices.get(lastKey) !== tabTitles.length - 1) { + isOverflowingEnd = true; + } } + return { isOverflowingStart, isOverflowingEnd }; + } + + getOverflowActions( + isOverflowingStart: boolean, + isOverflowingEnd: boolean + ): { showStartAction: string; showEndAction: string } { + let showStartAction = null; + let showEndAction = null; + + if (isOverflowingStart) { + showStartAction = "start"; + } + + if (isOverflowingEnd) { + showEndAction = "end"; + } + + return { showStartAction, showEndAction }; + } + + getActionChevronDirection = (overflowDirection: string): VNode => { + const isEnd = overflowDirection === "end"; + return ( + + ); + }; + + getOverflowIcons(): VNode[] { + const { isOverflowingStart, isOverflowingEnd } = this.getOverflowDirection(); + const { showStartAction, showEndAction } = this.getOverflowActions( + isOverflowingStart, + isOverflowingEnd + ); - return (action as VNode) !== null || (action as VNode[]).length > 0 ? action : null; + const chevronEnd = this.getActionChevronDirection(showEndAction); + const chevronStart = this.getActionChevronDirection(showStartAction); + return [chevronEnd, chevronStart]; } } From e9bb23c1e4d2817654f6c2bebfd62b73c0020a68 Mon Sep 17 00:00:00 2001 From: eliza Date: Thu, 5 Oct 2023 02:00:41 -0700 Subject: [PATCH 034/108] cleanup --- .../src/components/tab-nav/tab-nav.tsx | 68 +++++++++---------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 4399cb4b0b5..1f0fb7961f4 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -134,7 +134,6 @@ export class TabNav { componentWillRender(): void { const { parentTabsEl } = this; - // const tabTitles = this.el.querySelectorAll("calcite-tab-title"); this.layout = parentTabsEl?.layout; this.position = parentTabsEl?.position; @@ -144,9 +143,6 @@ export class TabNav { if (this.selectedTitle) { this.updateOffsetPosition(); } - // if (getElementDir(this.el) === "ltr" && this.layout === "inline") { - // this.findVisibleTabTitleIndices(tabTitles); - // } } componentDidRender(): void { @@ -311,6 +307,8 @@ export class TabNav { @State() selectedTabId: TabID; + @State() viewportVisibleTabTitleIndices: Map; + parentTabsEl: HTMLCalciteTabsElement; tabNavEl: HTMLDivElement; @@ -334,21 +332,20 @@ export class TabNav { this.getOverflowIcons(); }); - private visibleTabTitleIndices; - //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- - private findVisibleTabTitleIndices = (tabTitles: NodeListOf): Map => { + private findViewportVisibleTabTitleIndices = (): Map => { + const tabTitlesNodeList: NodeListOf = + this.el.querySelectorAll("calcite-tab-title"); const elWidth = this.el.clientWidth; - console.log("elWidth", elWidth); const tabTitlesMap = new Map(); function findIndex() { - tabTitles.forEach((tabTitle, index) => { + tabTitlesNodeList.forEach((tabTitle, index) => { const tabTitleRect = tabTitle?.getBoundingClientRect(); console.log("tabTitleRect", tabTitleRect.x); @@ -368,29 +365,26 @@ export class TabNav { private scrollToTabTitles = (direction: "forward" | "backward"): void => { const tabTitles = this.el.querySelectorAll("calcite-tab-title"); - let { visibleTabTitleIndices } = this; + let { viewportVisibleTabTitleIndices } = this; - visibleTabTitleIndices = this.findVisibleTabTitleIndices(tabTitles); - console.log("visibleTabTitleIndices in scrollToTabTitles", visibleTabTitleIndices); + viewportVisibleTabTitleIndices = this.findViewportVisibleTabTitleIndices(); + console.log("visibleTabTitleIndices in scrollToTabTitles", viewportVisibleTabTitleIndices); const tabTitlesArray = Array.from(tabTitles); let lastValue: number; - for (const value of visibleTabTitleIndices?.values()) { + for (const value of viewportVisibleTabTitleIndices?.values()) { lastValue = value; } console.log("lastValue", lastValue); - const valuesIterator = visibleTabTitleIndices?.values(); + const valuesIterator = viewportVisibleTabTitleIndices?.values(); const firstValue: number = valuesIterator?.next().value; requestAnimationFrame(() => { const targetIndex = direction === "forward" ? lastValue : firstValue - 1; const scrollInline = direction === "forward" ? "start" : "end"; - console.log("targetIndex", targetIndex); - console.log("tabTitlesArray", tabTitlesArray); - if (tabTitlesArray[targetIndex]) { tabTitlesArray[targetIndex].scrollIntoView({ behavior: "smooth", @@ -439,6 +433,11 @@ export class TabNav { this.indicatorWidth = this.selectedTitle?.offsetWidth; } + updateViewportVisibleTabTitleIndices(): void { + this.viewportVisibleTabTitleIndices = this.findViewportVisibleTabTitleIndices(); + console.log("this.viewportVisibleTabTitleIndices", this.viewportVisibleTabTitleIndices); + } + getIndexOfTabTitle(el: HTMLCalciteTabTitleElement, tabTitles = this.tabTitles): number { // In most cases, since these indexes correlate with tab contents, we want to consider all tab titles. // However, when doing relative index operations, it makes sense to pass in this.enabledTabTitles as the 2nd arg. @@ -488,8 +487,12 @@ export class TabNav { requestAnimationFrame(() => { this.updateOffsetPosition(); this.updateActiveWidth(); + tabTitles[this.selectedTabId].focus(); }); + requestAnimationFrame(() => { + this.updateViewportVisibleTabTitleIndices(); + }); } getOverflowDirection(): { @@ -498,46 +501,37 @@ export class TabNav { } { const dir = getElementDir(this.el); const tabTitles = this.el.querySelectorAll("calcite-tab-title"); - const visibleTabTitleIndices = this.findVisibleTabTitleIndices(tabTitles); + const viewportVisibleTabTitleIndices = this.findViewportVisibleTabTitleIndices(); let isOverflowingEnd = false; let isOverflowingStart = false; - if (dir === "ltr" && visibleTabTitleIndices.size > 0) { - const keyIterator = visibleTabTitleIndices.keys(); - + if (dir === "ltr" && viewportVisibleTabTitleIndices.size > 0) { let lastKey; - for (const key of keyIterator) { + for (const key of viewportVisibleTabTitleIndices.keys()) { lastKey = key; } - if (keyIterator.next().value !== 0) { + if (viewportVisibleTabTitleIndices.keys().next().value !== 0) { isOverflowingStart = true; } - if (visibleTabTitleIndices.get(lastKey) !== tabTitles.length - 1) { + if (viewportVisibleTabTitleIndices.get(lastKey) !== tabTitles.length - 1) { isOverflowingEnd = true; } } + return { isOverflowingStart, isOverflowingEnd }; } getOverflowActions( isOverflowingStart: boolean, isOverflowingEnd: boolean - ): { showStartAction: string; showEndAction: string } { - let showStartAction = null; - let showEndAction = null; - - if (isOverflowingStart) { - showStartAction = "start"; - } - - if (isOverflowingEnd) { - showEndAction = "end"; - } - - return { showStartAction, showEndAction }; + ): { showStartAction: string | null; showEndAction: string | null } { + return { + showStartAction: isOverflowingStart ? "start" : null, + showEndAction: isOverflowingEnd ? "end" : null, + }; } getActionChevronDirection = (overflowDirection: string): VNode => { From 712fb31655e851973502737764588881b73b4e3c Mon Sep 17 00:00:00 2001 From: eliza Date: Thu, 5 Oct 2023 14:55:06 -0700 Subject: [PATCH 035/108] apply bordered styling to the action button --- .../src/components/tab-nav/tab-nav.scss | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.scss b/packages/calcite-components/src/components/tab-nav/tab-nav.scss index d85d0ce1c9d..f83521fcc1d 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.scss +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.scss @@ -14,6 +14,29 @@ min-block-size: theme("spacing.11"); } +:host([bordered]) calcite-action { + box-shadow: inset 0 1px 0 var(--calcite-ui-border-1); + background-color: var(--calcite-ui-foreground-1); +} + +:host([bordered]:not([position="bottom"])) calcite-action { + margin-block-end: -1px; +} + +:host([bordered][position="bottom"]) calcite-action { + box-shadow: inset 0 1px 0 var(--calcite-ui-border-1), inset 0 -1px 0 var(--calcite-ui-border-1); +} + +:host([bordered]) calcite-action { + @apply border-color-1 border border-solid; +} + +.tab-nav::-webkit-scrollbar { + display: none; + -ms-overflow-style: none; + scrollbar-width: none; +} + .tab-nav { @apply flex w-full @@ -51,13 +74,11 @@ calcite-action { .arrow-end { @apply h-full overflow-hidden; inset-inline-end: 0; - box-shadow: -10px 0 10px -2px white; } .arrow-start { @apply h-full overflow-hidden; inset-inline-start: 0; - box-shadow: 10px 0 10px -2px white; } :host([layout="center"]) .tab-nav { From 3e5da4ce1e68e2ff9c8d8b43490d3bcfcbf72ec7 Mon Sep 17 00:00:00 2001 From: eliza Date: Tue, 10 Oct 2023 14:30:43 -0700 Subject: [PATCH 036/108] calcite appearance based on tab-nav being bordered or not --- .../src/components/tab-nav/tab-nav.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 1f0fb7961f4..69f9b6d76a7 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -184,7 +184,9 @@ export class TabNav { // eslint-disable-next-line react/jsx-sort-props -- ref should be last so node attrs/props are in sync (see https://github.com/Esri/calcite-design-system/pull/6530) ref={(el: HTMLDivElement) => (this.tabNavEl = el)} > - +
+ +
{ const tabTitleRect = tabTitle?.getBoundingClientRect(); - console.log("tabTitleRect", tabTitleRect.x); if (tabTitleRect?.x >= 0 && tabTitleRect?.x <= elWidth) { tabTitlesMap.set(tabTitle, index); @@ -357,8 +358,6 @@ export class TabNav { findIndex(); - console.log("tabTitlesMap", tabTitlesMap); - return tabTitlesMap; }; @@ -368,15 +367,12 @@ export class TabNav { let { viewportVisibleTabTitleIndices } = this; viewportVisibleTabTitleIndices = this.findViewportVisibleTabTitleIndices(); - console.log("visibleTabTitleIndices in scrollToTabTitles", viewportVisibleTabTitleIndices); - const tabTitlesArray = Array.from(tabTitles); let lastValue: number; for (const value of viewportVisibleTabTitleIndices?.values()) { lastValue = value; } - console.log("lastValue", lastValue); const valuesIterator = viewportVisibleTabTitleIndices?.values(); const firstValue: number = valuesIterator?.next().value; @@ -395,7 +391,6 @@ export class TabNav { }; private scrollToNextTabTitles = (): void => { - console.log("scrollToNextTabTitles"); this.scrollToTabTitles("forward"); }; @@ -435,7 +430,6 @@ export class TabNav { updateViewportVisibleTabTitleIndices(): void { this.viewportVisibleTabTitleIndices = this.findViewportVisibleTabTitleIndices(); - console.log("this.viewportVisibleTabTitleIndices", this.viewportVisibleTabTitleIndices); } getIndexOfTabTitle(el: HTMLCalciteTabTitleElement, tabTitles = this.tabTitles): number { @@ -536,8 +530,12 @@ export class TabNav { getActionChevronDirection = (overflowDirection: string): VNode => { const isEnd = overflowDirection === "end"; + return ( Date: Wed, 11 Oct 2023 14:04:11 -0700 Subject: [PATCH 037/108] WIP: work out tab-title container clipping to accomodate transparent arrows --- .../src/components/tab-nav/resources.ts | 1 + .../src/components/tab-nav/tab-nav.scss | 3 ++- .../src/components/tab-nav/tab-nav.tsx | 13 +++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/tab-nav/resources.ts b/packages/calcite-components/src/components/tab-nav/resources.ts index 192e8fd5e78..24e1429e7ae 100644 --- a/packages/calcite-components/src/components/tab-nav/resources.ts +++ b/packages/calcite-components/src/components/tab-nav/resources.ts @@ -6,4 +6,5 @@ export const ICON = { export const CSS = { arrowEnd: "arrow-end", arrowStart: "arrow-start", + tabTitleSlotWrapper: "tab-titles-slot-wrapper", }; diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.scss b/packages/calcite-components/src/components/tab-nav/tab-nav.scss index f83521fcc1d..a0f39c34b8e 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.scss +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.scss @@ -37,7 +37,8 @@ scrollbar-width: none; } -.tab-nav { +.tab-nav, +.tab-titles-slot-wrapper { @apply flex w-full justify-start diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 69f9b6d76a7..1e2499886c7 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -555,6 +555,19 @@ export class TabNav { const chevronEnd = this.getActionChevronDirection(showEndAction); const chevronStart = this.getActionChevronDirection(showStartAction); + const tabTitleSlotWrapper: HTMLDivElement = this.el.shadowRoot.querySelector( + `.${CSS.tabTitleSlotWrapper}` + ); + if (tabTitleSlotWrapper) { + const scaleValue = this.scale === "s" ? "1.5" : this.scale === "m" ? "2" : "2.5"; + console.log("tabTitleSlotWrapper", tabTitleSlotWrapper); + const style = tabTitleSlotWrapper.style; + + console.log("isOverflowingStart", isOverflowingStart); + console.log("isOverflowingEnd", isOverflowingEnd); + style.paddingInlineStart = isOverflowingStart ? `${scaleValue}rem` : ""; + style.paddingInlineEnd = isOverflowingEnd ? `${scaleValue}rem` : ""; + } return [chevronEnd, chevronStart]; } } From 7db0415ca40d45d811bb4a97f56701b0f101fbe8 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 6 Dec 2023 14:21:12 -0800 Subject: [PATCH 038/108] merge update --- .../calcite-components/src/components.d.ts | 85 ++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components.d.ts b/packages/calcite-components/src/components.d.ts index e9c910b4f0e..6cb5275c5e1 100644 --- a/packages/calcite-components/src/components.d.ts +++ b/packages/calcite-components/src/components.d.ts @@ -100,6 +100,7 @@ import { } from "./components/stepper/interfaces"; import { StepperItemMessages } from "./components/stepper-item/assets/stepper-item/t9n"; import { TabID, TabLayout, TabPosition } from "./components/tabs/interfaces"; +import { TabNavMessages } from "./components/tab-nav/assets/tab-nav/t9n"; import { TabChangeEventDetail, TabCloseEventDetail } from "./components/tab/interfaces"; import { TabTitleMessages } from "./components/tab-title/assets/tab-title/t9n"; import { RowType, TableLayout, TableRowFocusEvent } from "./components/table/interfaces"; @@ -209,6 +210,7 @@ export { } from "./components/stepper/interfaces"; export { StepperItemMessages } from "./components/stepper-item/assets/stepper-item/t9n"; export { TabID, TabLayout, TabPosition } from "./components/tabs/interfaces"; +export { TabNavMessages } from "./components/tab-nav/assets/tab-nav/t9n"; export { TabChangeEventDetail, TabCloseEventDetail } from "./components/tab/interfaces"; export { TabTitleMessages } from "./components/tab-title/assets/tab-title/t9n"; export { RowType, TableLayout, TableRowFocusEvent } from "./components/table/interfaces"; @@ -1532,6 +1534,10 @@ export namespace Components { selectionMode: Extract<"none" | "single" | "multiple", SelectionMode>; } interface CalciteDropdownItem { + /** + * When `true`, interaction is prevented and the component is displayed with lower opacity. + */ + disabled: boolean; /** * Specifies the URL of the linked resource, which can be set as an absolute or relative path. Determines if the component will render as an anchor. */ @@ -4455,6 +4461,14 @@ export namespace Components { indicatorOffset: number; indicatorWidth: number; layout: TabLayout; + /** + * Use this property to override individual strings used by the component. + */ + messageOverrides: Partial; + /** + * Made into a prop for testing purposes only. + */ + messages: TabNavMessages; /** * Specifies the position of `calcite-tab-nav` and `calcite-tab-title` components in relation to, and is inherited from the parent `calcite-tabs`, defaults to `top`. */ @@ -5467,6 +5481,10 @@ export interface CalciteSheetCustomEvent extends CustomEvent { detail: T; target: HTMLCalciteSheetElement; } +export interface CalciteShellPanelCustomEvent extends CustomEvent { + detail: T; + target: HTMLCalciteShellPanelElement; +} export interface CalciteSliderCustomEvent extends CustomEvent { detail: T; target: HTMLCalciteSliderElement; @@ -8747,7 +8765,58 @@ declare global { prototype: HTMLCalciteShellCenterRowElement; new (): HTMLCalciteShellCenterRowElement; }; - interface HTMLCalciteShellPanelElement extends Components.CalciteShellPanel, HTMLStencilElement {} + interface HTMLCalciteShellPanelElementEventMap { + calciteInternalShellPanelResizeStart: void; + calciteInternalShellPanelResizeEnd: void; + } + interface HTMLCalciteShellPanelElement extends Components.CalciteShellPanel, HTMLStencilElement { + addEventListener( + type: K, + listener: ( + this: HTMLCalciteShellPanelElement, + ev: CalciteShellPanelCustomEvent + ) => any, + options?: boolean | AddEventListenerOptions + ): void; + addEventListener( + type: K, + listener: (this: Document, ev: DocumentEventMap[K]) => any, + options?: boolean | AddEventListenerOptions + ): void; + addEventListener( + type: K, + listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, + options?: boolean | AddEventListenerOptions + ): void; + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions + ): void; + removeEventListener( + type: K, + listener: ( + this: HTMLCalciteShellPanelElement, + ev: CalciteShellPanelCustomEvent + ) => any, + options?: boolean | EventListenerOptions + ): void; + removeEventListener( + type: K, + listener: (this: Document, ev: DocumentEventMap[K]) => any, + options?: boolean | EventListenerOptions + ): void; + removeEventListener( + type: K, + listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, + options?: boolean | EventListenerOptions + ): void; + removeEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | EventListenerOptions + ): void; + } var HTMLCalciteShellPanelElement: { prototype: HTMLCalciteShellPanelElement; new (): HTMLCalciteShellPanelElement; @@ -11480,6 +11549,10 @@ declare namespace LocalJSX { selectionMode?: Extract<"none" | "single" | "multiple", SelectionMode>; } interface CalciteDropdownItem { + /** + * When `true`, interaction is prevented and the component is displayed with lower opacity. + */ + disabled?: boolean; /** * Specifies the URL of the linked resource, which can be set as an absolute or relative path. Determines if the component will render as an anchor. */ @@ -14104,6 +14177,8 @@ declare namespace LocalJSX { * Made into a prop for testing purposes only */ messages?: ShellPanelMessages; + onCalciteInternalShellPanelResizeEnd?: (event: CalciteShellPanelCustomEvent) => void; + onCalciteInternalShellPanelResizeStart?: (event: CalciteShellPanelCustomEvent) => void; /** * Specifies the component's position. Will be flipped when the element direction is right-to-left (`"rtl"`). */ @@ -14504,6 +14579,14 @@ declare namespace LocalJSX { indicatorOffset?: number; indicatorWidth?: number; layout?: TabLayout; + /** + * Use this property to override individual strings used by the component. + */ + messageOverrides?: Partial; + /** + * Made into a prop for testing purposes only. + */ + messages?: TabNavMessages; onCalciteInternalTabChange?: (event: CalciteTabNavCustomEvent) => void; /** * Emits when the selected `calcite-tab` changes. From 870f629400aac45209c6c728a4db2105c0634fdd Mon Sep 17 00:00:00 2001 From: JC Franco Date: Fri, 15 Dec 2023 09:35:26 -0700 Subject: [PATCH 039/108] alt approach w/ intersection observer --- .../src/components/tab-nav/tab-nav.tsx | 169 ++++++------------ 1 file changed, 57 insertions(+), 112 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 106a315ca20..697447de95c 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -164,6 +164,7 @@ export class TabNav { disconnectedCallback(): void { this.resizeObserver?.disconnect(); + this.intersectionObserver?.disconnect(); } //-------------------------------------------------------------------------- @@ -188,10 +189,17 @@ export class TabNav { }} onScroll={this.handleContainerScroll} // eslint-disable-next-line react/jsx-sort-props -- ref should be last so node attrs/props are in sync (see https://github.com/Esri/calcite-design-system/pull/6530) - ref={(el: HTMLDivElement) => (this.tabNavEl = el)} + ref={this.storeContainerRef} >
- + { + const slottedChildren = (event.target as HTMLSlotElement).assignedElements(); + slottedChildren.forEach((child) => { + this.intersectionObserver.observe(child); + }); + }} + />
(this.activeIndicatorEl = el as HTMLElement)} />
- {this.layout === "inline" && this.getOverflowIcons()} + {this.layout === "inline" && this.renderScrollingActions()}
); @@ -313,6 +321,10 @@ export class TabNav { @Element() el: HTMLCalciteTabNavElement; + @State() private overflowingStartAction: HTMLCalciteTabTitleElement = null; + + @State() private overflowingEndAction: HTMLCalciteTabTitleElement = null; + @State() selectedTabId: TabID; @State() viewportVisibleTabTitleIndices: Map; @@ -336,63 +348,60 @@ export class TabNav { this.activeIndicatorEl.style.transitionDuration = "0s"; this.updateActiveWidth(); this.updateOffsetPosition(); - - this.getOverflowIcons(); }); + @State() private tabTitleIntersectionObserverEntries: IntersectionObserverEntry[]; + + private intersectionObserver: IntersectionObserver; + //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- - private findViewportVisibleTabTitleIndices = (): Map => { - const tabTitlesNodeList: NodeListOf = - this.el.querySelectorAll("calcite-tab-title"); - const elWidth = this.el.clientWidth; - const tabTitlesMap = new Map(); + private storeContainerRef = (el: HTMLDivElement) => { + this.tabNavEl = el; - function findIndex() { - tabTitlesNodeList.forEach((tabTitle, index) => { - const tabTitleRect = tabTitle?.getBoundingClientRect(); - - if (tabTitleRect?.x >= 0 && tabTitleRect?.x <= elWidth) { - tabTitlesMap.set(tabTitle, index); - } - }); + if (!el) { + return; } - findIndex(); + // may need to return early for "inline" layout if scrolling is not applicable - return tabTitlesMap; + this.intersectionObserver = createObserver( + "intersection", + (entries) => { + this.tabTitleIntersectionObserverEntries = entries; + this.determineScrollStatus(); + }, + { + root: this.tabNavEl, + } + ); }; - private scrollToTabTitles = (direction: "forward" | "backward"): void => { - const tabTitles = this.el.querySelectorAll("calcite-tab-title"); - - let { viewportVisibleTabTitleIndices } = this; + private determineScrollStatus(): void { + // currently set for demonstration purposes, it could be split up into different pieces within the component (e.g., state props, etc.) + const overflowingStartTabTitle: null | HTMLCalciteTabTitleElement = null; + const overflowingEndTabTitle: null | HTMLCalciteTabTitleElement = null; - viewportVisibleTabTitleIndices = this.findViewportVisibleTabTitleIndices(); - const tabTitlesArray = Array.from(tabTitles); + // TODO: determine which items are overflowing and in which direction with intersection observer entries - let lastValue: number; - for (const value of viewportVisibleTabTitleIndices?.values()) { - lastValue = value; - } - - const valuesIterator = viewportVisibleTabTitleIndices?.values(); - const firstValue: number = valuesIterator?.next().value; + this.overflowingEndAction = overflowingEndTabTitle; + this.overflowingStartAction = overflowingStartTabTitle; + } + private scrollToTabTitles = (direction: "forward" | "backward"): void => { requestAnimationFrame(() => { - const targetIndex = direction === "forward" ? lastValue : firstValue - 1; - const scrollInline = direction === "forward" ? "start" : "end"; + const targetTabTitle: HTMLCalciteTabTitleElement = + direction === "forward" ? this.overflowingEndAction : this.overflowingStartAction; + const scrollInline = direction === "forward" ? "end" : "start"; - if (tabTitlesArray[targetIndex]) { - tabTitlesArray[targetIndex].scrollIntoView({ - behavior: "smooth", - inline: scrollInline, - }); - } + targetTabTitle.scrollIntoView({ + behavior: "smooth", + inline: scrollInline, + }); }); }; @@ -434,10 +443,6 @@ export class TabNav { this.indicatorWidth = this.selectedTitle?.offsetWidth; } - updateViewportVisibleTabTitleIndices(): void { - this.viewportVisibleTabTitleIndices = this.findViewportVisibleTabTitleIndices(); - } - getIndexOfTabTitle(el: HTMLCalciteTabTitleElement, tabTitles = this.tabTitles): number { // In most cases, since these indexes correlate with tab contents, we want to consider all tab titles. // However, when doing relative index operations, it makes sense to pass in this.enabledTabTitles as the 2nd arg. @@ -490,51 +495,9 @@ export class TabNav { tabTitles[this.selectedTabId].focus(); }); - requestAnimationFrame(() => { - this.updateViewportVisibleTabTitleIndices(); - }); - } - - getOverflowDirection(): { - isOverflowingStart: boolean; - isOverflowingEnd: boolean; - } { - const dir = getElementDir(this.el); - const tabTitles = this.el.querySelectorAll("calcite-tab-title"); - const viewportVisibleTabTitleIndices = this.findViewportVisibleTabTitleIndices(); - - let isOverflowingEnd = false; - let isOverflowingStart = false; - - if (dir === "ltr" && viewportVisibleTabTitleIndices.size > 0) { - let lastKey; - for (const key of viewportVisibleTabTitleIndices.keys()) { - lastKey = key; - } - - if (viewportVisibleTabTitleIndices.keys().next().value !== 0) { - isOverflowingStart = true; - } - - if (viewportVisibleTabTitleIndices.get(lastKey) !== tabTitles.length - 1) { - isOverflowingEnd = true; - } - } - - return { isOverflowingStart, isOverflowingEnd }; - } - - getOverflowActions( - isOverflowingStart: boolean, - isOverflowingEnd: boolean - ): { showStartAction: string | null; showEndAction: string | null } { - return { - showStartAction: isOverflowingStart ? "start" : null, - showEndAction: isOverflowingEnd ? "end" : null, - }; } - getActionChevronDirection = (overflowDirection: string): VNode => { + renderScrollingAction = (overflowDirection: "start" | "end"): VNode => { const isEnd = overflowDirection === "end"; return ( @@ -543,7 +506,9 @@ export class TabNav { (this.el.parentElement as HTMLCalciteTabsElement).bordered ? "solid" : "transparent" } class={isEnd ? CSS.arrowEnd : CSS.arrowStart} + hidden={(!isEnd && !this.overflowingStartAction) || (isEnd && !this.overflowingEndAction)} icon={isEnd ? ICON.chevronRight : ICON.chevronLeft} + key={overflowDirection} onClick={isEnd ? this.scrollToNextTabTitles : this.scrollToPreviousTabTitles} scale={this.scale} text="Placeholder" @@ -551,29 +516,9 @@ export class TabNav { ); }; - getOverflowIcons(): VNode[] { - const { isOverflowingStart, isOverflowingEnd } = this.getOverflowDirection(); - const { showStartAction, showEndAction } = this.getOverflowActions( - isOverflowingStart, - isOverflowingEnd - ); - - const chevronEnd = this.getActionChevronDirection(showEndAction); - const chevronStart = this.getActionChevronDirection(showStartAction); - - const tabTitleSlotWrapper: HTMLDivElement = this.el.shadowRoot.querySelector( - `.${CSS.tabTitleSlotWrapper}` - ); - if (tabTitleSlotWrapper) { - const scaleValue = this.scale === "s" ? "1.5" : this.scale === "m" ? "2" : "2.5"; - console.log("tabTitleSlotWrapper", tabTitleSlotWrapper); - const style = tabTitleSlotWrapper.style; - - console.log("isOverflowingStart", isOverflowingStart); - console.log("isOverflowingEnd", isOverflowingEnd); - style.paddingInlineStart = isOverflowingStart ? `${scaleValue}rem` : ""; - style.paddingInlineEnd = isOverflowingEnd ? `${scaleValue}rem` : ""; - } - return [chevronEnd, chevronStart]; + renderScrollingActions(): VNode[] { + const startScrollingAction = this.renderScrollingAction("start"); + const endScrollingAction = this.renderScrollingAction("end"); + return [startScrollingAction, endScrollingAction]; } } From 8e3c94216e9b72ef7cc972a6e8b9b5381ba36d28 Mon Sep 17 00:00:00 2001 From: JC Franco Date: Mon, 18 Dec 2023 13:05:45 -0700 Subject: [PATCH 040/108] add bound checking logic --- .../src/components/tab-nav/tab-nav.tsx | 59 +++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 697447de95c..9047d740135 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -321,9 +321,9 @@ export class TabNav { @Element() el: HTMLCalciteTabNavElement; - @State() private overflowingStartAction: HTMLCalciteTabTitleElement = null; + @State() private overflowingStartTabTitle: HTMLCalciteTabTitleElement = null; - @State() private overflowingEndAction: HTMLCalciteTabTitleElement = null; + @State() private overflowingEndTabTitle: HTMLCalciteTabTitleElement = null; @State() selectedTabId: TabID; @@ -350,7 +350,11 @@ export class TabNav { this.updateOffsetPosition(); }); - @State() private tabTitleIntersectionObserverEntries: IntersectionObserverEntry[]; + // TODO: create/remove on connect/disconnect + @State() private tabTitleToIntersectionObserverEntry = new Map< + HTMLCalciteTabTitleElement, + IntersectionObserverEntry + >(); private intersectionObserver: IntersectionObserver; @@ -372,7 +376,14 @@ export class TabNav { this.intersectionObserver = createObserver( "intersection", (entries) => { - this.tabTitleIntersectionObserverEntries = entries; + entries.forEach((entry) => { + // TODO: need to update map when tab-titles are removed + this.tabTitleToIntersectionObserverEntry.set( + entry.target as HTMLCalciteTabTitleElement, + entry + ); + }); + this.determineScrollStatus(); }, { @@ -383,19 +394,43 @@ export class TabNav { private determineScrollStatus(): void { // currently set for demonstration purposes, it could be split up into different pieces within the component (e.g., state props, etc.) - const overflowingStartTabTitle: null | HTMLCalciteTabTitleElement = null; - const overflowingEndTabTitle: null | HTMLCalciteTabTitleElement = null; + let overflowingStartTabTitle: null | HTMLCalciteTabTitleElement = null; + let overflowingEndTabTitle: null | HTMLCalciteTabTitleElement = null; + + // TODO: need to unobserve on removal of tab-title + const entries = Array.from(this.tabTitleToIntersectionObserverEntry.values()); + + entries.forEach((entry) => { + // Note: this depends on items being in DOM order + const outsideOfViewport = !entry.isIntersecting; + + if ( + outsideOfViewport && + entry.boundingClientRect.left < entry.rootBounds.left && + entry.boundingClientRect.right <= entry.rootBounds.right + ) { + overflowingStartTabTitle = entry.target as HTMLCalciteTabTitleElement; + } - // TODO: determine which items are overflowing and in which direction with intersection observer entries + if ( + outsideOfViewport && + entry.boundingClientRect.left > entry.rootBounds.right && + entry.boundingClientRect.right >= entry.rootBounds.right + ) { + if (!overflowingEndTabTitle) { + overflowingEndTabTitle = entry.target as HTMLCalciteTabTitleElement; + } + } + }); - this.overflowingEndAction = overflowingEndTabTitle; - this.overflowingStartAction = overflowingStartTabTitle; + this.overflowingEndTabTitle = overflowingEndTabTitle; + this.overflowingStartTabTitle = overflowingStartTabTitle; } private scrollToTabTitles = (direction: "forward" | "backward"): void => { requestAnimationFrame(() => { const targetTabTitle: HTMLCalciteTabTitleElement = - direction === "forward" ? this.overflowingEndAction : this.overflowingStartAction; + direction === "forward" ? this.overflowingEndTabTitle : this.overflowingStartTabTitle; const scrollInline = direction === "forward" ? "end" : "start"; targetTabTitle.scrollIntoView({ @@ -506,7 +541,9 @@ export class TabNav { (this.el.parentElement as HTMLCalciteTabsElement).bordered ? "solid" : "transparent" } class={isEnd ? CSS.arrowEnd : CSS.arrowStart} - hidden={(!isEnd && !this.overflowingStartAction) || (isEnd && !this.overflowingEndAction)} + hidden={ + (!isEnd && !this.overflowingStartTabTitle) || (isEnd && !this.overflowingEndTabTitle) + } icon={isEnd ? ICON.chevronRight : ICON.chevronLeft} key={overflowDirection} onClick={isEnd ? this.scrollToNextTabTitles : this.scrollToPreviousTabTitles} From e8fec064a2d6d5a56ea749f5d725a7e3a88981cf Mon Sep 17 00:00:00 2001 From: JC Franco Date: Tue, 16 Jan 2024 13:10:31 -0800 Subject: [PATCH 041/108] tidy up --- packages/calcite-components/src/components/tabs/tabs.e2e.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/calcite-components/src/components/tabs/tabs.e2e.ts b/packages/calcite-components/src/components/tabs/tabs.e2e.ts index 9e8b50c1ef4..3f7ec456daa 100644 --- a/packages/calcite-components/src/components/tabs/tabs.e2e.ts +++ b/packages/calcite-components/src/components/tabs/tabs.e2e.ts @@ -26,7 +26,7 @@ describe("calcite-tabs", () => { }); describe("honors hidden attribute", () => { - hidden(tabsSnippet); + hidden("calcite-tabs"); }); describe("defaults", () => { @@ -46,7 +46,7 @@ describe("calcite-tabs", () => { }); describe("accessible: checked", () => { - accessible(tabsSnippet); + accessible(`${tabsContent}`); }); it("sets up basic aria attributes", async () => { @@ -283,7 +283,6 @@ describe("calcite-tabs", () => { }, wrappedTabTemplateHTML, ); - expect(finalSelectedItem.tabTitle).toBe("title-2"); expect(finalSelectedItem.tab).toBe("tab-2"); }); From 406b8e100624fb6effed1a879d4ae61fd82557d0 Mon Sep 17 00:00:00 2001 From: JC Franco Date: Tue, 16 Jan 2024 14:54:19 -0800 Subject: [PATCH 042/108] fix missing import --- .../calcite-components/src/components/tab-nav/tab-nav.e2e.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts index 9e64aa0df9b..24f95c12b62 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts @@ -1,6 +1,7 @@ import { E2EPage, newE2EPage } from "@stencil/core/testing"; import { accessible, defaults, renders, hidden } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; +import { CSS } from "./resources"; describe("calcite-tab-nav", () => { describe("defaults", () => { From cc3b64cea3996850b6053fa0e6579612e5ebde42 Mon Sep 17 00:00:00 2001 From: JC Franco Date: Tue, 16 Jan 2024 14:54:49 -0800 Subject: [PATCH 043/108] roll back tabs being full width by default --- packages/calcite-components/src/components/tabs/tabs.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/tabs/tabs.scss b/packages/calcite-components/src/components/tabs/tabs.scss index 343cc224345..ed21a427295 100644 --- a/packages/calcite-components/src/components/tabs/tabs.scss +++ b/packages/calcite-components/src/components/tabs/tabs.scss @@ -1,5 +1,5 @@ :host { - @apply flex flex-col w-full; + @apply flex flex-col; } :host([bordered]) { From 1c9e394080e6af92e5003f18c227d496824a19c2 Mon Sep 17 00:00:00 2001 From: JC Franco Date: Tue, 16 Jan 2024 14:59:04 -0800 Subject: [PATCH 044/108] simplify styles --- .../calcite-components/src/components/tabs/tabs.scss | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/calcite-components/src/components/tabs/tabs.scss b/packages/calcite-components/src/components/tabs/tabs.scss index ed21a427295..29477f81a5b 100644 --- a/packages/calcite-components/src/components/tabs/tabs.scss +++ b/packages/calcite-components/src/components/tabs/tabs.scss @@ -5,10 +5,11 @@ :host([bordered]) { box-shadow: inset 0 1px 0 var(--calcite-color-border-1); background-color: var(--calcite-color-foreground-1); -} - -:host([bordered]:not([position="bottom"])) ::slotted(calcite-tab-nav) { margin-block-end: -1px; + + section { + @apply border-color-1 border border-solid; + } } :host([bordered][position="bottom"]) { @@ -17,10 +18,6 @@ inset 0 -1px 0 var(--calcite-color-border-1); } -:host([bordered]) section { - @apply border-color-1 border border-solid; -} - :host([bordered][scale="s"]) section { @apply p-3; } From cf95c28f702ad2b43e3cc3169557be6e58cd8a9c Mon Sep 17 00:00:00 2001 From: JC Franco Date: Tue, 16 Jan 2024 15:22:15 -0800 Subject: [PATCH 045/108] wire up messages --- packages/calcite-components/src/components/tab-nav/tab-nav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 5d333017723..828678444c1 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -598,7 +598,7 @@ export class TabNav implements LocalizedComponent, T9nComponent { key={overflowDirection} onClick={isEnd ? this.scrollToNextTabTitles : this.scrollToPreviousTabTitles} scale={this.scale} - text="Placeholder" + text={isEnd ? this.messages.nextTabTitles : this.messages.previousTabTitles} /> ); }; From c0a0614a3f5b898d8357ec33493a067670361648 Mon Sep 17 00:00:00 2001 From: JC Franco Date: Tue, 16 Jan 2024 15:25:10 -0800 Subject: [PATCH 046/108] tidy up --- .../src/components/tab-nav/tab-nav.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx index 828678444c1..7099745c38b 100644 --- a/packages/calcite-components/src/components/tab-nav/tab-nav.tsx +++ b/packages/calcite-components/src/components/tab-nav/tab-nav.tsx @@ -583,29 +583,24 @@ export class TabNav implements LocalizedComponent, T9nComponent { } private renderScrollingAction = (overflowDirection: "start" | "end"): VNode => { + const { bordered, messages, overflowingStartTabTitle, overflowingEndTabTitle, scale } = this; const isEnd = overflowDirection === "end"; return (