From e6949afd377c71266e3d5163e8a3c5a3b448ac45 Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Thu, 14 May 2026 17:26:02 -0400 Subject: [PATCH] feat(back-to-top): port pf-v5-back-to-top to pf-v6-back-to-top --- elements/pf-v5-back-to-top/README.md | 32 --- .../demo/always-visible.html | 28 --- .../demo/button-no-text.html | 43 ---- elements/pf-v5-back-to-top/demo/button.html | 52 ----- elements/pf-v5-back-to-top/demo/index.html | 41 ---- elements/pf-v5-back-to-top/demo/label.html | 41 ---- elements/pf-v5-back-to-top/demo/no-text.html | 41 ---- .../demo/scroll-distance.html | 41 ---- .../docs/pf-v5-back-to-top.md | 110 ---------- .../pf-v5-back-to-top/pf-v5-back-to-top.css | 77 ------- .../pf-v5-back-to-top/pf-v5-back-to-top.ts | 195 ------------------ .../test/pf-back-to-top.e2e.ts | 25 --- elements/pf-v6-back-to-top/README.md | 40 ++++ .../demo/always-visible.html | 12 ++ .../pf-v6-back-to-top/demo/button-mode.html | 28 +++ elements/pf-v6-back-to-top/demo/index.html | 28 +++ .../demo/scrollable-container.html | 17 ++ .../pf-v6-back-to-top/pf-v6-back-to-top.css | 88 ++++++++ .../pf-v6-back-to-top/pf-v6-back-to-top.ts | 187 +++++++++++++++++ .../test/pf-back-to-top.spec.ts | 134 +++++++----- 20 files changed, 477 insertions(+), 783 deletions(-) delete mode 100644 elements/pf-v5-back-to-top/README.md delete mode 100644 elements/pf-v5-back-to-top/demo/always-visible.html delete mode 100644 elements/pf-v5-back-to-top/demo/button-no-text.html delete mode 100644 elements/pf-v5-back-to-top/demo/button.html delete mode 100644 elements/pf-v5-back-to-top/demo/index.html delete mode 100644 elements/pf-v5-back-to-top/demo/label.html delete mode 100644 elements/pf-v5-back-to-top/demo/no-text.html delete mode 100644 elements/pf-v5-back-to-top/demo/scroll-distance.html delete mode 100644 elements/pf-v5-back-to-top/docs/pf-v5-back-to-top.md delete mode 100644 elements/pf-v5-back-to-top/pf-v5-back-to-top.css delete mode 100644 elements/pf-v5-back-to-top/pf-v5-back-to-top.ts delete mode 100644 elements/pf-v5-back-to-top/test/pf-back-to-top.e2e.ts create mode 100644 elements/pf-v6-back-to-top/README.md create mode 100644 elements/pf-v6-back-to-top/demo/always-visible.html create mode 100644 elements/pf-v6-back-to-top/demo/button-mode.html create mode 100644 elements/pf-v6-back-to-top/demo/index.html create mode 100644 elements/pf-v6-back-to-top/demo/scrollable-container.html create mode 100644 elements/pf-v6-back-to-top/pf-v6-back-to-top.css create mode 100644 elements/pf-v6-back-to-top/pf-v6-back-to-top.ts rename elements/{pf-v5-back-to-top => pf-v6-back-to-top}/test/pf-back-to-top.spec.ts (65%) diff --git a/elements/pf-v5-back-to-top/README.md b/elements/pf-v5-back-to-top/README.md deleted file mode 100644 index 7b95a9b756..0000000000 --- a/elements/pf-v5-back-to-top/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Back to top - -The **back to top** component is a shortcut that allows users to quickly navigate to the top of a lengthy content page. - - -## Installation -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-v5-back-to-top/pf-v5-back-to-top.js'; -``` - -## Usage - -```html -Back to Top - -``` - -[docs]: https://patternflyelements.org/components/back-to-top diff --git a/elements/pf-v5-back-to-top/demo/always-visible.html b/elements/pf-v5-back-to-top/demo/always-visible.html deleted file mode 100644 index 2064eb4126..0000000000 --- a/elements/pf-v5-back-to-top/demo/always-visible.html +++ /dev/null @@ -1,28 +0,0 @@ -
-

Always visible

- Focusable element (top) -
- -Back to top - - - - diff --git a/elements/pf-v5-back-to-top/demo/button-no-text.html b/elements/pf-v5-back-to-top/demo/button-no-text.html deleted file mode 100644 index af285d7147..0000000000 --- a/elements/pf-v5-back-to-top/demo/button-no-text.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
-

Button No Text

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - - - - - - diff --git a/elements/pf-v5-back-to-top/demo/button.html b/elements/pf-v5-back-to-top/demo/button.html deleted file mode 100644 index 452104c0a9..0000000000 --- a/elements/pf-v5-back-to-top/demo/button.html +++ /dev/null @@ -1,52 +0,0 @@ - - - Accessibility Warning Using the Button/JS variant, implementation must apply click event and focus to the element that is scrolled to. - -
-
-

Button

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - -Back to top - - - - diff --git a/elements/pf-v5-back-to-top/demo/index.html b/elements/pf-v5-back-to-top/demo/index.html deleted file mode 100644 index 8fe24f6e22..0000000000 --- a/elements/pf-v5-back-to-top/demo/index.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-

Default

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - -Back to top - - - - diff --git a/elements/pf-v5-back-to-top/demo/label.html b/elements/pf-v5-back-to-top/demo/label.html deleted file mode 100644 index afbd54ca59..0000000000 --- a/elements/pf-v5-back-to-top/demo/label.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-

Default

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - - - - - - diff --git a/elements/pf-v5-back-to-top/demo/no-text.html b/elements/pf-v5-back-to-top/demo/no-text.html deleted file mode 100644 index ae337f891f..0000000000 --- a/elements/pf-v5-back-to-top/demo/no-text.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-

No Text

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - - - - - - diff --git a/elements/pf-v5-back-to-top/demo/scroll-distance.html b/elements/pf-v5-back-to-top/demo/scroll-distance.html deleted file mode 100644 index c3c0a8ef7a..0000000000 --- a/elements/pf-v5-back-to-top/demo/scroll-distance.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-

Default

-

Focusable element (top)

- Scroll down to end of cyan box, 200px (default). -
-
-Focusable element (bottom) - -Back to top - - - - diff --git a/elements/pf-v5-back-to-top/docs/pf-v5-back-to-top.md b/elements/pf-v5-back-to-top/docs/pf-v5-back-to-top.md deleted file mode 100644 index 23266eea16..0000000000 --- a/elements/pf-v5-back-to-top/docs/pf-v5-back-to-top.md +++ /dev/null @@ -1,110 +0,0 @@ - - -{% renderOverview %} - Back to top button is designed to only be used once per page. - Back to top -{% endrenderOverview %} - -{% band header="Usage" %} - - ### Default - {% htmlexample %}Back to top{% endhtmlexample %} - - ### Label attribute - {% htmlexample %}{% endhtmlexample %} - - ### No text or label attribute - `[aria-label]` attribute defaults to text 'Back to top' - {% htmlexample %} - - - {% endhtmlexample %} - -
- - ### Scrollable Selector - - See [scrollable-selector](#attributes) in Attributes section below for more information. - - {% htmlexample %} -
-
-
- Scroll down to end of cyan box, 400px (default). -
-
- Back to top -
- {% endhtmlexample %} - - ### Scroll Distance - - See [scroll-distance](#attributes) in Attributes section below for more information. - - {% htmlexample %} -
-
-
- Scroll down to end of cyan box, 100px. -
-
- Back to top -
- {% endhtmlexample %} - -
- -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-v5-back-to-top/pf-v5-back-to-top.css b/elements/pf-v5-back-to-top/pf-v5-back-to-top.css deleted file mode 100644 index 9b4960d2f0..0000000000 --- a/elements/pf-v5-back-to-top/pf-v5-back-to-top.css +++ /dev/null @@ -1,77 +0,0 @@ -:host { - display: inline-block; - position: absolute; - /** Right position for back to top */ - right: var(--pf-v5-c-back-to-top--Right, var(--pf-global--spacer--2xl, 3rem)); - /** Bottom position for back to top */ - bottom: var(--pf-v5-c-back-to-top--Bottom, var(--pf-global--spacer--lg, 1.5rem)); -} - -[part="trigger"] { - /** Box shadow for back to top button */ - box-shadow: var(--pf-v5-c-back-to-top--c-button--BoxShadow, var(--pf-global--BoxShadow--lg-bottom, 0 0.75rem 0.75rem -0.5rem rgba(3, 3, 3, 0.18))); - - /** Font size for back to top button */ - --pf-v5-c-button--FontSize: var(--pf-v5-c-back-to-top--c-button--FontSize, var(--pf-global--FontSize--xs, 0.75rem)); - /** Border radius for back to top button */ - --pf-v5-c-button--BorderRadius: var(--pf-v5-c-back-to-top--c-button--BorderRadius, var(--pf-global--BorderRadius--lg, 30em)); - /** Top padding for back to top button */ - --pf-v5-c-button--PaddingTop: var(--pf-v5-c-back-to-top--c-button--PaddingTop, var(--pf-global--spacer--xs, 0.25rem)); - /** Right padding for back to top button */ - --pf-v5-c-button--PaddingRight: var(--pf-v5-c-back-to-top--c-button--PaddingRight, var(--pf-global--spacer--sm, 0.5rem)); - /** Bottom padding for back to top button */ - --pf-v5-c-button--PaddingBottom: var(--pf-v5-c-back-to-top--c-button--PaddingBottom, var(--pf-global--spacer--xs, 0.25rem)); - /** Left padding for back to top button */ - --pf-v5-c-button--PaddingLeft: var(--pf-v5-c-back-to-top--c-button--PaddingLeft, var(--pf-global--spacer--sm, 0.5rem)); -} - -a { - display: inline-flex; - align-items: center; - justify-content: center; - /** Primary color for back to top button */ - color: var(--pf-v5-c-button--m-primary--Color, var(--pf-global--Color--light-100, #fff)); - /** Primary background color for back to top button */ - background-color: var(--pf-v5-c-button--m-primary--BackgroundColor, var(--pf-global--primary-color--100, #06c)); - text-decoration: none; - font-size: var(--pf-v5-c-button--FontSize); - padding: var(--pf-v5-c-button--PaddingTop) var(--pf-v5-c-button--PaddingRight) var(--pf-v5-c-button--PaddingBottom) var(--pf-v5-c-button--PaddingLeft); - border-radius: var(--pf-v5-c-button--BorderRadius); - /** End icon margin left for back to top button */ - gap: var(--pf-v5-c-button__icon--m-end--MarginLeft, var(--pf-global--spacer--xs, 0.25rem)); -} - -a:hover, -a:focus { - --pf-v5-c-button--m-primary--Color: var(--pf-v5-c-button--m-primary--hover--Color, var(--pf-global--Color--light-100, #fff)); - --pf-v5-c-button--m-primary--BackgroundColor: var(--pf-v5-c-button--m-primary--hover--BackgroundColor, var(--pf-global--primary-color--200, #004080)); -} - -[part="trigger"]:is(pf-v5-button):hover, -[part="trigger"]:is(pf-v5-button):focus-within { - --pf-v5-c-button--m-primary--BackgroundColor: var(--pf-v5-c-button--m-primary--hover--BackgroundColor, var(--pf-global--primary-color--200, #004080)); - --pf-v5-c-button--m-primary--Color: var(--pf-v5-c-button--m-primary--hover--Color, var(--pf-global--Color--light-100, #fff)); -} - -[part="trigger"][hidden] { - display: none; -} - -pf-v5-icon { - /* override icon size as default sm variant is incorrect */ - --pf-v5-icon--size: var(--pf-v5-c-button--FontSize); - vertical-align: -0.125rem; -} - -span { - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--pf-v5-c-button__icon--m-end--MarginLeft, var(--pf-global--spacer--xs, 0.25rem)); -} - -@media (min-width: 768px) { - :host { - --pf-v5-c-back-to-top--Bottom: var(--pf-v5-c-back-to-top--md--Bottom, var(--pf-global--spacer--2xl, 3rem)); - } -} diff --git a/elements/pf-v5-back-to-top/pf-v5-back-to-top.ts b/elements/pf-v5-back-to-top/pf-v5-back-to-top.ts deleted file mode 100644 index d4d272868a..0000000000 --- a/elements/pf-v5-back-to-top/pf-v5-back-to-top.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { LitElement, html, isServer, type PropertyValues, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; - -import { Logger } from '@patternfly/pfe-core/controllers/logger.js'; - -import '@patternfly/elements/pf-v5-button/pf-v5-button.js'; -import '@patternfly/elements/pf-v5-icon/pf-v5-icon.js'; - -import styles from './pf-v5-back-to-top.css'; - -/** - * The **back to top** component is a shortcut that allows users to quickly navigate to the top of a lengthy content page. - * @summary A shortcut that allows users to quickly navigate to the top of a lengthy content page. - * @alias Back to Top - */ -@customElement('pf-v5-back-to-top') -export class PfV5BackToTop extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - /** Shorthand for the `icon` slot, the value is icon name */ - @property({ reflect: true }) icon?: string; - - /** Icon set for the `icon` property */ - @property({ attribute: 'icon-set' }) iconSet?: string; - - /** Flag to always show back to top button, defaults to false. */ - @property({ reflect: true, type: Boolean, attribute: 'always-visible' }) alwaysVisible = false; - - /** Element selector to spy on for scrolling. Not passing a selector defaults to spying on window scroll events */ - @property({ reflect: true, attribute: 'scrollable-selector' }) scrollableSelector?: string; - - /** Distance from the top of the scrollable element to trigger the visibility of the back to top button */ - @property({ type: Number, attribute: 'scroll-distance' }) scrollDistance = 400; - - /** Accessible name for the back-to-top link, use when component does not have slotted text */ - @property() label?: string; - - /** Page fragment link to target element, must include hash ex: #top */ - @property({ reflect: true }) href?: string; - - #scrollSpy = false; - - #visible = false; - - #scrollElement?: Element | Window; - - #hasSlottedText = false; - - #logger = new Logger(this); - - get #rootNode(): Document | ShadowRoot | null { - let root = null; - if (isServer) { - return null; - } else if ((root = this.getRootNode()) instanceof Document || root instanceof ShadowRoot) { - return root; - } else { - return document; - } - } - - get #ariaLabel(): string | undefined { - if (this.#hasSlottedText) { - return undefined; - } - return this.label ?? 'Back to top'; - } - - override connectedCallback(): void { - super.connectedCallback(); - this.#addScrollListener(); - } - - override disconnectedCallback(): void { - super.disconnectedCallback(); - this.#removeScrollListener(); - } - - override willUpdate(changed: PropertyValues): void { - if (changed.has('scrollableSelector')) { - this.#addScrollListener(); - } - if (changed.has('alwaysVisible')) { - this.#toggleVisibility(); - } - } - - render(): TemplateResult<1> { - // ensure href has a hash - if (this.href && this.href.charAt(0) !== '#') { - this.href = `#${this.href}`; - this.#logger.warn(`missing hash in href fragment link`); - } - - if (this.href) { - return html` - - - - - - - - - `; - } else { - return html` - - - - - - - - - - - `; - } - } - - #onSlotchange(event: Event) { - const slot = event.currentTarget as HTMLSlotElement; - const nodes = slot.assignedNodes(); - this.#hasSlottedText = nodes.length > 0 ? true : false; - this.requestUpdate(); - } - - #removeScrollListener() { - this.#scrollElement?.removeEventListener('scroll', this.#toggleVisibility); - } - - #addScrollListener() { - this.#removeScrollListener(); - - if (this.scrollableSelector?.trim() === '') { - this.#logger.error(`scrollable-selector attribute cannot be empty`); - return; - } - - this.#scrollSpy = !!this.scrollableSelector; - if (isServer) { - return; - } else if (this.#scrollSpy && this.scrollableSelector) { - const scrollableElement = this.#rootNode?.querySelector?.(this.scrollableSelector); - if (!scrollableElement) { - this.#logger.error(`unable to find element with selector ${this.scrollableSelector}`); - return; - } - this.#scrollElement = scrollableElement; - } else { - this.#scrollElement = window; - } - - this.#scrollElement.addEventListener('scroll', this.#toggleVisibility, { passive: true }); - this.#toggleVisibility(); - } - - #toggleVisibility = () => { - if (this.alwaysVisible) { - this.#visible = true; - this.requestUpdate(); - return; - } - const previousVisibility = this.#visible; - if (this.#scrollElement) { - const scrolled = - (this.#scrollElement instanceof Window) ? - this.#scrollElement.scrollY - : this.#scrollElement.scrollTop; - this.#visible = (scrolled > this.scrollDistance); - if (previousVisibility !== this.#visible) { - this.requestUpdate(); - } - } - }; -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-v5-back-to-top': PfV5BackToTop; - } -} diff --git a/elements/pf-v5-back-to-top/test/pf-back-to-top.e2e.ts b/elements/pf-v5-back-to-top/test/pf-back-to-top.e2e.ts deleted file mode 100644 index d9c618da25..0000000000 --- a/elements/pf-v5-back-to-top/test/pf-back-to-top.e2e.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { test } from '@playwright/test'; -import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; -import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; - -const tagName = 'pf-v5-back-to-top'; - -test.describe(tagName, () => { - test('snapshot', async ({ page }) => { - const componentPage = new PfeDemoPage(page, tagName); - await componentPage.navigate(); - await componentPage.snapshot(); - }); - - test('ssr', async ({ browser }) => { - const fixture = new SSRPage({ - tagName, - browser, - demoDir: new URL('../demo/', import.meta.url), - importSpecifiers: [ - `@patternfly/elements/${tagName}/${tagName}.js`, - ], - }); - await fixture.snapshots(); - }); -}); diff --git a/elements/pf-v6-back-to-top/README.md b/elements/pf-v6-back-to-top/README.md new file mode 100644 index 0000000000..17a2c79c06 --- /dev/null +++ b/elements/pf-v6-back-to-top/README.md @@ -0,0 +1,40 @@ +# Back to top + +`` is a shortcut that allows users to quickly navigate to the top of a lengthy content page. + +## Usage + +```html + +Back to top + + +Back to top + + +Back to top +``` + +## Divergences from React `BackToTop` + +### Not implemented + +| React prop | Notes | +|-------------|--------------------------------------------------------| +| `className` | Not needed; shadow DOM provides encapsulation. | + +### Changed API + +| React prop | Web component | Difference | +|---------------------|-------------------------|---------------------------------------------------------------------------| +| `title` | Default slot | React uses a `title` prop for button text; WC uses slotted content. | +| `isAlwaysVisible` | `always-visible` | Boolean attribute instead of React prop. | +| `scrollableSelector`| `scrollable-selector` | Dash-case attribute for the camelCase property. | + +### Added + +| Web component API | Notes | +|------------------------|---------------------------------------------------------------------------------------| +| `href` | When set, renders as an `` link instead of a ` + `; + } + + #onClick() { + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + this.#scrollElement?.scrollTo({ + top: 0, + behavior: (prefersReducedMotion ? 'instant' : 'smooth'), + }); + } + + #onSlotchange(event: Event) { + const slot = event.currentTarget as HTMLSlotElement; + this.#hasSlottedText = slot.assignedNodes().some( + n => n.nodeType === Node.ELEMENT_NODE || !!n.textContent?.trim(), + ); + this.requestUpdate(); + } + + #removeScrollListener() { + this.#scrollElement?.removeEventListener('scroll', this.#toggleVisibility); + } + + #addScrollListener() { + this.#removeScrollListener(); + + if (this.scrollableSelector?.trim() === '') { + this.#logger.error(`scrollable-selector attribute cannot be empty`); + return; + } + + if (isServer) { + return; + } + + if (this.scrollableSelector) { + const scrollableElement = this.#rootNode?.querySelector?.(this.scrollableSelector); + if (!scrollableElement) { + this.#logger.error(`unable to find element with selector ${this.scrollableSelector}`); + return; + } + this.#scrollElement = scrollableElement; + } else { + this.#scrollElement = window; + } + + this.#scrollElement.addEventListener('scroll', this.#toggleVisibility, { passive: true }); + this.#toggleVisibility(); + } + + #toggleVisibility = () => { + if (this.alwaysVisible) { + this.#visible = true; + this.requestUpdate(); + return; + } + const previousVisibility = this.#visible; + if (this.#scrollElement) { + const scrolled = + (this.#scrollElement instanceof Window) ? + this.#scrollElement.scrollY + : this.#scrollElement.scrollTop; + this.#visible = scrolled > 400; + if (previousVisibility !== this.#visible) { + this.requestUpdate(); + } + } + }; +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v6-back-to-top': PfV6BackToTop; + } +} diff --git a/elements/pf-v5-back-to-top/test/pf-back-to-top.spec.ts b/elements/pf-v6-back-to-top/test/pf-back-to-top.spec.ts similarity index 65% rename from elements/pf-v5-back-to-top/test/pf-back-to-top.spec.ts rename to elements/pf-v6-back-to-top/test/pf-back-to-top.spec.ts index 2524318a49..c7a05ead75 100644 --- a/elements/pf-v5-back-to-top/test/pf-back-to-top.spec.ts +++ b/elements/pf-v6-back-to-top/test/pf-back-to-top.spec.ts @@ -4,44 +4,44 @@ import { setViewport, sendKeys } from '@web/test-runner-commands'; import { allUpdates } from '@patternfly/pfe-tools/test/utils.js'; -import { PfV5BackToTop } from '../pf-v5-back-to-top.js'; +import { PfV6BackToTop } from '../pf-v6-back-to-top.js'; import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; -describe('', function() { +describe('', function() { it('imperatively instantiates', function() { - expect(document.createElement('pf-v5-back-to-top')).to.be.an.instanceof(PfV5BackToTop); + expect(document.createElement('pf-v6-back-to-top')).to.be.an.instanceof(PfV6BackToTop); }); describe('simply instantiating', function() { - let element: PfV5BackToTop; + let element: PfV6BackToTop; beforeEach(async function() { - element = await createFixture(html``); + element = await createFixture(html``); }); it('should upgrade', function() { - const klass = customElements.get('pf-v5-back-to-top'); + const klass = customElements.get('pf-v6-back-to-top'); expect(element) .to.be.an.instanceOf(klass) .and - .to.be.an.instanceOf(PfV5BackToTop); + .to.be.an.instanceOf(PfV6BackToTop); }); }); - describe('when rendered in a viewport with a height smaller then content length', function() { - let element: PfV5BackToTop; + describe('with href (link mode)', function() { + let element: PfV6BackToTop; beforeEach(async function() { await setViewport({ width: 320, height: 640 }); window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); - const container = await createFixture(html` + const container = await createFixture(html`
- Back to top + Back to top
`); - element = container.querySelector('pf-v5-back-to-top')!; + element = container.querySelector('pf-v6-back-to-top')!; await allUpdates(element); }); @@ -50,7 +50,7 @@ describe('', function() { expect(snapshot).to.not.axContainRole('link'); }); - it('should not be accessible', async function() { + it('should not be accessible when hidden', async function() { const snapshot = await a11ySnapshot(); expect(snapshot).to.not.axContainName('Back to top'); }); @@ -62,7 +62,7 @@ describe('', function() { await allUpdates(element); }); - it('should be visible', async function() { + it('should be visible as a link', async function() { expect(await a11ySnapshot()) .to.axContainQuery({ role: 'link', name: 'Back to top' }); }); @@ -84,7 +84,7 @@ describe('', function() { }); }); - describe('when the always visible property is true', function() { + describe('when always-visible is true', function() { beforeEach(async function() { window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); @@ -112,50 +112,66 @@ describe('', function() { }); }); }); + }); + + describe('without href (button mode)', function() { + let element: PfV6BackToTop; - describe('when the scroll distance is set to 1000', function() { + beforeEach(async function() { + await setViewport({ width: 320, height: 640 }); + window.scrollTo({ top: 0, behavior: 'instant' }); + await nextFrame(); + const container = await createFixture(html` +
+
+ Back to top +
+ `); + element = container.querySelector('pf-v6-back-to-top')!; + await allUpdates(element); + }); + + it('should be hidden on init', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot).to.not.axContainRole('button'); + }); + + describe('when scrolled 401px', function() { beforeEach(async function() { - element.scrollDistance = 1000; + window.scrollTo({ top: 401, behavior: 'instant' }); + await nextFrame(); await allUpdates(element); }); - it('should be hidden', async function() { - expect(await a11ySnapshot()).to.not.axContainRole('link'); + it('should be visible as a button', async function() { + expect(await a11ySnapshot()) + .to.axContainQuery({ role: 'button', name: 'Back to top' }); }); - describe('when scrolled 1001px', function() { - beforeEach(async function() { - window.scrollTo({ top: 1001, behavior: 'instant' }); - await nextFrame(); - await allUpdates(element); - }); - - it('should be visible', async function() { - expect(await a11ySnapshot()) - .to.axContainQuery({ role: 'link', name: 'Back to top' }); - }); + it('should be accessible', async function() { + await expect(element).to.be.accessible(); }); }); }); - describe('when rendered in an element with an overflowed height', function() { - let element: PfV5BackToTop; + describe('in an overflowed container with scrollable-selector', function() { + let element: PfV6BackToTop; beforeEach(async function() { window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); - const container = await createFixture(html` + const container = await createFixture(html`
- Back to top + Back to top
`); - element = container.querySelector('pf-v5-back-to-top')!; + element = container.querySelector('pf-v6-back-to-top')!; await allUpdates(element); }); it('should be hidden on init', async function() { - const snapshot = await a11ySnapshot({ selector: 'pf-v5-back-to-top' }); + const snapshot = await a11ySnapshot({ selector: 'pf-v6-back-to-top' }); expect(snapshot?.children).to.not.be.ok; }); @@ -176,20 +192,20 @@ describe('', function() { }); describe('when no text is provided', function() { - let element: PfV5BackToTop; - describe('as a link', function() { + let element: PfV6BackToTop; + beforeEach(async function() { await setViewport({ width: 320, height: 640 }); window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); - const container = await createFixture(html` + const container = await createFixture(html`
- +
`); - element = container.querySelector('pf-v5-back-to-top')!; + element = container.querySelector('pf-v6-back-to-top')!; await allUpdates(element); }); @@ -200,7 +216,7 @@ describe('', function() { await allUpdates(element); }); - it('should have a label of "Back to top"', async function() { + it('should have a default accessible label of "Back to top"', async function() { expect(await a11ySnapshot()) .to.axContainQuery({ role: 'link', name: 'Back to top' }); }); @@ -208,17 +224,19 @@ describe('', function() { }); describe('as a button', function() { + let element: PfV6BackToTop; + beforeEach(async function() { await setViewport({ width: 320, height: 640 }); window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); - const container = await createFixture(html` + const container = await createFixture(html`
- +
`); - element = container.querySelector('pf-v5-back-to-top')!; + element = container.querySelector('pf-v6-back-to-top')!; await allUpdates(element); }); @@ -229,7 +247,7 @@ describe('', function() { await allUpdates(element); }); - it('should have a label of "Back to top"', async function() { + it('should have a default accessible label of "Back to top"', async function() { expect(await a11ySnapshot()) .to.axContainQuery({ role: 'button', name: 'Back to top' }); }); @@ -237,21 +255,21 @@ describe('', function() { }); }); - describe('when a label is provided', function() { - let element: PfV5BackToTop; - + describe('when accessible-label is provided', function() { describe('as a link', function() { + let element: PfV6BackToTop; + beforeEach(async function() { await setViewport({ width: 320, height: 640 }); window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); - const container = await createFixture(html` + const container = await createFixture(html`
- +
`); - element = container.querySelector('pf-v5-back-to-top')!; + element = container.querySelector('pf-v6-back-to-top')!; await allUpdates(element); }); @@ -262,7 +280,7 @@ describe('', function() { await allUpdates(element); }); - it('should have a label of "Return to top"', async function() { + it('should have the custom label', async function() { expect(await a11ySnapshot()) .to.axContainQuery({ role: 'link', name: 'Return to top' }); }); @@ -270,17 +288,19 @@ describe('', function() { }); describe('as a button', function() { + let element: PfV6BackToTop; + beforeEach(async function() { await setViewport({ width: 320, height: 640 }); window.scrollTo({ top: 0, behavior: 'instant' }); await nextFrame(); - const container = await createFixture(html` + const container = await createFixture(html`
- +
`); - element = container.querySelector('pf-v5-back-to-top')!; + element = container.querySelector('pf-v6-back-to-top')!; await allUpdates(element); }); @@ -291,7 +311,7 @@ describe('', function() { await allUpdates(element); }); - it('should have a label of "Return to top"', async function() { + it('should have the custom label', async function() { expect(await a11ySnapshot()) .to.axContainQuery({ role: 'button', name: 'Return to top' }); });