From 4d4821cb9f1812dea9211bdb7f89c62b75164981 Mon Sep 17 00:00:00 2001 From: Todor Stoyanov Date: Tue, 7 Jan 2025 07:43:52 +0200 Subject: [PATCH 1/7] feat(ui5-timeline): introduce "growing" property --- packages/fiori/src/Timeline.ts | 195 +++++++++++++++++++- packages/fiori/src/TimelineGroupItem.ts | 2 +- packages/fiori/src/TimelineItem.ts | 2 +- packages/fiori/src/TimelineItemTemplate.tsx | 2 +- packages/fiori/src/TimelineTemplate.tsx | 60 +++++- packages/fiori/src/themes/Timeline.css | 15 ++ packages/fiori/src/types/TimelineLayout.ts | 26 ++- packages/fiori/test/pages/Timeline.html | 28 ++- 8 files changed, 309 insertions(+), 21 deletions(-) diff --git a/packages/fiori/src/Timeline.ts b/packages/fiori/src/Timeline.ts index 28d9a324048e..5a5ea267b9d8 100644 --- a/packages/fiori/src/Timeline.ts +++ b/packages/fiori/src/Timeline.ts @@ -5,9 +5,12 @@ import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import BusyIndicator from "@ui5/webcomponents/dist/BusyIndicator.js"; import { isTabNext, isTabPrevious, + isSpace, + isEnter, } from "@ui5/webcomponents-base/dist/Keys.js"; import type { ITabbable } from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js"; import type ToggleButton from "@ui5/webcomponents/dist/ToggleButton.js"; @@ -15,12 +18,14 @@ import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation import NavigationMode from "@ui5/webcomponents-base/dist/types/NavigationMode.js"; import { TIMELINE_ARIA_LABEL } from "./generated/i18n/i18n-defaults.js"; import TimelineTemplate from "./TimelineTemplate.js"; -import "./TimelineItem.js"; -import "./TimelineGroupItem.js"; - +import TimelineItem from "./TimelineItem.js"; +import TimelineGroupItem from "./TimelineGroupItem.js"; +import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; +import type { ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js"; +import debounce from "@ui5/webcomponents-base/dist/util/debounce.js"; // Styles import TimelineCss from "./generated/themes/Timeline.css.js"; -import TimelineLayout from "./types/TimelineLayout.js"; +import {TimelineLayout, TimeLineGrowingMode} from "./types/TimelineLayout.js"; /** * Interface for components that may be slotted inside `ui5-timeline` as items @@ -43,6 +48,7 @@ interface ITimelineItem extends UI5Element, ITabbable { const SHORT_LINE_WIDTH = "ShortLineWidth"; const LARGE_LINE_WIDTH = "LargeLineWidth"; +const GROWING_WITH_SCROLL_DEBOUNCE_RATE = 250; // ms /** * @class @@ -65,8 +71,24 @@ const LARGE_LINE_WIDTH = "LargeLineWidth"; renderer: jsxRenderer, styles: TimelineCss, template: TimelineTemplate, + dependencies: [BusyIndicator, TimelineItem, TimelineGroupItem], +}) + +/** + * Fired when the user presses the `More` button or scrolls to the Timeline's end. + * + * **Note:** The event will be fired if `growing` is set to `Button` or `Scroll`. + * @public + * @since 2.0.0 + */ +@event("load-more", { + bubbles: true, }) + class Timeline extends UI5Element { + eventDetails!: { + "load-more": void, + } /** * Defines the items orientation. * @default "Vertical" @@ -85,6 +107,52 @@ class Timeline extends UI5Element { @property() accessibleName?: string; + /** + * Defines if the component would display a loading indicator over the Timeline. + * + * @default false + * @since 2.0.0 + * @public + */ + @property({ type: Boolean }) + loading = false; + + /** + * Defines the delay in milliseconds, after which the loading indicator will show up for this component. + * @default 1000 + * @public + */ + @property({ type: Number }) + loadingDelay = 1000; + + /** + * Defines whether the Timeline will have growing capability either by pressing a `More` button, + * or via user scroll. In both cases `load-more` event is fired. + * + * Available options: + * + * `Button` - Shows a `More` button at the bottom of the Timeline, pressing of which triggers the `load-more` event. + * + * `Scroll` - The `load-more` event is triggered when the user scrolls to the bottom of the Timeline; + * + * `None` (default) - The growing is off. + * + * **Restrictions:** `growing="Scroll"` is not supported for Internet Explorer, + * and the component will fallback to `growing="Button"`. + * @default "None" + * @since 2.0.0 + * @public + */ + @property() + growing: `${TimeLineGrowingMode}` = "None"; + + /** + * Defines the active state of the `More` button. + * @private + */ + @property({ type: Boolean }) + _loadMoreActive = false; + /** * Determines the content of the `ui5-timeline`. * @public @@ -96,6 +164,9 @@ class Timeline extends UI5Element { static i18nBundle: I18nBundle; _itemNavigation: ItemNavigation; + growingIntersectionObserver?: IntersectionObserver | null; + timeLineEndObserved: boolean; + initialIntersection: boolean; constructor() { super(); @@ -103,6 +174,12 @@ class Timeline extends UI5Element { this._itemNavigation = new ItemNavigation(this, { getItemsCallback: () => this._navigatableItems, }); + + this.timeLineEndObserved = false; + + // Indicates the Timeline bottom most part has been detected by the IntersectionObserver + // for the first time. + this.initialIntersection = true; } get ariaLabel() { @@ -111,6 +188,116 @@ class Timeline extends UI5Element { : Timeline.i18nBundle.getText(TIMELINE_ARIA_LABEL); } + get growsOnScroll(): boolean { + return this.growing === TimeLineGrowingMode.Scroll; + } + + get timeLineEndDOM(): Element { + return this.shadowRoot!.querySelector(".ui5-time-line-end-marker")!; + } + + get growingButtonIcon() { + return this.layout === TimelineLayout.Horizontal ? "process" : "drill-down"; + } + + get moreBtn(): HTMLElement | null { + const domRef = this.getDomRef(); + + if (this.growsWithButton && domRef) { + return domRef.querySelector(`#${this._id}-growingButton`); + } + + return null; + } + + get showBusyIndicatorOverlay() { + return !this.growsWithButton && this.loading; + } + + get growsWithButton(): boolean { + return this.growing === TimeLineGrowingMode.Button; + } + + onAfterRendering() { + if (this.growsOnScroll) { + this.observeTimeLineEnd(); + } + } + + onEnterDOM() { + this.growingIntersectionObserver = this.getIntersectionObserver(); + } + + onExitDOM() { + this.growingIntersectionObserver!.disconnect(); + this.growingIntersectionObserver = null; + this.timeLineEndObserved = false; + } + + observeTimeLineEnd() { + if (!this.timeLineEndObserved) { + this.getIntersectionObserver().observe(this.timeLineEndDOM); + this.timeLineEndObserved = true; + } + } + + getIntersectionObserver(): IntersectionObserver { + if (!this.growingIntersectionObserver) { + this.growingIntersectionObserver = new IntersectionObserver(this.onInteresection.bind(this), { + root: document, + threshold: 1.0, + }); + } + + return this.growingIntersectionObserver; + } + + onInteresection(entries: Array) { + if (this.initialIntersection) { + this.initialIntersection = false; + return; + } + + if (entries.some(entry => entry.isIntersecting)) { + debounce(this.loadMore.bind(this), GROWING_WITH_SCROLL_DEBOUNCE_RATE); + } + } + + loadMore() { + this.fireDecoratorEvent("load-more"); + } + + _onLoadMoreKeydown(e: KeyboardEvent) { + if (isSpace(e)) { + e.preventDefault(); + this._loadMoreActive = true; + } + + if (isEnter(e)) { + this._onLoadMoreClick(); + this._loadMoreActive = true; + } + } + + _onLoadMoreKeyup(e: KeyboardEvent) { + if (isSpace(e)) { + this._onLoadMoreClick(); + } + this._loadMoreActive = false; + } + + onInvalidation(change: ChangeInfo) { + console.error(change) + if (change.type === "property" && change.name === "growing") { + this.timeLineEndObserved = false; + this.getIntersectionObserver().disconnect(); + } + } + + _onLoadMoreClick() { + this.fireDecoratorEvent("load-more"); + } + _onfocusin(e: FocusEvent) { let target = e.target as ITimelineItem | ToggleButton; diff --git a/packages/fiori/src/TimelineGroupItem.ts b/packages/fiori/src/TimelineGroupItem.ts index b73bced44a6f..ab28425ed51e 100644 --- a/packages/fiori/src/TimelineGroupItem.ts +++ b/packages/fiori/src/TimelineGroupItem.ts @@ -4,7 +4,7 @@ import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; -import TimelineLayout from "./types/TimelineLayout.js"; +import {TimelineLayout} from "./types/TimelineLayout.js"; import type { ITimelineItem } from "./Timeline.js"; import TimelineGroupItemTemplate from "./TimelineGroupItemTemplate.js"; diff --git a/packages/fiori/src/TimelineItem.ts b/packages/fiori/src/TimelineItem.ts index 352e6a683249..e35dd3d5d23d 100644 --- a/packages/fiori/src/TimelineItem.ts +++ b/packages/fiori/src/TimelineItem.ts @@ -7,8 +7,8 @@ import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; import type Link from "@ui5/webcomponents/dist/Link.js"; import type { ITimelineItem } from "./Timeline.js"; import TimelineItemTemplate from "./TimelineItemTemplate.js"; -import type TimelineLayout from "./types/TimelineLayout.js"; // Styles +import {TimelineLayout} from "./types/TimelineLayout.js"; import TimelineItemCss from "./generated/themes/TimelineItem.css.js"; /** diff --git a/packages/fiori/src/TimelineItemTemplate.tsx b/packages/fiori/src/TimelineItemTemplate.tsx index cb90a7933248..4f43b14221a2 100644 --- a/packages/fiori/src/TimelineItemTemplate.tsx +++ b/packages/fiori/src/TimelineItemTemplate.tsx @@ -1,7 +1,7 @@ import type TimelineItem from "./TimelineItem.js"; import Link from "@ui5/webcomponents/dist/Link.js"; import Icon from "@ui5/webcomponents/dist/Icon.js"; -import TimelineLayout from "./types/TimelineLayout.js"; +import { TimelineLayout } from "./types/TimelineLayout.js"; export default function TimelineItemTemplate(this: TimelineItem) { return ( diff --git a/packages/fiori/src/TimelineTemplate.tsx b/packages/fiori/src/TimelineTemplate.tsx index 6550273dc5c8..8a5fdd4c8e62 100644 --- a/packages/fiori/src/TimelineTemplate.tsx +++ b/packages/fiori/src/TimelineTemplate.tsx @@ -1,4 +1,6 @@ -import type Timeline from "./Timeline.js"; +import Button from "@ui5/webcomponents/dist/Button.js"; +import Timeline from "./Timeline.js"; +import BusyIndicator from "@ui5/webcomponents/dist/BusyIndicator.js"; export default function TimelineTemplate(this: Timeline) { return ( @@ -6,15 +8,53 @@ export default function TimelineTemplate(this: Timeline) { onFocusIn={this._onfocusin} onKeyDown={this._onkeydown}> -
-
    - {this.items.map(item => -
  • - -
  • - )} -
-
+
+
    + {this.items.map(item => +
  • + +
  • + )} + { this.growsWithButton && moreRow.call(this)} + { this.growsOnScroll && endRow.call(this)} +
+
+ ); } + +function moreRow(this: Timeline) { + return ( +
  • +
    + +
    + {this.loading && + + + } +
  • + ) +} + +function endRow(this: Timeline) { + return ( + + ) +} \ No newline at end of file diff --git a/packages/fiori/src/themes/Timeline.css b/packages/fiori/src/themes/Timeline.css index 757a68c70f71..a061896d5f3e 100644 --- a/packages/fiori/src/themes/Timeline.css +++ b/packages/fiori/src/themes/Timeline.css @@ -14,6 +14,21 @@ padding: 0; } +:host([layout="Horizontal"]) .ui5-timeline-list-item.ui5-timeline-list-growing { + display: flex; + flex-direction: column; + justify-content: center; +} +:host([layout="Vertical"]) .ui5-timeline-list-item.ui5-timeline-list-growing { + display: flex; + flex-direction: row; + justify-content: center; +} + +:host .ui5-time-line-end-marker { + margin: -1px; +} + :host([layout="Vertical"]) .ui5-timeline-list { display: flex; flex-direction: column; diff --git a/packages/fiori/src/types/TimelineLayout.ts b/packages/fiori/src/types/TimelineLayout.ts index 8076d8dd0ad4..182c03c2a489 100644 --- a/packages/fiori/src/types/TimelineLayout.ts +++ b/packages/fiori/src/types/TimelineLayout.ts @@ -17,4 +17,28 @@ enum TimelineLayout { Horizontal= "Horizontal", } -export default TimelineLayout; +enum TimeLineGrowingMode { + /** + * Component `load-more` is fired + * upon pressing a "More" button at the bottom. + * @public + */ + Button = "Button", + + /** + * Component `load-more` is fired upon scroll. + * @public + */ + Scroll = "Scroll", + + /** + * Component growing is not enabled. + * @public + */ + None = "None", +} + +export { + TimelineLayout, + TimeLineGrowingMode +} diff --git a/packages/fiori/test/pages/Timeline.html b/packages/fiori/test/pages/Timeline.html index 15e6abfca042..458dad3f60fe 100644 --- a/packages/fiori/test/pages/Timeline.html +++ b/packages/fiori/test/pages/Timeline.html @@ -141,9 +141,9 @@

    Basic Timeline - Vertical With groups

    20.02.2017 11:30 - + - + 20.02.2017 11:30 @@ -228,7 +228,7 @@

    Basic Timeline - Horizontal

    Advanced Timeline - Horizontal With Groups and Diverse Components

    - +
    Discussion about upcoming projects
    @@ -294,6 +294,28 @@

    Advanced Timeline - Horizontal With Groups and Diverse Components

    document.getElementById("test-item").addEventListener("ui5-name-click", function (event) { result.innerHTML = event.target.getAttribute("name"); }); + + const growing = document.getElementById("horizontalWithGrpsGrowing"); + growing.addEventListener("load-more", (e) => { + const newGroup = document.createElement("ui5-timeline-group-item"); + + newGroup.setAttribute("group-name", "test"); + + for (let i = 0; i < 10; i++) { + newGroup.innerHTML += ` + + + + + + +
    Review project deliverables
    +
    + `; + growing.appendChild(newGroup); + } + + }); \ No newline at end of file From e7c526917326086b7abd3688a9e8a049aba6cdf4 Mon Sep 17 00:00:00 2001 From: Todor Stoyanov Date: Mon, 20 Jan 2025 15:28:13 +0200 Subject: [PATCH 2/7] Correction based on code review suggestions. --- packages/fiori/cypress/specs/Timeline.cy.ts | 247 ++++++++++++++++++ packages/fiori/src/Timeline.ts | 32 ++- packages/fiori/src/TimelineGroupItem.ts | 2 +- packages/fiori/src/TimelineItem.ts | 2 +- packages/fiori/src/TimelineItemTemplate.tsx | 2 +- packages/fiori/src/TimelineTemplate.tsx | 16 +- packages/fiori/src/themes/Timeline.css | 4 +- .../fiori/src/types/TimelineGrowingMode.ts | 23 ++ packages/fiori/src/types/TimelineLayout.ts | 26 +- packages/fiori/test/pages/Timeline.html | 114 ++++---- 10 files changed, 354 insertions(+), 114 deletions(-) create mode 100644 packages/fiori/cypress/specs/Timeline.cy.ts create mode 100644 packages/fiori/src/types/TimelineGrowingMode.ts diff --git a/packages/fiori/cypress/specs/Timeline.cy.ts b/packages/fiori/cypress/specs/Timeline.cy.ts new file mode 100644 index 000000000000..729125086305 --- /dev/null +++ b/packages/fiori/cypress/specs/Timeline.cy.ts @@ -0,0 +1,247 @@ +import { html } from "lit"; +import "../../src/Timeline.js"; +import Timeline from "../../src/Timeline.js"; + +const sample = html` + + + + + MR SOF02 2.43 + + + Online meeting + + + +`; + +const sampleWithSingleItem = html` + + + +`; + +const groupSample = html` + + + + Morning event + + + Good morning + + + 20.02.2017 11:30 + + + + 20.02.2017 11:30 + + + + + + + + + + + +`; + +describe("Timeline general interaction", () => { + it("should fire name-click event on a normal item name", () => { + cy.mount(sample); + + cy.get("[ui5-timeline]") + .as("timeline") + .then($item => { + $item.get(0).addEventListener("name-click", cy.stub().as("clicked")); + }); + + cy.get("ui5-timeline-item") + .shadow() + .find("ui5-link") + .click(); + + cy.get("@clicked").should("have.been.calledOnce"); + }); + + it("setting accessible-name applied on the host element is reflected on the ul tag", () => { + cy.mount(sample); + cy.get("[ui5-timeline]") + .shadow() + .find("ul") + .should("have.attr", "aria-label", "Timeline vertical"); + }); + + it("Item within Timeline Item is rendered", () => { + cy.mount(sampleWithSingleItem); + cy.get("[ui5-timeline]") + .find("#testTimelineItem") + .shadow() + .find(".ui5-tli-bubble") + .find(".ui5-tli-desc") + .should("exist"); + }); +}); + +describe("Timeline with group items interactions", () => { + it("Group items are rendered", () => { + cy.mount(groupSample); + + cy.get("[ui5-timeline]") + .find("[ui5-timeline-group-item][group-name='Events']") + .as("groupItem"); + + cy.get("@groupItem") + .eq(0) + .find("ui5-timeline-item") + .should("have.length", 4); + }); + + it("Group items are collapsed on button click", () => { + cy.mount(groupSample); + + cy.get("[ui5-timeline]") + .find("[ui5-timeline-group-item][group-name='Events']") + .as("currentGroup"); + + cy.get("@currentGroup") + .eq(0) + .shadow() + .find("[ui5-toggle-button]") + .as("currentGroupButton"); + + cy.get("@currentGroupButton") + .realClick(); + + cy.realPress("Tab"); + + cy.get("[ui5-timeline]") + .find("[ui5-timeline-group-item][group-name='Meetings']") + .as("nextGroup"); + + cy.get("@nextGroup") + .eq(0) + .shadow() + .find("[ui5-toggle-button]") + .should("be.focused"); + }); + + it("Group items are navigable", () => { + cy.mount(groupSample); + + cy.get("[ui5-timeline]") + .find("[ui5-timeline-group-item][group-name='Events']") + .eq(0) + .as("currentGroup"); + + cy.realPress("Tab"); + cy.realPress("ArrowDown"); + cy.realPress("ArrowDown"); + cy.realPress("ArrowDown"); + cy.realPress("ArrowUp"); + + + cy.get("@currentGroup") + .find("ui5-timeline-item") + .eq(1) + .should("be.focused"); + }); + + it("Group can be collapsed/expanded using keyboard", () => { + cy.mount(groupSample); + + cy.get("[ui5-timeline]") + .find("[ui5-timeline-group-item][group-name='Events']") + .as("currentGroup"); + + cy.get("@currentGroup") + .eq(0) + .shadow() + .find("[ui5-toggle-button]") + .as("currentGroupButton"); + + cy.get("@currentGroupButton") + .realClick(); + + cy.realPress("Enter"); + + cy.get("@currentGroup") + .eq(0) + .should("not.have.attr", "collapsed"); + + cy.realPress("Space"); + + cy.get("@currentGroup") + .eq(0) + .should("have.attr", "collapsed"); + }); +}); + +describe("Timeline with growing mode", () => { + it("tests 'loadMore' event fired upon infinite scroll", () => { // 8 + cy.mount(html` +
    + + + + + + + +
    `); + + cy.get("[ui5-timeline]") + .as("timeline"); + + cy.get("@timeline") + .then(timeline => { + timeline.get(0).addEventListener("ui5-load-more", cy.stub().as("loadMore")); + }) + cy.get("#scroll-container") + .scrollTo("bottom", { duration: 100 }); + + cy.get("@loadMore") + .should("have.been.calledOnce"); + }); + + it("Arrow down and up navigation between last item and growing button", () => { + cy.mount(html` + + + + + + `); + + cy.get("[ui5-timeline]") + .as("timeline"); + + cy.get("@timeline") + .find("ui5-timeline-item") + .last() + .click(); + + cy.get("@timeline") + .find("ui5-timeline-item") + .last() + .should("be.focused"); + + cy.realPress("Tab"); + + cy.get("@timeline") + .shadow() + .find("[id$='growing-btn']") + .should("be.focused"); + + cy.realPress("Tab"); + + cy.get("@timeline") + .find("ui5-timeline-item") + .first() + .should("be.focused"); + }); +}); \ No newline at end of file diff --git a/packages/fiori/src/Timeline.ts b/packages/fiori/src/Timeline.ts index 5a5ea267b9d8..07247bf3126e 100644 --- a/packages/fiori/src/Timeline.ts +++ b/packages/fiori/src/Timeline.ts @@ -23,9 +23,15 @@ import TimelineGroupItem from "./TimelineGroupItem.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import type { ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js"; import debounce from "@ui5/webcomponents-base/dist/util/debounce.js"; +import query from "@ui5/webcomponents-base/dist/decorators/query.js"; +import process from "@ui5/webcomponents-icons/dist/process.js"; +import drillDown from "@ui5/webcomponents-icons/dist/drill-down.js"; // Styles import TimelineCss from "./generated/themes/Timeline.css.js"; -import {TimelineLayout, TimeLineGrowingMode} from "./types/TimelineLayout.js"; +import TimelineLayout from "./types/TimelineLayout.js"; +// Mode +import TimeLineGrowingMode from "./types/TimelineGrowingMode.js"; +import type Button from "@ui5/webcomponents/dist/Button.js"; /** * Interface for components that may be slotted inside `ui5-timeline` as items @@ -79,7 +85,7 @@ const GROWING_WITH_SCROLL_DEBOUNCE_RATE = 250; // ms * * **Note:** The event will be fired if `growing` is set to `Button` or `Scroll`. * @public - * @since 2.0.0 + * @since 2.7.0 */ @event("load-more", { bubbles: true, @@ -111,7 +117,7 @@ class Timeline extends UI5Element { * Defines if the component would display a loading indicator over the Timeline. * * @default false - * @since 2.0.0 + * @since 2.7.0 * @public */ @property({ type: Boolean }) @@ -140,7 +146,7 @@ class Timeline extends UI5Element { * **Restrictions:** `growing="Scroll"` is not supported for Internet Explorer, * and the component will fallback to `growing="Button"`. * @default "None" - * @since 2.0.0 + * @since 2.7.0 * @public */ @property() @@ -160,6 +166,9 @@ class Timeline extends UI5Element { @slot({ type: HTMLElement, individualSlots: true, "default": true }) items!: Array; + @query(".ui5-timeline-end-marker") + timeLineEndDOM!: HTMLElement; + @i18n("@ui5/webcomponents-fiori") static i18nBundle: I18nBundle; @@ -192,12 +201,8 @@ class Timeline extends UI5Element { return this.growing === TimeLineGrowingMode.Scroll; } - get timeLineEndDOM(): Element { - return this.shadowRoot!.querySelector(".ui5-time-line-end-marker")!; - } - get growingButtonIcon() { - return this.layout === TimelineLayout.Horizontal ? "process" : "drill-down"; + return this.layout === TimelineLayout.Horizontal ? process : drillDown; } get moreBtn(): HTMLElement | null { @@ -229,8 +234,10 @@ class Timeline extends UI5Element { } onExitDOM() { - this.growingIntersectionObserver!.disconnect(); - this.growingIntersectionObserver = null; + if (this.growingIntersectionObserver) { + this.growingIntersectionObserver.disconnect(); + this.growingIntersectionObserver = null; + } this.timeLineEndObserved = false; } @@ -287,7 +294,6 @@ class Timeline extends UI5Element { } onInvalidation(change: ChangeInfo) { - console.error(change) if (change.type === "property" && change.name === "growing") { this.timeLineEndObserved = false; this.getIntersectionObserver().disconnect(); @@ -299,7 +305,7 @@ class Timeline extends UI5Element { } _onfocusin(e: FocusEvent) { - let target = e.target as ITimelineItem | ToggleButton; + let target = e.target as ITimelineItem | ToggleButton | HTMLElement; if ((target as ITimelineItem).isGroupItem) { target = target.shadowRoot!.querySelector("[ui5-toggle-button]")!; diff --git a/packages/fiori/src/TimelineGroupItem.ts b/packages/fiori/src/TimelineGroupItem.ts index ab28425ed51e..b73bced44a6f 100644 --- a/packages/fiori/src/TimelineGroupItem.ts +++ b/packages/fiori/src/TimelineGroupItem.ts @@ -4,7 +4,7 @@ import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; -import {TimelineLayout} from "./types/TimelineLayout.js"; +import TimelineLayout from "./types/TimelineLayout.js"; import type { ITimelineItem } from "./Timeline.js"; import TimelineGroupItemTemplate from "./TimelineGroupItemTemplate.js"; diff --git a/packages/fiori/src/TimelineItem.ts b/packages/fiori/src/TimelineItem.ts index e35dd3d5d23d..5d5271869578 100644 --- a/packages/fiori/src/TimelineItem.ts +++ b/packages/fiori/src/TimelineItem.ts @@ -8,7 +8,7 @@ import type Link from "@ui5/webcomponents/dist/Link.js"; import type { ITimelineItem } from "./Timeline.js"; import TimelineItemTemplate from "./TimelineItemTemplate.js"; // Styles -import {TimelineLayout} from "./types/TimelineLayout.js"; +import TimelineLayout from "./types/TimelineLayout.js"; import TimelineItemCss from "./generated/themes/TimelineItem.css.js"; /** diff --git a/packages/fiori/src/TimelineItemTemplate.tsx b/packages/fiori/src/TimelineItemTemplate.tsx index 4f43b14221a2..cb90a7933248 100644 --- a/packages/fiori/src/TimelineItemTemplate.tsx +++ b/packages/fiori/src/TimelineItemTemplate.tsx @@ -1,7 +1,7 @@ import type TimelineItem from "./TimelineItem.js"; import Link from "@ui5/webcomponents/dist/Link.js"; import Icon from "@ui5/webcomponents/dist/Icon.js"; -import { TimelineLayout } from "./types/TimelineLayout.js"; +import TimelineLayout from "./types/TimelineLayout.js"; export default function TimelineItemTemplate(this: TimelineItem) { return ( diff --git a/packages/fiori/src/TimelineTemplate.tsx b/packages/fiori/src/TimelineTemplate.tsx index 8a5fdd4c8e62..030e926bf6b3 100644 --- a/packages/fiori/src/TimelineTemplate.tsx +++ b/packages/fiori/src/TimelineTemplate.tsx @@ -1,5 +1,5 @@ import Button from "@ui5/webcomponents/dist/Button.js"; -import Timeline from "./Timeline.js"; +import type Timeline from "./Timeline.js"; import BusyIndicator from "@ui5/webcomponents/dist/BusyIndicator.js"; export default function TimelineTemplate(this: Timeline) { @@ -15,8 +15,8 @@ export default function TimelineTemplate(this: Timeline) { )} - { this.growsWithButton && moreRow.call(this)} - { this.growsOnScroll && endRow.call(this)} + { this.growsWithButton && moreRow.call(this) } + { this.growsOnScroll && endRow.call(this) }
    @@ -29,10 +29,10 @@ function moreRow(this: Timeline) {
  • -
    -

    Result

    - -
  • Basic Timeline - Vertical

    - + MR SOF02 2.43 @@ -92,14 +88,14 @@

    Basic Timeline - Vertical

    Online meeting - +

    Basic Timeline - Horizontal

    - + MR SOF02 2.43 @@ -129,34 +125,32 @@

    Basic Timeline - Horizontal

    Basic Timeline - Vertical With groups

    - + - Morning event - + Morning event + Good morning - - 20.02.2017 11:30 + + 20.02.2017 11:30 - - - - 20.02.2017 11:30 - + + 20.02.2017 11:30 + - + - - - + + +
    @@ -166,19 +160,19 @@

    Basic Timeline - Vertical With groups

    Basic Timeline - Vertical

    - + Good morning - - 20.02.2017 11:30 - - 20.02.2017 11:30 - - - - - + + 20.02.2017 11:30 + + 20.02.2017 11:30 + + + + +
    @@ -188,18 +182,18 @@

    Basic Timeline - Horizontal With Groups

    - + - + - 20.02.2017 11:30 - - + 20.02.2017 11:30 + + - - - + + +
    @@ -209,18 +203,18 @@

    Basic Timeline - Horizontal With Groups

    Basic Timeline - Horizontal

    - Test item - Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. - - Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. - Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. - - 20.02.2017 11:30 - - - Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. - Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. - + Test item + Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. + Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. + + 20.02.2017 11:30 + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. + Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. +
    @@ -228,7 +222,7 @@

    Basic Timeline - Horizontal

    Advanced Timeline - Horizontal With Groups and Diverse Components

    - +
    Discussion about upcoming projects
    @@ -289,13 +283,7 @@

    Advanced Timeline - Horizontal With Groups and Diverse Components

    diff --git a/packages/website/docs/_components_pages/fiori/Timeline/Timeline.mdx b/packages/website/docs/_components_pages/fiori/Timeline/Timeline.mdx index 0fe7dc4d04e8..e0fc6dbe8266 100644 --- a/packages/website/docs/_components_pages/fiori/Timeline/Timeline.mdx +++ b/packages/website/docs/_components_pages/fiori/Timeline/Timeline.mdx @@ -2,6 +2,7 @@ import Basic from "../../../_samples/fiori/Timeline/Basic/Basic.md"; import Horizontal from "../../../_samples/fiori/Timeline/Horizontal/Horizontal.md"; import InCard from "../../../_samples/fiori/Timeline/InCard/InCard.md"; import WithGroups from "../../../_samples/fiori/Timeline/WithGroups/WithGroups.md"; +import WithGrowing from "../../../_samples/fiori/Timeline/WithGrowing/WithGrowing.md"; <%COMPONENT_OVERVIEW%> @@ -22,4 +23,8 @@ import WithGroups from "../../../_samples/fiori/Timeline/WithGroups/WithGroups.m ### Timeline with Groups - \ No newline at end of file + + +### Timeline with Growing + + \ No newline at end of file diff --git a/packages/website/docs/_samples/fiori/Timeline/WithGrowing/WithGrowing.md b/packages/website/docs/_samples/fiori/Timeline/WithGrowing/WithGrowing.md new file mode 100644 index 000000000000..17798ecc59ab --- /dev/null +++ b/packages/website/docs/_samples/fiori/Timeline/WithGrowing/WithGrowing.md @@ -0,0 +1,4 @@ +import html from '!!raw-loader!./sample.html'; +import js from '!!raw-loader!./main.js'; + + diff --git a/packages/website/docs/_samples/fiori/Timeline/WithGrowing/main.js b/packages/website/docs/_samples/fiori/Timeline/WithGrowing/main.js new file mode 100644 index 000000000000..3213a2205147 --- /dev/null +++ b/packages/website/docs/_samples/fiori/Timeline/WithGrowing/main.js @@ -0,0 +1,35 @@ +import "@ui5/webcomponents/dist/Label.js"; + +import "@ui5/webcomponents-fiori/dist/Timeline.js"; +import "@ui5/webcomponents-fiori/dist/TimelineItem.js"; + +import "@ui5/webcomponents-icons/dist/phone.js"; +import "@ui5/webcomponents-icons/dist/calendar.js"; + +let itemsLoaded = 0; +const itemToLoad = 5; +const growingTimeline = document.querySelector("#growingTimeline"); +const timelineItemTemplate = (index) => { + var timelineItem = document.createElement("ui5-timeline-item"); + timelineItem.titleText = "title text"; + timelineItem.subtitleText = "the subtitle tex goes here " + index; + timelineItem.icon = "calendar"; + return timelineItem; +} + + +const insertItems = (timeline) => { + for (var i = itemsLoaded; i < itemsLoaded + itemToLoad; i++) { + timeline.appendChild(timelineItemTemplate(i)); + } + itemsLoaded+= itemToLoad; +} + +growingTimeline.addEventListener("load-more", (e) => { + growingTimeline.loading = true; + + setTimeout(() => { + insertItems(growingTimeline); + growingTimeline.loading = false; + }, 1500); +}); \ No newline at end of file diff --git a/packages/website/docs/_samples/fiori/Timeline/WithGrowing/sample.html b/packages/website/docs/_samples/fiori/Timeline/WithGrowing/sample.html new file mode 100644 index 000000000000..77fc3f287e74 --- /dev/null +++ b/packages/website/docs/_samples/fiori/Timeline/WithGrowing/sample.html @@ -0,0 +1,32 @@ + + + + + + + + Sample + + + + +
    + + + + + + MR SOF02 2.43 + + + + Online meeting + + +
    + + + + + + From 1fdfb96bcc9c0c151d78dc5fa79a8968003d2819 Mon Sep 17 00:00:00 2001 From: Todor Stoyanov Date: Fri, 24 Jan 2025 14:10:34 +0200 Subject: [PATCH 5/7] fix: correction based on code review suggestions --- packages/fiori/src/Timeline.ts | 54 +++--- packages/fiori/src/TimelineTemplate.tsx | 2 + packages/fiori/src/themes/Timeline.css | 11 ++ packages/fiori/test/pages/Timeline.html | 8 +- .../test/pages/TimelineGrowing_Button.html | 169 ++++++++++++++++++ .../test/pages/TimelineGrowing_Scroll.html | 169 ++++++++++++++++++ 6 files changed, 383 insertions(+), 30 deletions(-) create mode 100644 packages/fiori/test/pages/TimelineGrowing_Button.html create mode 100644 packages/fiori/test/pages/TimelineGrowing_Scroll.html diff --git a/packages/fiori/src/Timeline.ts b/packages/fiori/src/Timeline.ts index d8f716317b91..2e5a48f6a5e2 100644 --- a/packages/fiori/src/Timeline.ts +++ b/packages/fiori/src/Timeline.ts @@ -77,7 +77,6 @@ const GROWING_WITH_SCROLL_DEBOUNCE_RATE = 250; // ms renderer: jsxRenderer, styles: TimelineCss, template: TimelineTemplate, - dependencies: [BusyIndicator, TimelineItem, TimelineGroupItem], }) /** @@ -114,7 +113,7 @@ class Timeline extends UI5Element { accessibleName?: string; /** - * Defines if the component would display a loading indicator over the Timeline. + * Defines if the component should display a loading indicator over the Timeline. * * @default false * @since 2.7.0 @@ -174,8 +173,8 @@ class Timeline extends UI5Element { _itemNavigation: ItemNavigation; growingIntersectionObserver?: IntersectionObserver | null; - timeLineEndObserved: boolean; - initialIntersection: boolean; + timeLineEndObserved = false; + initialIntersection = true; constructor() { super(); @@ -183,12 +182,6 @@ class Timeline extends UI5Element { this._itemNavigation = new ItemNavigation(this, { getItemsCallback: () => this._navigatableItems, }); - - this.timeLineEndObserved = false; - - // Indicates the Timeline bottom most part has been detected by the IntersectionObserver - // for the first time. - this.initialIntersection = true; } get ariaLabel() { @@ -216,19 +209,15 @@ class Timeline extends UI5Element { onAfterRendering() { if (this.growsOnScroll) { this.observeTimeLineEnd(); + } else if (this.timeLineEndObserved){ + this.unobserveTimelineEnd(); } - } - onEnterDOM() { this.growingIntersectionObserver = this.getIntersectionObserver(); } onExitDOM() { - if (this.growingIntersectionObserver) { - this.growingIntersectionObserver.disconnect(); - this.growingIntersectionObserver = null; - } - this.timeLineEndObserved = false; + this.unobserveTimelineEnd(); } observeTimeLineEnd() { @@ -238,10 +227,30 @@ class Timeline extends UI5Element { } } + async observeTimelineEnd() { + if (!this.timeLineEndObserved) { + //await renderFinished(); + this.getIntersectionObserver().observe(this.timelineEndDOM!); + this.timeLineEndObserved = true; + } + } + + unobserveTimelineEnd() { + if (this.growingIntersectionObserver) { + this.growingIntersectionObserver.disconnect(); + this.growingIntersectionObserver = null; + this.timeLineEndObserved = false; + } + } + + get timelineEndDOM() { + return this.shadowRoot!.querySelector(".ui5-timeline-end-marker"); + } + getIntersectionObserver(): IntersectionObserver { if (!this.growingIntersectionObserver) { this.growingIntersectionObserver = new IntersectionObserver(this.onIntersection.bind(this), { - root: document, + root: null, threshold: 1.0, }); } @@ -283,19 +292,12 @@ class Timeline extends UI5Element { this._loadMoreActive = false; } - onInvalidation(change: ChangeInfo) { - if (change.type === "property" && change.name === "growing") { - this.timeLineEndObserved = false; - this.getIntersectionObserver().disconnect(); - } - } - _onLoadMoreClick() { this.fireDecoratorEvent("load-more"); } _onfocusin(e: FocusEvent) { - let target = e.target as ITimelineItem | ToggleButton | HTMLElement; + let target = e.target as ITimelineItem | ToggleButton; if ((target as ITimelineItem).isGroupItem) { target = target.shadowRoot!.querySelector("[ui5-toggle-button]")!; diff --git a/packages/fiori/src/TimelineTemplate.tsx b/packages/fiori/src/TimelineTemplate.tsx index 0deee4192a2c..afc72ff670c4 100644 --- a/packages/fiori/src/TimelineTemplate.tsx +++ b/packages/fiori/src/TimelineTemplate.tsx @@ -7,6 +7,7 @@ export default function TimelineTemplate(this: Timeline) {
    +
    +
      {this.items.map(item =>
    • diff --git a/packages/fiori/src/themes/Timeline.css b/packages/fiori/src/themes/Timeline.css index a3de049c7a33..0e12bb56bec1 100644 --- a/packages/fiori/src/themes/Timeline.css +++ b/packages/fiori/src/themes/Timeline.css @@ -76,4 +76,15 @@ position: relative; box-sizing: border-box; padding-inline-end: 0; +} + +:host([layout="Vertical"]) .ui5-timeline-scroll-container { + overflow: auto; + height: 100%; + width: 100%; +} + +:host([growing="Scroll"]) .ui5-timeline-end-marker { + /* Ensure the list-end-marker has a block property to always be stretched and "visible" on the screen */ + display: inline-block; } \ No newline at end of file diff --git a/packages/fiori/test/pages/Timeline.html b/packages/fiori/test/pages/Timeline.html index aa11ab7c5475..33435ca0c5e4 100644 --- a/packages/fiori/test/pages/Timeline.html +++ b/packages/fiori/test/pages/Timeline.html @@ -219,10 +219,10 @@

      Basic Timeline - Horizontal

    -
    +

    Advanced Timeline - Horizontal With Groups and Diverse Components

    -
    - +
    +
    Discussion about upcoming projects
    @@ -307,7 +307,7 @@

    Advanced Timeline - Horizontal With Groups and Diverse Components

    growing.loading = false; } - }, 10000); + }, 1000000); }); diff --git a/packages/fiori/test/pages/TimelineGrowing_Button.html b/packages/fiori/test/pages/TimelineGrowing_Button.html new file mode 100644 index 000000000000..4291e4419ec2 --- /dev/null +++ b/packages/fiori/test/pages/TimelineGrowing_Button.html @@ -0,0 +1,169 @@ + + + + + + + Timeline + + + + + + + + + +
    +

    ui5-timeline

    +
    + +
    +

    Timeline - Vertical

    +
    + + + + MR SOF02 2.43 + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + +
    +

    Basic Timeline - Horizontal

    +
    + + + + MR SOF02 2.43 + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + +
    +
    + +
    + +
    + + + + + + + \ No newline at end of file diff --git a/packages/fiori/test/pages/TimelineGrowing_Scroll.html b/packages/fiori/test/pages/TimelineGrowing_Scroll.html new file mode 100644 index 000000000000..4291e4419ec2 --- /dev/null +++ b/packages/fiori/test/pages/TimelineGrowing_Scroll.html @@ -0,0 +1,169 @@ + + + + + + + Timeline + + + + + + + + + +
    +

    ui5-timeline

    +
    + +
    +

    Timeline - Vertical

    +
    + + + + MR SOF02 2.43 + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + +
    +

    Basic Timeline - Horizontal

    +
    + + + + MR SOF02 2.43 + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + + Online meeting + + +
    +
    + +
    + +
    + + + + + + + \ No newline at end of file From 7bc5a54a986bef2489f2028b1884ec24cc8263cd Mon Sep 17 00:00:00 2001 From: Todor Stoyanov Date: Fri, 24 Jan 2025 15:01:42 +0200 Subject: [PATCH 6/7] fix: correction based on code review suggestions --- packages/fiori/src/Timeline.ts | 16 +- packages/fiori/test/pages/Timeline.html | 535 ++++++++++-------- .../test/pages/TimelineGrowing_Button.html | 97 ++-- .../test/pages/TimelineGrowing_Scroll.html | 97 ++-- .../pages/styles/TimelineGrowing_Button.css | 20 + .../pages/styles/TimelineGrowing_Scroll.css | 21 + 6 files changed, 416 insertions(+), 370 deletions(-) create mode 100644 packages/fiori/test/pages/styles/TimelineGrowing_Button.css create mode 100644 packages/fiori/test/pages/styles/TimelineGrowing_Scroll.css diff --git a/packages/fiori/src/Timeline.ts b/packages/fiori/src/Timeline.ts index 2e5a48f6a5e2..7876cc4166d4 100644 --- a/packages/fiori/src/Timeline.ts +++ b/packages/fiori/src/Timeline.ts @@ -132,15 +132,15 @@ class Timeline extends UI5Element { /** * Defines whether the Timeline will have growing capability either by pressing a `More` button, - * or via user scroll. In both cases `load-more` event is fired. + * or via user scroll. In both cases a `load-more` event is fired. * * Available options: * - * `Button` - Shows a button at the end of the Timeline, pressing which triggers the load-more event. + * `Button` - Displays a button at the end of the Timeline, which when pressed triggers the `load-more` event. * - * `Scroll` - The `load-more` event is triggered when the user scrolls to the bottom of the Timeline; + * `Scroll` -Triggers the `load-more` event when the user scrolls to the bottom of the Timeline. * - * `None` (default) - The growing is off. + * `None` (default) - The growing functionality is off. * * **Restrictions:** `growing="Scroll"` is not supported for Internet Explorer, * and the component will fallback to `growing="Button"`. @@ -180,7 +180,7 @@ class Timeline extends UI5Element { super(); this._itemNavigation = new ItemNavigation(this, { - getItemsCallback: () => this._navigatableItems, + getItemsCallback: () => this._navigableItems, }); } @@ -373,8 +373,8 @@ class Timeline extends UI5Element { updatedTarget = target.shadowRoot!.querySelector("[ui5-toggle-button]")!; } - const nextTargetIndex = isNext ? this._navigatableItems.indexOf(updatedTarget) + 1 : this._navigatableItems.indexOf(updatedTarget) - 1; - const nextTarget = this._navigatableItems[nextTargetIndex]; + const nextTargetIndex = isNext ? this._navigableItems.indexOf(updatedTarget) + 1 : this._navigableItems.indexOf(updatedTarget) - 1; + const nextTarget = this._navigableItems[nextTargetIndex]; if (!nextTarget) { return; @@ -387,7 +387,7 @@ class Timeline extends UI5Element { } } - get _navigatableItems() { + get _navigableItems() { const navigatableItems: Array = []; if (!this.items.length) { diff --git a/packages/fiori/test/pages/Timeline.html b/packages/fiori/test/pages/Timeline.html index 33435ca0c5e4..c67960accbd4 100644 --- a/packages/fiori/test/pages/Timeline.html +++ b/packages/fiori/test/pages/Timeline.html @@ -14,287 +14,342 @@ -
    -

    ui5-timeline

    -
    +
    +

    ui5-timeline

    +
    -
    -
    -

    Timeline within Card Vertical

    -
    - - - - - - - - MR SOF02 2.43 - - - Online meeting - - - -
    -

    Timeline within Card Horizontal

    -
    - - - - - - - - MR SOF02 2.43 - - - Online meeting - - - +
    +
    +

    Timeline within Card Vertical

    +
    + + + + + + + + MR SOF02 2.43 + + + Online meeting + + + +
    +

    Timeline within Card Horizontal

    +
    + + + + + + + + MR SOF02 2.43 + + + Online meeting + + + +
    -
    -
    +
    -
    -

    Basic Timeline - Vertical

    +
    +

    Basic Timeline - Vertical

    - - + + MR SOF02 2.43 - + Online meeting - + Online meeting - + Online meeting - + Online meeting - + Online meeting - + Online meeting - + Online meeting - + Online meeting - + -
    +
    -
    -

    Basic Timeline - Horizontal

    +
    +

    Basic Timeline - Horizontal

    - - + + MR SOF02 2.43 - + Online meeting - + Online meeting - + Online meeting - + Online meeting - + Online meeting - + Online meeting -
    +
    + +
    -
    +
    +

    Basic Timeline - Vertical With groups

    +
    + + + + Morning event + + + Good morning + + + 20.02.2017 11:30 + + + + + 20.02.2017 11:30 + + -
    -

    Basic Timeline - Vertical With groups

    -
    - + + + + + + + + + +
    +
    - - - Morning event - +
    +

    Basic Timeline - Vertical

    +
    + + Good morning - - 20.02.2017 11:30 - - - - - 20.02.2017 11:30 - - - - - - - - - - - - -
    -
    - -
    -

    Basic Timeline - Vertical

    -
    - - - - Good morning - - - 20.02.2017 11:30 - - 20.02.2017 11:30 - - - - - - -
    -
    - -
    -

    Basic Timeline - Horizontal With Groups

    -
    - - - - - - - - 20.02.2017 11:30 - - - - - - - - -
    -
    - -
    -

    Basic Timeline - Horizontal

    -
    - - Test item - Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. - - Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. - Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. - - 20.02.2017 11:30 - - - Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. - Lorem ipsum dolor sit amet consectetur adipisicing elit. Ad sequi magnam nam sed numquam. - - -
    -
    - -
    -

    Advanced Timeline - Horizontal With Groups and Diverse Components

    -
    - - - -
    Discussion about upcoming projects
    - Meeting Link -
    - - Important -
    Topics: UI5 Basics, Advanced Concepts
    - Workshop Details -
    - - -
    Speaker: Sarah Kerrigan
    - Join Webinar -
    -
    - - - - - - - -
    Quarterly planning session
    -
    - - -
    Reviewing the budget for Q1
    -
    - - -
    Manager: John Smith
    - -
    -
    - - - High Priority -
    Task progress update
    -
    - - Deadline -
    Upload final project documents
    - Submit -
    - - - - - - -
    Review project deliverables
    -
    -
    -
    -
    -
    -
    - - + + +
    +
    + + \ No newline at end of file diff --git a/packages/fiori/test/pages/TimelineGrowing_Button.html b/packages/fiori/test/pages/TimelineGrowing_Button.html index 4291e4419ec2..c77e312d0178 100644 --- a/packages/fiori/test/pages/TimelineGrowing_Button.html +++ b/packages/fiori/test/pages/TimelineGrowing_Button.html @@ -9,19 +9,19 @@ - + - +
    -

    ui5-timeline

    +

    Timeline growing "Button"

    Timeline - Vertical

    -
    - +
    + Timeline - Vertical icon="calendar">
    -

    Basic Timeline - Horizontal

    -
    - + +

    Timeline - Horizontal

    +
    + Basic Timeline - Horizontal const timelineVertical = document.getElementById("timelineVertical"); const timelineHorizontal = document.getElementById("timelineHorizontal"); - const growing = document.getElementById("horizontalWithGroupsGrowing"); - - // const addElement = (timeline) => { - // timeline.loading = true - - // setTimeout(() => { - // const newGroup = document.createElement("ui5-timeline-group-item"); - - // newGroup.setAttribute("group-name", "test"); - - // for (let i = 0; i < 10; i++) { - // newGroup.innerHTML += ` - // - // - // - // - // - // - //
    Review project deliverables
    - //
    - // `; - // growing.appendChild(newGroup); - // growing.loading = false; - // } - - // }, 1000000); - // } - // growing.addEventListener("load-more", (e) => { - // growing.loading = true - - // setTimeout(() => { - // const newGroup = document.createElement("ui5-timeline-group-item"); - - // newGroup.setAttribute("group-name", "test"); - - // for (let i = 0; i < 10; i++) { - // newGroup.innerHTML += ` - // - // - // - // - // - // - //
    Review project deliverables
    - //
    - // `; - // growing.appendChild(newGroup); - // growing.loading = false; - // } - - // }, 1000000); - // }); + timelineVertical.addEventListener("load-more", addElement); + timelineHorizontal.addEventListener("load-more", addElement); + + function addElement(e) { + this.loading = true; + + setTimeout(() => { + const newGroup = document.createElement("ui5-timeline-group-item"); + + newGroup.setAttribute("group-name", "test"); + + for (let i = 0; i < 10; i++) { + newGroup.innerHTML += ` + + + + + + +
    Review project deliverables
    +
    + `; + this.appendChild(newGroup); + this.loading = false; + } + + }, 1500); + } diff --git a/packages/fiori/test/pages/TimelineGrowing_Scroll.html b/packages/fiori/test/pages/TimelineGrowing_Scroll.html index 4291e4419ec2..2768e6c6ac49 100644 --- a/packages/fiori/test/pages/TimelineGrowing_Scroll.html +++ b/packages/fiori/test/pages/TimelineGrowing_Scroll.html @@ -9,19 +9,19 @@ - + - +
    -

    ui5-timeline

    +

    Timeline growing "Scroll"

    Timeline - Vertical

    -
    - +
    + Timeline - Vertical icon="calendar">
    -

    Basic Timeline - Horizontal

    -
    - + +

    Timeline - Horizontal

    +
    + Basic Timeline - Horizontal const timelineVertical = document.getElementById("timelineVertical"); const timelineHorizontal = document.getElementById("timelineHorizontal"); - const growing = document.getElementById("horizontalWithGroupsGrowing"); - - // const addElement = (timeline) => { - // timeline.loading = true - - // setTimeout(() => { - // const newGroup = document.createElement("ui5-timeline-group-item"); - - // newGroup.setAttribute("group-name", "test"); - - // for (let i = 0; i < 10; i++) { - // newGroup.innerHTML += ` - // - // - // - // - // - // - //
    Review project deliverables
    - //
    - // `; - // growing.appendChild(newGroup); - // growing.loading = false; - // } - - // }, 1000000); - // } - // growing.addEventListener("load-more", (e) => { - // growing.loading = true - - // setTimeout(() => { - // const newGroup = document.createElement("ui5-timeline-group-item"); - - // newGroup.setAttribute("group-name", "test"); - - // for (let i = 0; i < 10; i++) { - // newGroup.innerHTML += ` - // - // - // - // - // - // - //
    Review project deliverables
    - //
    - // `; - // growing.appendChild(newGroup); - // growing.loading = false; - // } - - // }, 1000000); - // }); + timelineVertical.addEventListener("load-more", addElement); + timelineHorizontal.addEventListener("load-more", addElement); + + function addElement(e) { + this.loading = true; + + setTimeout(() => { + const newGroup = document.createElement("ui5-timeline-group-item"); + + newGroup.setAttribute("group-name", "test"); + + for (let i = 0; i < 10; i++) { + newGroup.innerHTML += ` + + + + + + +
    Review project deliverables
    +
    + `; + this.appendChild(newGroup); + this.loading = false; + } + + }, 1500); + } diff --git a/packages/fiori/test/pages/styles/TimelineGrowing_Button.css b/packages/fiori/test/pages/styles/TimelineGrowing_Button.css new file mode 100644 index 000000000000..b709ae8f2e5d --- /dev/null +++ b/packages/fiori/test/pages/styles/TimelineGrowing_Button.css @@ -0,0 +1,20 @@ +.timeline_auto { + padding-inline-start: 2rem; +} + +.header { + display: flex; + justify-content: center; +} + +.timeline-vertical-container { + max-height: 25rem; + overflow: auto; + margin-bottom: 5rem; + display: flex; +} + +.timeline-horizontal-container { + max-width: 50%; + overflow: auto; +} diff --git a/packages/fiori/test/pages/styles/TimelineGrowing_Scroll.css b/packages/fiori/test/pages/styles/TimelineGrowing_Scroll.css new file mode 100644 index 000000000000..4c93bc206c4e --- /dev/null +++ b/packages/fiori/test/pages/styles/TimelineGrowing_Scroll.css @@ -0,0 +1,21 @@ +.timeline_auto { + padding-inline-start: 2rem; +} + +.header { + display: flex; + justify-content: center; +} + +.timeline-vertical-container { + max-height: 25rem; + overflow: auto; + margin-bottom: 5rem; + display: flex; +} + +.timeline-horizontal-container { + max-width: 50%; + overflow: auto; +} + From 7abb288a63678a34a5b6942e766f9b6121921216 Mon Sep 17 00:00:00 2001 From: Todor Stoyanov Date: Fri, 24 Jan 2025 16:09:18 +0200 Subject: [PATCH 7/7] fix: clear lint errors --- packages/fiori/cypress/specs/Timeline.cy.ts | 14 ++- packages/fiori/src/Timeline.ts | 10 +-- packages/fiori/src/TimelineItem.ts | 2 +- packages/fiori/src/TimelineTemplate.tsx | 16 ++-- .../fiori/src/types/TimelineGrowingMode.ts | 3 +- packages/fiori/test/specs/Timeline.spec.js | 88 ------------------- 6 files changed, 19 insertions(+), 114 deletions(-) delete mode 100644 packages/fiori/test/specs/Timeline.spec.js diff --git a/packages/fiori/cypress/specs/Timeline.cy.ts b/packages/fiori/cypress/specs/Timeline.cy.ts index 729125086305..5c25fd5d8c16 100644 --- a/packages/fiori/cypress/specs/Timeline.cy.ts +++ b/packages/fiori/cypress/specs/Timeline.cy.ts @@ -1,6 +1,5 @@ import { html } from "lit"; -import "../../src/Timeline.js"; -import Timeline from "../../src/Timeline.js"; +import type Timeline from "../../src/Timeline.js"; const sample = html` @@ -24,7 +23,6 @@ const sampleWithSingleItem = html` const groupSample = html` - Morning event @@ -144,7 +142,6 @@ describe("Timeline with group items interactions", () => { cy.realPress("ArrowDown"); cy.realPress("ArrowUp"); - cy.get("@currentGroup") .find("ui5-timeline-item") .eq(1) @@ -176,8 +173,8 @@ describe("Timeline with group items interactions", () => { cy.realPress("Space"); cy.get("@currentGroup") - .eq(0) - .should("have.attr", "collapsed"); + .eq(0) + .should("have.attr", "collapsed"); }); }); @@ -200,7 +197,8 @@ describe("Timeline with growing mode", () => { cy.get("@timeline") .then(timeline => { timeline.get(0).addEventListener("ui5-load-more", cy.stub().as("loadMore")); - }) + }); + cy.get("#scroll-container") .scrollTo("bottom", { duration: 100 }); @@ -244,4 +242,4 @@ describe("Timeline with growing mode", () => { .first() .should("be.focused"); }); -}); \ No newline at end of file +}); diff --git a/packages/fiori/src/Timeline.ts b/packages/fiori/src/Timeline.ts index 7876cc4166d4..f166291477dd 100644 --- a/packages/fiori/src/Timeline.ts +++ b/packages/fiori/src/Timeline.ts @@ -5,7 +5,7 @@ import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; -import BusyIndicator from "@ui5/webcomponents/dist/BusyIndicator.js"; +import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js"; import { isTabNext, isTabPrevious, @@ -18,10 +18,7 @@ import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation import NavigationMode from "@ui5/webcomponents-base/dist/types/NavigationMode.js"; import { TIMELINE_ARIA_LABEL } from "./generated/i18n/i18n-defaults.js"; import TimelineTemplate from "./TimelineTemplate.js"; -import TimelineItem from "./TimelineItem.js"; -import TimelineGroupItem from "./TimelineGroupItem.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; -import type { ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js"; import debounce from "@ui5/webcomponents-base/dist/util/debounce.js"; import query from "@ui5/webcomponents-base/dist/decorators/query.js"; import process from "@ui5/webcomponents-icons/dist/process.js"; @@ -31,7 +28,6 @@ import TimelineCss from "./generated/themes/Timeline.css.js"; import TimelineLayout from "./types/TimelineLayout.js"; // Mode import TimeLineGrowingMode from "./types/TimelineGrowingMode.js"; -import type Button from "@ui5/webcomponents/dist/Button.js"; /** * Interface for components that may be slotted inside `ui5-timeline` as items @@ -209,7 +205,7 @@ class Timeline extends UI5Element { onAfterRendering() { if (this.growsOnScroll) { this.observeTimeLineEnd(); - } else if (this.timeLineEndObserved){ + } else if (this.timeLineEndObserved) { this.unobserveTimelineEnd(); } @@ -229,7 +225,7 @@ class Timeline extends UI5Element { async observeTimelineEnd() { if (!this.timeLineEndObserved) { - //await renderFinished(); + await renderFinished(); this.getIntersectionObserver().observe(this.timelineEndDOM!); this.timeLineEndObserved = true; } diff --git a/packages/fiori/src/TimelineItem.ts b/packages/fiori/src/TimelineItem.ts index 5d5271869578..5a49161e6401 100644 --- a/packages/fiori/src/TimelineItem.ts +++ b/packages/fiori/src/TimelineItem.ts @@ -8,7 +8,7 @@ import type Link from "@ui5/webcomponents/dist/Link.js"; import type { ITimelineItem } from "./Timeline.js"; import TimelineItemTemplate from "./TimelineItemTemplate.js"; // Styles -import TimelineLayout from "./types/TimelineLayout.js"; +import type TimelineLayout from "./types/TimelineLayout.js"; import TimelineItemCss from "./generated/themes/TimelineItem.css.js"; /** diff --git a/packages/fiori/src/TimelineTemplate.tsx b/packages/fiori/src/TimelineTemplate.tsx index afc72ff670c4..d2b6685432e0 100644 --- a/packages/fiori/src/TimelineTemplate.tsx +++ b/packages/fiori/src/TimelineTemplate.tsx @@ -6,9 +6,9 @@ export default function TimelineTemplate(this: Timeline) { return (
    - - +
    - +
    ); } @@ -53,9 +53,9 @@ function moreRow(this: Timeline) { class="ui5-timeline-growing-button-busy-indicator" active> - } + } - ) + ); } function endRow(this: Timeline) { @@ -63,5 +63,5 @@ function endRow(this: Timeline) { - ) -} \ No newline at end of file + ); +} diff --git a/packages/fiori/src/types/TimelineGrowingMode.ts b/packages/fiori/src/types/TimelineGrowingMode.ts index dfc38728d653..de2481b3623c 100644 --- a/packages/fiori/src/types/TimelineGrowingMode.ts +++ b/packages/fiori/src/types/TimelineGrowingMode.ts @@ -1,4 +1,3 @@ - enum TimeLineGrowingMode { /** * Component `load-more` is fired @@ -20,4 +19,4 @@ enum TimeLineGrowingMode { None = "None", } -export default TimeLineGrowingMode; \ No newline at end of file +export default TimeLineGrowingMode; diff --git a/packages/fiori/test/specs/Timeline.spec.js b/packages/fiori/test/specs/Timeline.spec.js deleted file mode 100644 index 28bb92da2a3c..000000000000 --- a/packages/fiori/test/specs/Timeline.spec.js +++ /dev/null @@ -1,88 +0,0 @@ -import { assert } from "chai"; - -describe("Timeline general interaction", () => { - before(async () => { - await browser.url(`test/pages/Timeline.html`); - }); - - it("should fire name-click event on a normal item name", async () => { - const timelineItemName = await browser.$("ui5-timeline-item").shadow$("ui5-link"); - const result = await browser.$("#result"); - - await timelineItemName.click(); - assert.strictEqual(await result.getText(), "Stanislava Baltova", "Click event is fired"); - }); - - it("setting accessible-name applied on the host element is reflected on the ul tag", async () => { - const timeline = await browser.$("#timelineAccName"); - - assert.strictEqual(await timeline.shadow$("ul").getAttribute("aria-label"), "Timeline vertical", "Attribute is reflected"); - }); - - it("Item within Timeline Item is rendered", async () => { - const timeline = await browser.$("#testTimeline"); - const timelineItem = await timeline.$("#testTimelineItem").shadow$("ui5-tli-desc"); - - assert.ok(timelineItem, "Item within Timeline Item is rendered"); - }) -}); - -describe("Timeline with group items interactions", () => { - before(async () => { - await browser.url(`test/pages/Timeline.html`); - }); - - it("Group items are rendered", async () => { - const timeline = await browser.$("#verticalWithGrps"); - const groupItem = await timeline.$$("ui5-timeline-group-item[group-name='Events']"); - const groupItemsLength = await groupItem[0].$$("ui5-timeline-item").length; - - - assert.strictEqual(groupItemsLength, 4, "Group items are rendered"); - }) - - it("Group items are collapsed on button click", async () => { - const timeline = await browser.$("#verticalWithGrps"); - const groupItem = await timeline.$$("ui5-timeline-group-item[group-name='Events']"); - const groupItemButton = await groupItem[0].shadow$("ui5-toggle-button"); - - await groupItemButton.click(); - - await browser.keys("Tab"); - - const nextGroupItem = await timeline.$$("ui5-timeline-group-item[group-name='Meetings']"); - const nextGroupItemButton = nextGroupItem[0].shadow$("ui5-toggle-button"); - - assert.ok(nextGroupItemButton.matches(":focus"), "Items are hidden on group collapse"); - }) - - it("Group items are navigatable", async () => { - const timeline = await browser.$("#verticalWithGrps"); - const groupItem = await timeline.$$("ui5-timeline-group-item[group-name='Events']"); - - await browser.keys("Tab"); - await browser.keys("ArrowDown"); - await browser.keys("ArrowDown"); - await browser.keys("ArrowUp"); - - const secondItemInGroup = await groupItem[0].$$("ui5-timeline-item"); - - assert.ok(secondItemInGroup[1].matches(":focus"), "Group items are navigatable with tab and arrow keys"); - }) - - it("Group can be collapsed/expanded using keyboard", async () => { - const timeline = await browser.$("#verticalWithGrps"); - const groupItem = await timeline.$$("ui5-timeline-group-item[group-name='Meetings']"); - const groupItemButton = await groupItem[0].shadow$("ui5-toggle-button"); - - await groupItemButton.click(); - await browser.keys("Enter"); - - assert.strictEqual(await groupItem[0].hasAttribute("collapsed"), false, "Group can be expanded with keyboard"); - - await browser.keys("Space"); - - assert.strictEqual(await groupItem[0].hasAttribute("collapsed"), true, "Group can be collapsed with keyboard"); - } - ); -})