mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-10 00:27:41 +08:00
fix(item-sliding): refresh sliding behavior when options are added or removed asynchronously (#27572)
Issue number: resolves #25578 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> 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? <!-- Please describe the behavior or changes that are being added by this PR. --> 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 <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->
This commit is contained in:
@ -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<HTMLIonItemOptionElement>(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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -12,6 +12,12 @@
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
|
||||
<style>
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -23,8 +29,18 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-button id="toggle-button" onclick="toggle()">Make async changes</ion-button>
|
||||
<ion-list>
|
||||
<ion-item-sliding>
|
||||
<ion-item-sliding id="async-item">
|
||||
<ion-item-options side="end">
|
||||
<ion-item-option>Option</ion-item-option>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
<ion-item-sliding id="async-options-added">
|
||||
<ion-item>ion-item-options added async</ion-item>
|
||||
</ion-item-sliding>
|
||||
<ion-item-sliding id="async-options-removed">
|
||||
<ion-item>ion-item-options removed async</ion-item>
|
||||
<ion-item-options side="end">
|
||||
<ion-item-option>Option</ion-item-option>
|
||||
</ion-item-options>
|
||||
@ -32,16 +48,40 @@
|
||||
</ion-list>
|
||||
|
||||
<script>
|
||||
const itemSliding = document.querySelector('ion-item-sliding');
|
||||
setTimeout(() => {
|
||||
const item = document.createElement('ion-item');
|
||||
item.innerText = 'Item Sliding Option';
|
||||
itemSliding.appendChild(item);
|
||||
const slidingAsyncItem = document.querySelector('#async-item');
|
||||
const slidingAsyncOptionsAdded = document.querySelector('#async-options-added');
|
||||
const slidingAsyncOptionsRemoved = document.querySelector('#async-options-removed');
|
||||
|
||||
item.onclick = () => {
|
||||
[slidingAsyncOptionsAdded, slidingAsyncOptionsRemoved].forEach((itemSliding) => {
|
||||
itemSliding.querySelector('ion-item').onclick = () => {
|
||||
itemSliding.open('end');
|
||||
};
|
||||
}, 250);
|
||||
});
|
||||
|
||||
/**
|
||||
* Function used rather than setTimeout to ensure ion-item-slidings
|
||||
* are fully inited before making changes. Otherwise some bugs would
|
||||
* not appear or be flaky.
|
||||
*/
|
||||
function toggle() {
|
||||
const asyncItem = document.createElement('ion-item');
|
||||
asyncItem.innerText = 'ion-item added async';
|
||||
slidingAsyncItem.appendChild(asyncItem);
|
||||
|
||||
asyncItem.onclick = () => {
|
||||
slidingAsyncItem.open('end');
|
||||
};
|
||||
|
||||
const asyncOptions = document.createElement('ion-item-options');
|
||||
asyncOptions.setAttribute('side', 'end');
|
||||
asyncOptions.innerHTML = `<ion-item-option>Option</ion-item-option>`;
|
||||
slidingAsyncOptionsAdded.appendChild(asyncOptions);
|
||||
|
||||
slidingAsyncOptionsRemoved.querySelector('ion-item-options').remove();
|
||||
|
||||
const toggleButton = document.querySelector('#toggle-button');
|
||||
toggleButton.classList.add('hidden');
|
||||
}
|
||||
</script>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
@ -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/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user