From b2a226ae663695be0666cd862510d8d843c80b9a Mon Sep 17 00:00:00 2001 From: Amanda Johnston <90629384+amandaejohnston@users.noreply.github.com> Date: Tue, 6 Jun 2023 10:24:40 -0500 Subject: [PATCH] fix(item-sliding): refresh sliding behavior when options are added or removed asynchronously (#27572) Issue number: resolves #25578 --------- ## What is the current behavior? If `ion-item-option`s are added or removed from an `ion-item-sliding` asyncronously/after it has initialized, the sliding behavior of the item will not update. If options are added to an item that didn't have any, the item will not be openable. If all options are removed, the item will still be openable, though no options will render. ## What is the new behavior? The item now re-checks its options when it detects that any have been added or removed, using the same utility/behavior as `ion-select`. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information --- .../components/item-sliding/item-sliding.tsx | 19 ++++++- .../item-sliding/test/async/index.html | 56 ++++++++++++++++--- .../test/async/item-sliding.e2e.ts | 27 +++++++-- 3 files changed, 86 insertions(+), 16 deletions(-) diff --git a/core/src/components/item-sliding/item-sliding.tsx b/core/src/components/item-sliding/item-sliding.tsx index 3f671c0b1d..74c854e185 100644 --- a/core/src/components/item-sliding/item-sliding.tsx +++ b/core/src/components/item-sliding/item-sliding.tsx @@ -1,5 +1,6 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; import { Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core'; +import { watchForOptions } from '@utils/watch-options'; import { getIonMode } from '../../global/ionic-global'; import type { Gesture, GestureDetail } from '../../interface'; @@ -47,6 +48,7 @@ export class ItemSliding implements ComponentInterface { private gesture?: Gesture; private contentEl: HTMLElement | null = null; private initialContentScrollY = true; + private mutationObserver?: MutationObserver; @Element() el!: HTMLIonItemSlidingElement; @@ -69,13 +71,19 @@ export class ItemSliding implements ComponentInterface { @Event() ionDrag!: EventEmitter; async connectedCallback() { - this.item = this.el.querySelector('ion-item'); - this.contentEl = findClosestIonContent(this.el); + const { el } = this; + + this.item = el.querySelector('ion-item'); + this.contentEl = findClosestIonContent(el); await this.updateOptions(); + this.mutationObserver = watchForOptions(el, 'ion-item-option', async () => { + await this.updateOptions(); + }); + this.gesture = (await import('../../utils/gesture')).createGesture({ - el: this.el, + el, gestureName: 'item-swipe', gesturePriority: 100, threshold: 5, @@ -99,6 +107,11 @@ export class ItemSliding implements ComponentInterface { if (openSlidingItem === this.el) { openSlidingItem = undefined; } + + if (this.mutationObserver) { + this.mutationObserver.disconnect(); + this.mutationObserver = undefined; + } } /** diff --git a/core/src/components/item-sliding/test/async/index.html b/core/src/components/item-sliding/test/async/index.html index 401e37dc96..032d0ef3a9 100644 --- a/core/src/components/item-sliding/test/async/index.html +++ b/core/src/components/item-sliding/test/async/index.html @@ -12,6 +12,12 @@ + + @@ -23,8 +29,18 @@ + Make async changes - + + + Option + + + + ion-item-options added async + + + ion-item-options removed async Option @@ -32,16 +48,40 @@ diff --git a/core/src/components/item-sliding/test/async/item-sliding.e2e.ts b/core/src/components/item-sliding/test/async/item-sliding.e2e.ts index d91375403e..aa44b11292 100644 --- a/core/src/components/item-sliding/test/async/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/async/item-sliding.e2e.ts @@ -3,14 +3,17 @@ import { configs, test } from '@utils/test/playwright'; configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { test.describe(title('item-sliding: async'), () => { - test('should open even when item is added async', async ({ page }) => { + test.beforeEach(async ({ page }) => { await page.goto(`/src/components/item-sliding/test/async`, config); + const toggleButton = page.locator('#toggle-button'); - const itemEl = page.locator('ion-item'); - const itemSlidingEl = page.locator('ion-item-sliding'); + await toggleButton.click(); + await expect(toggleButton).toHaveClass(/hidden/); // class is added when everything is ready + }); - // Wait for item to be added to DOM - await page.waitForSelector('ion-item'); + test('should open even when ion-item is added async', async ({ page }) => { + const itemSlidingEl = page.locator('#async-item'); + const itemEl = itemSlidingEl.locator('ion-item'); // Click item to open ion-item-sliding await itemEl.click(); @@ -18,5 +21,19 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => // This class is added when the item sliding component is fully open await expect(itemSlidingEl).toHaveClass(/item-sliding-active-slide/); }); + + test('should open when ion-item-options are added async', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/25578', + }); + + const itemSlidingEl = page.locator('#async-options-added'); + const itemEl = itemSlidingEl.locator('ion-item'); + + await itemEl.click(); + + await expect(itemSlidingEl).toHaveClass(/item-sliding-active-slide/); + }); }); });