mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
chore: sync
This commit is contained in:
@@ -35,9 +35,9 @@ export interface Animation {
|
||||
progressStep(step: number): Animation;
|
||||
progressEnd(playTo: 0 | 1 | undefined, step: number, dur?: number): Animation;
|
||||
|
||||
from(property: string, value: any): Animation;
|
||||
to(property: string, value: any): Animation;
|
||||
fromTo(property: string, fromValue: any, toValue: any): Animation;
|
||||
from(property: string, value: string | number): Animation;
|
||||
to(property: string, value: string | number): Animation;
|
||||
fromTo(property: string, fromValue: string | number, toValue: string | number): Animation;
|
||||
|
||||
/**
|
||||
* Set the keyframes for the animation.
|
||||
@@ -131,7 +131,7 @@ export interface Animation {
|
||||
* browsers that do not support
|
||||
* the Web Animations API.
|
||||
*/
|
||||
getWebAnimations(): any[];
|
||||
getWebAnimations(): globalThis.Animation[]; // Use the Web Animation interface, not the Ionic Animations interface
|
||||
|
||||
/**
|
||||
* Add a function that performs a
|
||||
|
||||
@@ -600,6 +600,17 @@ export const createAnimation = (animationId?: string): Animation => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Clean up any value coercion before
|
||||
* the user callbacks fire otherwise
|
||||
* they may get stale values. For example,
|
||||
* if someone calls progressStart(0) the
|
||||
* animation may still be reversed.
|
||||
*/
|
||||
forceDurationValue = undefined;
|
||||
forceDirectionValue = undefined;
|
||||
forceDelayValue = undefined;
|
||||
|
||||
onFinishCallbacks.forEach((onFinishCallback) => {
|
||||
return onFinishCallback.c(currentStep, ani);
|
||||
});
|
||||
@@ -848,21 +859,8 @@ export const createAnimation = (animationId?: string): Animation => {
|
||||
}
|
||||
}
|
||||
|
||||
if (playTo !== undefined) {
|
||||
onFinish(
|
||||
() => {
|
||||
forceDurationValue = undefined;
|
||||
forceDirectionValue = undefined;
|
||||
forceDelayValue = undefined;
|
||||
},
|
||||
{
|
||||
oneTimeCallback: true,
|
||||
}
|
||||
);
|
||||
|
||||
if (!parentAnimation) {
|
||||
play();
|
||||
}
|
||||
if (playTo !== undefined && !parentAnimation) {
|
||||
play();
|
||||
}
|
||||
|
||||
return ani;
|
||||
|
||||
@@ -4,6 +4,23 @@ import { processKeyframes } from '../animation-utils';
|
||||
import { getTimeGivenProgression } from '../cubic-bezier';
|
||||
|
||||
describe('Animation Class', () => {
|
||||
describe('progressEnd callbacks', () => {
|
||||
test('coerced state should be reset before onFinish runs', (done) => {
|
||||
const el = document.createElement('div');
|
||||
const animation = createAnimation()
|
||||
.addElement(el)
|
||||
.fromTo('transform', 'translateX(0px)', 'translateX(100px)')
|
||||
.duration(50);
|
||||
|
||||
animation
|
||||
.onFinish(() => {
|
||||
expect(animation.getDirection()).toBe('normal');
|
||||
expect(animation.getDuration()).toBe(50);
|
||||
done();
|
||||
})
|
||||
.progressEnd(0, 0.5, 10);
|
||||
});
|
||||
});
|
||||
describe('play()', () => {
|
||||
it('should resolve when the animation is cancelled', async () => {
|
||||
// Tell Jest to expect 1 assertion for async code
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { BackButtonEvent } from '@utils/hardware-back-button';
|
||||
|
||||
/**
|
||||
* When accessing the document or window, it is important
|
||||
* to account for SSR applications where the
|
||||
@@ -27,7 +29,7 @@
|
||||
* on the window for certain CustomEvent types you can add that definition
|
||||
* here as long as you are using the "win" utility below.
|
||||
*/
|
||||
type IonicWindow = Window & {
|
||||
type IonicEvents = {
|
||||
addEventListener(
|
||||
type: 'ionKeyboardDidShow',
|
||||
listener: (ev: CustomEvent<{ keyboardHeight: number }>) => void,
|
||||
@@ -38,8 +40,41 @@ type IonicWindow = Window & {
|
||||
listener: (ev: CustomEvent<{ keyboardHeight: number }>) => void,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
addEventListener(
|
||||
type: 'ionInputDidLoad',
|
||||
listener: (ev: CustomEvent<HTMLIonInputElement | HTMLIonTextareaElement>) => void,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
removeEventListener(
|
||||
type: 'ionInputDidLoad',
|
||||
listener: (ev: CustomEvent<HTMLIonInputElement | HTMLIonTextareaElement>) => void,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
addEventListener(
|
||||
type: 'ionInputDidUnload',
|
||||
listener: (ev: CustomEvent<HTMLIonInputElement | HTMLIonTextareaElement>) => void,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
removeEventListener(
|
||||
type: 'ionInputDidUnload',
|
||||
listener: (ev: CustomEvent<HTMLIonInputElement | HTMLIonTextareaElement>) => void,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
addEventListener(
|
||||
type: 'ionBackButton',
|
||||
listener: (ev: BackButtonEvent) => void,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
removeEventListener(
|
||||
type: 'ionBackButton',
|
||||
listener: (ev: BackButtonEvent) => void,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
};
|
||||
|
||||
type IonicWindow = Window & IonicEvents;
|
||||
type IonicDocument = Document & IonicEvents;
|
||||
|
||||
export const win: IonicWindow | undefined = typeof window !== 'undefined' ? window : undefined;
|
||||
|
||||
export const doc: Document | undefined = typeof document !== 'undefined' ? document : undefined;
|
||||
export const doc: IonicDocument | undefined = typeof document !== 'undefined' ? document : undefined;
|
||||
|
||||
44
core/src/utils/forms/compare-with-utils.ts
Normal file
44
core/src/utils/forms/compare-with-utils.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
type CompareFn = (currentValue: any, compareValue: any) => boolean;
|
||||
|
||||
/**
|
||||
* Uses the compareWith param to compare two values to determine if they are equal.
|
||||
*
|
||||
* @param currentValue The current value of the control.
|
||||
* @param compareValue The value to compare against.
|
||||
* @param compareWith The function or property name to use to compare values.
|
||||
*/
|
||||
export const compareOptions = (
|
||||
currentValue: any,
|
||||
compareValue: any,
|
||||
compareWith?: string | CompareFn | null
|
||||
): boolean => {
|
||||
if (typeof compareWith === 'function') {
|
||||
return compareWith(currentValue, compareValue);
|
||||
} else if (typeof compareWith === 'string') {
|
||||
return currentValue[compareWith] === compareValue[compareWith];
|
||||
} else {
|
||||
return Array.isArray(compareValue) ? compareValue.includes(currentValue) : currentValue === compareValue;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compares a value against the current value(s) to determine if it is selected.
|
||||
*
|
||||
* @param currentValue The current value of the control.
|
||||
* @param compareValue The value to compare against.
|
||||
* @param compareWith The function or property name to use to compare values.
|
||||
*/
|
||||
export const isOptionSelected = (
|
||||
currentValue: any[] | any,
|
||||
compareValue: any,
|
||||
compareWith?: string | CompareFn | null
|
||||
) => {
|
||||
if (currentValue === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(currentValue)) {
|
||||
return currentValue.some((val) => compareOptions(val, compareValue, compareWith));
|
||||
} else {
|
||||
return compareOptions(currentValue, compareValue, compareWith);
|
||||
}
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './form-controller';
|
||||
export * from './notch-controller';
|
||||
export * from './compare-with-utils';
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import type { BackButtonEvent } from '../interface';
|
||||
|
||||
// TODO(FW-2832): type
|
||||
type Handler = (processNextHandler: () => void) => Promise<any> | void | null;
|
||||
|
||||
export interface BackButtonEventDetail {
|
||||
register(priority: number, handler: (processNextHandler: () => void) => Promise<any> | void): void;
|
||||
}
|
||||
|
||||
export type BackButtonEvent = CustomEvent<BackButtonEventDetail>;
|
||||
|
||||
interface HandlerRegister {
|
||||
priority: number;
|
||||
handler: Handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { doc } from '@utils/browser';
|
||||
|
||||
import type { Config } from '../../interface';
|
||||
import { findClosestIonContent } from '../content';
|
||||
import { componentOnReady } from '../helpers';
|
||||
@@ -12,7 +14,14 @@ const SCROLL_ASSIST = true;
|
||||
const HIDE_CARET = true;
|
||||
|
||||
export const startInputShims = async (config: Config, platform: 'ios' | 'android') => {
|
||||
const doc = document;
|
||||
/**
|
||||
* If doc is undefined then we are in an SSR environment
|
||||
* where input shims do not apply.
|
||||
*/
|
||||
if (doc === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isIOS = platform === 'ios';
|
||||
const isAndroid = platform === 'android';
|
||||
|
||||
@@ -115,15 +124,13 @@ export const startInputShims = async (config: Config, platform: 'ios' | 'android
|
||||
registerInput(input);
|
||||
}
|
||||
|
||||
// TODO(FW-2832): types
|
||||
|
||||
doc.addEventListener('ionInputDidLoad', ((ev: InputEvent) => {
|
||||
doc.addEventListener('ionInputDidLoad', (ev: InputEvent) => {
|
||||
registerInput(ev.detail);
|
||||
}) as any);
|
||||
});
|
||||
|
||||
doc.addEventListener('ionInputDidUnload', ((ev: InputEvent) => {
|
||||
doc.addEventListener('ionInputDidUnload', (ev: InputEvent) => {
|
||||
unregisterInput(ev.detail);
|
||||
}) as any);
|
||||
});
|
||||
};
|
||||
|
||||
type InputEvent = CustomEvent<HTMLElement>;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { doc } from '@utils/browser';
|
||||
import type { BackButtonEvent } from '@utils/hardware-back-button';
|
||||
import { MENU_BACK_BUTTON_PRIORITY } from '@utils/hardware-back-button';
|
||||
import { printIonWarning } from '@utils/logging';
|
||||
|
||||
import type { MenuI, MenuControllerI } from '../../components/menu/menu-interface';
|
||||
import type { AnimationBuilder, BackButtonEvent } from '../../interface';
|
||||
import { MENU_BACK_BUTTON_PRIORITY } from '../hardware-back-button';
|
||||
import type { AnimationBuilder } from '../../interface';
|
||||
import { componentOnReady } from '../helpers';
|
||||
|
||||
import { menuOverlayAnimation } from './animations/overlay';
|
||||
@@ -227,17 +229,14 @@ const createMenuController = (): MenuControllerI => {
|
||||
registerAnimation('push', menuPushAnimation);
|
||||
registerAnimation('overlay', menuOverlayAnimation);
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
document.addEventListener('ionBackButton', (ev: any) => {
|
||||
// TODO(FW-2832): type
|
||||
const openMenu = _getOpenSync();
|
||||
if (openMenu) {
|
||||
(ev as BackButtonEvent).detail.register(MENU_BACK_BUTTON_PRIORITY, () => {
|
||||
return openMenu.close();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
doc?.addEventListener('ionBackButton', (ev: BackButtonEvent) => {
|
||||
const openMenu = _getOpenSync();
|
||||
if (openMenu) {
|
||||
ev.detail.register(MENU_BACK_BUTTON_PRIORITY, () => {
|
||||
return openMenu.close();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
registerAnimation,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { doc } from '@utils/browser';
|
||||
import type { BackButtonEvent } from '@utils/hardware-back-button';
|
||||
|
||||
import { config } from '../global/config';
|
||||
import { getIonMode } from '../global/ionic-global';
|
||||
@@ -7,7 +8,6 @@ import type {
|
||||
AlertOptions,
|
||||
Animation,
|
||||
AnimationBuilder,
|
||||
BackButtonEvent,
|
||||
FrameworkDelegate,
|
||||
HTMLIonOverlayElement,
|
||||
IonicConfig,
|
||||
@@ -697,6 +697,7 @@ export const safeCall = (handler: any, arg?: any) => {
|
||||
|
||||
export const BACKDROP = 'backdrop';
|
||||
export const GESTURE = 'gesture';
|
||||
export const OVERLAY_GESTURE_PRIORITY = 39;
|
||||
|
||||
/**
|
||||
* Creates a delegate controller.
|
||||
|
||||
@@ -6,18 +6,20 @@ import { raf } from '@utils/helpers';
|
||||
* This is not needed for components using native slots in the Shadow DOM.
|
||||
* @internal
|
||||
* @param el The host element to observe
|
||||
* @param slotName mutationCallback will fire when nodes on this slot change
|
||||
* @param slotName mutationCallback will fire when nodes on these slot(s) change
|
||||
* @param mutationCallback The callback to fire whenever the slotted content changes
|
||||
*/
|
||||
export const createSlotMutationController = (
|
||||
el: HTMLElement,
|
||||
slotName: string,
|
||||
slotName: string | string[],
|
||||
mutationCallback: () => void
|
||||
): SlotMutationController => {
|
||||
let hostMutationObserver: MutationObserver | undefined;
|
||||
let slottedContentMutationObserver: MutationObserver | undefined;
|
||||
|
||||
if (win !== undefined && 'MutationObserver' in win) {
|
||||
const slots = Array.isArray(slotName) ? slotName : [slotName];
|
||||
|
||||
hostMutationObserver = new MutationObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
for (const node of entry.addedNodes) {
|
||||
@@ -25,7 +27,7 @@ export const createSlotMutationController = (
|
||||
* Check to see if the added node
|
||||
* is our slotted content.
|
||||
*/
|
||||
if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).slot === slotName) {
|
||||
if (node.nodeType === Node.ELEMENT_NODE && slots.includes((node as HTMLElement).slot)) {
|
||||
/**
|
||||
* If so, we want to watch the slotted
|
||||
* content itself for changes. This lets us
|
||||
|
||||
@@ -18,6 +18,7 @@ This guide details best practices that should be followed when writing E2E tests
|
||||
- [Test for positive and negative cases](#practice-positive-negative)
|
||||
- [Start your test with the configuration or layout in place if possible](#practice-test-config)
|
||||
- [Place your test closest to the fix or feature](#practice-test-close)
|
||||
- [Account for different locales when writing tests](#practice-locales)
|
||||
|
||||
<h2 id="practice-test">Use the customized `test` function</h2>
|
||||
|
||||
@@ -259,3 +260,7 @@ This allows tests to remain fast on CI as we can focus on the test itself instea
|
||||
<h2 id="practice-test-close">Place your test closest to the fix or feature</h2>
|
||||
|
||||
Tests should be placed closest to where the fix or feature was implemented. This means that if a fix was written for `ion-button`, then the test should be placed in `src/components/button/tests`.
|
||||
|
||||
<h2 id="practice-locales">Account for different locales when writing tests</h2>
|
||||
|
||||
Tests ran on CI may not run on the same locale as your local machine. It's always a good idea to apply locale considerations to components that support it, when writing tests (i.e. `ion-datetime` should specify `locale="en-US"`).
|
||||
|
||||
Reference in New Issue
Block a user