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 type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||||
import { Component, Element, Event, Host, Method, Prop, State, Watch, h } 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 { getIonMode } from '../../global/ionic-global';
|
||||||
import type { Gesture, GestureDetail } from '../../interface';
|
import type { Gesture, GestureDetail } from '../../interface';
|
||||||
@ -47,6 +48,7 @@ export class ItemSliding implements ComponentInterface {
|
|||||||
private gesture?: Gesture;
|
private gesture?: Gesture;
|
||||||
private contentEl: HTMLElement | null = null;
|
private contentEl: HTMLElement | null = null;
|
||||||
private initialContentScrollY = true;
|
private initialContentScrollY = true;
|
||||||
|
private mutationObserver?: MutationObserver;
|
||||||
|
|
||||||
@Element() el!: HTMLIonItemSlidingElement;
|
@Element() el!: HTMLIonItemSlidingElement;
|
||||||
|
|
||||||
@ -69,13 +71,19 @@ export class ItemSliding implements ComponentInterface {
|
|||||||
@Event() ionDrag!: EventEmitter;
|
@Event() ionDrag!: EventEmitter;
|
||||||
|
|
||||||
async connectedCallback() {
|
async connectedCallback() {
|
||||||
this.item = this.el.querySelector('ion-item');
|
const { el } = this;
|
||||||
this.contentEl = findClosestIonContent(this.el);
|
|
||||||
|
this.item = el.querySelector('ion-item');
|
||||||
|
this.contentEl = findClosestIonContent(el);
|
||||||
|
|
||||||
await this.updateOptions();
|
await this.updateOptions();
|
||||||
|
|
||||||
|
this.mutationObserver = watchForOptions<HTMLIonItemOptionElement>(el, 'ion-item-option', async () => {
|
||||||
|
await this.updateOptions();
|
||||||
|
});
|
||||||
|
|
||||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||||
el: this.el,
|
el,
|
||||||
gestureName: 'item-swipe',
|
gestureName: 'item-swipe',
|
||||||
gesturePriority: 100,
|
gesturePriority: 100,
|
||||||
threshold: 5,
|
threshold: 5,
|
||||||
@ -99,6 +107,11 @@ export class ItemSliding implements ComponentInterface {
|
|||||||
if (openSlidingItem === this.el) {
|
if (openSlidingItem === this.el) {
|
||||||
openSlidingItem = undefined;
|
openSlidingItem = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.mutationObserver) {
|
||||||
|
this.mutationObserver.disconnect();
|
||||||
|
this.mutationObserver = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -12,6 +12,12 @@
|
|||||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@ -23,8 +29,18 @@
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
<ion-button id="toggle-button" onclick="toggle()">Make async changes</ion-button>
|
||||||
<ion-list>
|
<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-options side="end">
|
||||||
<ion-item-option>Option</ion-item-option>
|
<ion-item-option>Option</ion-item-option>
|
||||||
</ion-item-options>
|
</ion-item-options>
|
||||||
@ -32,16 +48,40 @@
|
|||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const itemSliding = document.querySelector('ion-item-sliding');
|
const slidingAsyncItem = document.querySelector('#async-item');
|
||||||
setTimeout(() => {
|
const slidingAsyncOptionsAdded = document.querySelector('#async-options-added');
|
||||||
const item = document.createElement('ion-item');
|
const slidingAsyncOptionsRemoved = document.querySelector('#async-options-removed');
|
||||||
item.innerText = 'Item Sliding Option';
|
|
||||||
itemSliding.appendChild(item);
|
|
||||||
|
|
||||||
item.onclick = () => {
|
[slidingAsyncOptionsAdded, slidingAsyncOptionsRemoved].forEach((itemSliding) => {
|
||||||
|
itemSliding.querySelector('ion-item').onclick = () => {
|
||||||
itemSliding.open('end');
|
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>
|
</script>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ion-app>
|
</ion-app>
|
||||||
|
|||||||
@ -3,14 +3,17 @@ import { configs, test } from '@utils/test/playwright';
|
|||||||
|
|
||||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
test.describe(title('item-sliding: async'), () => {
|
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);
|
await page.goto(`/src/components/item-sliding/test/async`, config);
|
||||||
|
const toggleButton = page.locator('#toggle-button');
|
||||||
|
|
||||||
const itemEl = page.locator('ion-item');
|
await toggleButton.click();
|
||||||
const itemSlidingEl = page.locator('ion-item-sliding');
|
await expect(toggleButton).toHaveClass(/hidden/); // class is added when everything is ready
|
||||||
|
});
|
||||||
|
|
||||||
// Wait for item to be added to DOM
|
test('should open even when ion-item is added async', async ({ page }) => {
|
||||||
await page.waitForSelector('ion-item');
|
const itemSlidingEl = page.locator('#async-item');
|
||||||
|
const itemEl = itemSlidingEl.locator('ion-item');
|
||||||
|
|
||||||
// Click item to open ion-item-sliding
|
// Click item to open ion-item-sliding
|
||||||
await itemEl.click();
|
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
|
// This class is added when the item sliding component is fully open
|
||||||
await expect(itemSlidingEl).toHaveClass(/item-sliding-active-slide/);
|
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