chore: sync

This commit is contained in:
Liam DeBeasi
2023-12-18 10:46:20 -05:00
382 changed files with 4167 additions and 861 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View 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);
}
};

View File

@@ -1,2 +1,3 @@
export * from './form-controller';
export * from './notch-controller';
export * from './compare-with-utils';

View File

@@ -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;

View File

@@ -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>;

View File

@@ -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,

View File

@@ -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.

View File

@@ -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

View File

@@ -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"`).