release-4.11.9

This commit is contained in:
Ely Lucas
2020-01-23 13:56:20 -07:00
committed by GitHub
15 changed files with 102 additions and 42 deletions

View File

@ -1,3 +1,16 @@
## [4.11.9](https://github.com/ionic-team/ionic/compare/v4.11.8...v4.11.9) (2020-01-23)
### Bug Fixes
* **core:** updating type of input value to accept numbers, fixes [#20173](https://github.com/ionic-team/ionic/issues/20173) ([#20267](https://github.com/ionic-team/ionic/issues/20267)) ([7080205](https://github.com/ionic-team/ionic/commit/708020551f9c51ca3b32d7b49bf4572db3dda12e))
* **react:** adding missing overlay component events, fixes [#19923](https://github.com/ionic-team/ionic/issues/19923) ([#20266](https://github.com/ionic-team/ionic/issues/20266)) ([ec6a8dd](https://github.com/ionic-team/ionic/commit/ec6a8dd86f3854edba367f79a6ebac7d60eed839))
* **react:** Don't render overlay children if isOpen is false, fixes [#20225](https://github.com/ionic-team/ionic/issues/20225) ([#20226](https://github.com/ionic-team/ionic/issues/20226)) ([aff9612](https://github.com/ionic-team/ionic/commit/aff9612d1197dca48eab6eff9d749032c380cf82))
* **react:** re attach props on update, fixes 20192 ([#20228](https://github.com/ionic-team/ionic/issues/20228)) ([9e35ebe](https://github.com/ionic-team/ionic/commit/9e35ebed4a1590ef2521f5f8c393bdd9dea32a04))
* **react:** remove leaving view when routerdirection is back, fixes [#20124](https://github.com/ionic-team/ionic/issues/20124) ([#20268](https://github.com/ionic-team/ionic/issues/20268)) ([63d4e87](https://github.com/ionic-team/ionic/commit/63d4e877fb18c90d70c4cbd5f66ffccb8ee6489c))
* **react:** support routes without a path for notfound routes, fixes [#20259](https://github.com/ionic-team/ionic/issues/20259) ([#20261](https://github.com/ionic-team/ionic/issues/20261)) ([2f8c13b](https://github.com/ionic-team/ionic/commit/2f8c13b6960f9bcfb941c36fa6e1742b96f80ba9))
* **react:** update icon types to be a string as well, fixes [#20229](https://github.com/ionic-team/ionic/issues/20229) ([#20230](https://github.com/ionic-team/ionic/issues/20230)) ([1411d8a](https://github.com/ionic-team/ionic/commit/1411d8a173bfefd7db5241218fd5641b7e9da823))
# [5.0.0-beta.5](https://github.com/ionic-team/ionic/compare/v4.11.8...v5.0.0-beta.5) (2020-01-17) # [5.0.0-beta.5](https://github.com/ionic-team/ionic/compare/v4.11.8...v5.0.0-beta.5) (2020-01-17)

View File

@ -435,7 +435,7 @@ ion-input,prop,size,number | undefined,undefined,false,false
ion-input,prop,spellcheck,boolean,false,false,false ion-input,prop,spellcheck,boolean,false,false,false
ion-input,prop,step,string | undefined,undefined,false,false ion-input,prop,step,string | undefined,undefined,false,false
ion-input,prop,type,"date" | "email" | "number" | "password" | "search" | "tel" | "text" | "time" | "url",'text',false,false ion-input,prop,type,"date" | "email" | "number" | "password" | "search" | "tel" | "text" | "time" | "url",'text',false,false
ion-input,prop,value,null | string | undefined,'',false,false ion-input,prop,value,null | number | string | undefined,'',false,false
ion-input,method,getInputElement,getInputElement() => Promise<HTMLInputElement> ion-input,method,getInputElement,getInputElement() => Promise<HTMLInputElement>
ion-input,method,setFocus,setFocus() => Promise<void> ion-input,method,setFocus,setFocus() => Promise<void>
ion-input,event,ionBlur,void,true ion-input,event,ionBlur,void,true

View File

@ -978,7 +978,7 @@ export namespace Components {
/** /**
* The value of the input. * The value of the input.
*/ */
'value'?: string | null; 'value'?: string | number | null;
} }
interface IonItem { interface IonItem {
/** /**
@ -4232,7 +4232,7 @@ declare namespace LocalJSX {
/** /**
* The value of the input. * The value of the input.
*/ */
'value'?: string | null; 'value'?: string | number | null;
} }
interface IonItem { interface IonItem {
/** /**

View File

@ -169,7 +169,7 @@ export class Input implements ComponentInterface {
/** /**
* The value of the input. * The value of the input.
*/ */
@Prop({ mutable: true }) value?: string | null = ''; @Prop({ mutable: true }) value?: string | number | null = '';
/** /**
* Update the native input element when the value changes * Update the native input element when the value changes
@ -177,7 +177,7 @@ export class Input implements ComponentInterface {
@Watch('value') @Watch('value')
protected valueChanged() { protected valueChanged() {
this.emitStyle(); this.emitStyle();
this.ionChange.emit({ value: this.value }); this.ionChange.emit({ value: this.value == null ? this.value : this.value.toString() });
} }
/** /**
@ -263,7 +263,8 @@ export class Input implements ComponentInterface {
} }
private getValue(): string { private getValue(): string {
return this.value || ''; return typeof this.value === 'number' ? this.value.toString() :
(this.value || '').toString();
} }
private emitStyle() { private emitStyle() {

View File

@ -238,7 +238,7 @@ export const InputExample: React.FC = () => (
| `spellcheck` | `spellcheck` | If `true`, the element will have its spelling and grammar checked. | `boolean` | `false` | | `spellcheck` | `spellcheck` | If `true`, the element will have its spelling and grammar checked. | `boolean` | `false` |
| `step` | `step` | Works with the min and max attributes to limit the increments at which a value can be set. Possible values are: `"any"` or a positive floating point number. | `string \| undefined` | `undefined` | | `step` | `step` | Works with the min and max attributes to limit the increments at which a value can be set. Possible values are: `"any"` or a positive floating point number. | `string \| undefined` | `undefined` |
| `type` | `type` | The type of control to display. The default type is text. | `"date" \| "email" \| "number" \| "password" \| "search" \| "tel" \| "text" \| "time" \| "url"` | `'text'` | | `type` | `type` | The type of control to display. The default type is text. | `"date" \| "email" \| "number" \| "password" \| "search" \| "tel" \| "text" \| "time" \| "url"` | `'text'` |
| `value` | `value` | The value of the input. | `null \| string \| undefined` | `''` | | `value` | `value` | The value of the input. | `null \| number \| string \| undefined` | `''` |
## Events ## Events

View File

@ -1,6 +1,6 @@
import { RouteProps, match } from 'react-router-dom'; import { RouteProps, match } from 'react-router-dom';
export interface IonRouteData { export interface IonRouteData {
match: match<{ tab: string }> | null; match: match | null;
childProps: RouteProps; childProps: RouteProps;
} }

View File

@ -154,7 +154,7 @@ export class RouteManager extends React.Component<RouteManagerProps, RouteManage
* record the view that originally directed to the new view for back button purposes. * record the view that originally directed to the new view for back button purposes.
*/ */
enteringView.prevId = leavingView.id; enteringView.prevId = leavingView.id;
} else if (action === 'pop' || action === 'replace') { } else {
leavingView.mount = false; leavingView.mount = false;
this.removeOrphanedViews(enteringView, enteringViewStack); this.removeOrphanedViews(enteringView, enteringViewStack);
} }
@ -255,12 +255,23 @@ export class RouteManager extends React.Component<RouteManagerProps, RouteManage
const views: ViewItem[] = []; const views: ViewItem[] = [];
let activeId: string | undefined; let activeId: string | undefined;
const ionRouterOutlet = React.Children.only(children) as React.ReactElement; const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
let foundMatch = false;
React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => { React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => {
const routeId = generateId(); const routeId = generateId();
this.routes[routeId] = child; this.routes[routeId] = child;
views.push(createViewItem(child, routeId, this.props.history.location)); views.push(createViewItem(child, routeId, this.props.history.location));
}); });
if (!foundMatch) {
const notFoundRoute = views.find(r => {
// try to find a route that doesn't have a path or from prop, that will be our not found route
return !r.routeData.childProps.path && !r.routeData.childProps.from;
});
if (notFoundRoute) {
notFoundRoute.show = true;
}
}
this.registerViewStack(id, activeId, views, routerOutlet, this.props.location); this.registerViewStack(id, activeId, views, routerOutlet, this.props.location);
function createViewItem(child: React.ReactElement<any>, routeId: string, location: HistoryLocation) { function createViewItem(child: React.ReactElement<any>, routeId: string, location: HistoryLocation) {
@ -289,6 +300,9 @@ export class RouteManager extends React.Component<RouteManagerProps, RouteManage
if (match && view.isIonRoute) { if (match && view.isIonRoute) {
activeId = viewId; activeId = viewId;
} }
if (!foundMatch && match) {
foundMatch = true;
}
return view; return view;
} }
} }
@ -360,7 +374,7 @@ export class RouteManager extends React.Component<RouteManagerProps, RouteManage
React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => { React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => {
for (const routeKey in this.routes) { for (const routeKey in this.routes) {
const route = this.routes[routeKey]; const route = this.routes[routeKey];
if (route.props.path === child.props.path) { if (typeof route.props.path !== 'undefined' && route.props.path === (child.props.path || child.props.from)) {
this.routes[routeKey] = child; this.routes[routeKey] = child;
} }
} }

View File

@ -65,6 +65,7 @@ export class View extends React.Component<ViewProps, {}> {
...value, ...value,
registerIonPage: this.registerIonPage.bind(this) registerIonPage: this.registerIonPage.bind(this)
}; };
return ( return (
<NavContext.Provider value={newProvider}> <NavContext.Provider value={newProvider}>
{this.props.children} {this.props.children}

View File

@ -13,7 +13,7 @@ export interface ViewStack {
* The holistic view of all the Routes configured for an application inside of an IonRouterOutlet. * The holistic view of all the Routes configured for an application inside of an IonRouterOutlet.
*/ */
export class ViewStacks { export class ViewStacks {
private viewStacks: { [key: string]: ViewStack | undefined } = {}; private viewStacks: { [key: string]: ViewStack | undefined; } = {};
get(key: string) { get(key: string) {
return this.viewStacks[key]; return this.viewStacks[key];
@ -31,25 +31,34 @@ export class ViewStacks {
delete this.viewStacks[key]; delete this.viewStacks[key];
} }
findViewInfoByLocation(location: HistoryLocation, viewKey?: string) { findViewInfoByLocation(location: HistoryLocation, viewKey: string) {
let view: ViewItem<IonRouteData> | undefined; let view: ViewItem<IonRouteData> | undefined;
let match: IonRouteData['match'] | null | undefined; let match: IonRouteData['match'] | null | undefined;
let viewStack: ViewStack | undefined; let viewStack: ViewStack | undefined;
if (viewKey) {
viewStack = this.viewStacks[viewKey]; viewStack = this.viewStacks[viewKey];
if (viewStack) { if (viewStack) {
viewStack.views.some(matchView); viewStack.views.some(matchView);
if (!view) {
viewStack.views.some(r => {
// try to find a route that doesn't have a path or from prop, that will be our not found route
if (!r.routeData.childProps.path && !r.routeData.childProps.from) {
match = {
path: location.pathname,
url: location.pathname,
isExact: true,
params: {}
};
view = r;
return true;
}
return false;
});
} }
} else {
const keys = this.getKeys();
keys.some(key => {
viewStack = this.viewStacks[key];
return viewStack!.views.some(matchView);
});
} }
const result = { view, viewStack, match }; return { view, viewStack, match };
return result;
function matchView(v: ViewItem) { function matchView(v: ViewItem) {
const matchProps = { const matchProps = {
@ -61,7 +70,7 @@ export class ViewStacks {
if (myMatch) { if (myMatch) {
view = v; view = v;
match = myMatch; match = myMatch;
return view.location === location.pathname; return true;
} }
return false; return false;
} }

View File

@ -6,7 +6,7 @@ export interface ActionSheetButton extends Omit<ActionSheetButtonCore, 'icon'> {
icon?: { icon?: {
ios: string; ios: string;
md: string; md: string;
}; } | string;
} }
export interface ActionSheetOptions extends Omit<ActionSheetOptionsCore, 'buttons'> { export interface ActionSheetOptions extends Omit<ActionSheetOptionsCore, 'buttons'> {

View File

@ -6,7 +6,7 @@ export interface ToastButton extends Omit<ToastButtonCore, 'icon'> {
icon?: { icon?: {
ios: string; ios: string;
md: string; md: string;
}; } | string;
} }
export interface ToastOptions extends Omit<ToastOptionsCore, 'buttons'> { export interface ToastOptions extends Omit<ToastOptionsCore, 'buttons'> {

View File

@ -11,13 +11,19 @@ interface OverlayBase extends HTMLElement {
export interface ReactControllerProps { export interface ReactControllerProps {
isOpen: boolean; isOpen: boolean;
onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void; onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onDidPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
} }
export const createControllerComponent = <OptionsType extends object, OverlayType extends OverlayBase>( export const createControllerComponent = <OptionsType extends object, OverlayType extends OverlayBase>(
displayName: string, displayName: string,
controller: { create: (options: OptionsType) => Promise<OverlayType>; } controller: { create: (options: OptionsType) => Promise<OverlayType>; }
) => { ) => {
const dismissEventName = `on${displayName}DidDismiss`; const didDismissEventName = `on${displayName}DidDismiss`;
const didPresentEventName = `on${displayName}DidPresent`;
const willDismissEventName = `on${displayName}WillDismiss`;
const willPresentEventName = `on${displayName}WillPresent`;
type Props = OptionsType & ReactControllerProps & { type Props = OptionsType & ReactControllerProps & {
forwardedRef?: React.RefObject<OverlayType>; forwardedRef?: React.RefObject<OverlayType>;
@ -67,12 +73,15 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
} }
async present(prevProps?: Props) { async present(prevProps?: Props) {
const { isOpen, onDidDismiss, ...cProps } = this.props; const { isOpen, onDidDismiss, onDidPresent, onWillDismiss, onWillPresent, ...cProps } = this.props;
this.overlay = await controller.create({ this.overlay = await controller.create({
...cProps as any ...cProps as any
}); });
attachProps(this.overlay, { attachProps(this.overlay, {
[dismissEventName]: this.handleDismiss [didDismissEventName]: this.handleDismiss,
[didPresentEventName]: (e: CustomEvent) => this.props.onDidPresent && this.props.onDidPresent(e),
[willDismissEventName]: (e: CustomEvent) => this.props.onWillDismiss && this.props.onWillDismiss(e),
[willPresentEventName]: (e: CustomEvent) => this.props.onWillPresent && this.props.onWillPresent(e)
}, prevProps); }, prevProps);
// Check isOpen again since the value could have changed during the async call to controller.create // Check isOpen again since the value could have changed during the async call to controller.create
// It's also possible for the component to have become unmounted. // It's also possible for the component to have become unmounted.

View File

@ -13,13 +13,19 @@ export interface ReactOverlayProps {
children?: React.ReactNode; children?: React.ReactNode;
isOpen: boolean; isOpen: boolean;
onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void; onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onDidPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
} }
export const createOverlayComponent = <OverlayComponent extends object, OverlayType extends OverlayElement>( export const createOverlayComponent = <OverlayComponent extends object, OverlayType extends OverlayElement>(
displayName: string, displayName: string,
controller: { create: (options: any) => Promise<OverlayType>; } controller: { create: (options: any) => Promise<OverlayType>; }
) => { ) => {
const dismissEventName = `on${displayName}DidDismiss`; const didDismissEventName = `on${displayName}DidDismiss`;
const didPresentEventName = `on${displayName}DidPresent`;
const willDismissEventName = `on${displayName}WillDismiss`;
const willPresentEventName = `on${displayName}WillPresent`;
type Props = OverlayComponent & ReactOverlayProps & { type Props = OverlayComponent & ReactOverlayProps & {
forwardedRef?: React.RefObject<OverlayType>; forwardedRef?: React.RefObject<OverlayType>;
@ -40,7 +46,7 @@ export const createOverlayComponent = <OverlayComponent extends object, OverlayT
} }
componentDidMount() { componentDidMount() {
if (this.props.isOpen as boolean) { if (this.props.isOpen) {
this.present(); this.present();
} }
} }
@ -59,6 +65,10 @@ export const createOverlayComponent = <OverlayComponent extends object, OverlayT
} }
async componentDidUpdate(prevProps: Props) { async componentDidUpdate(prevProps: Props) {
if (this.overlay) {
attachProps(this.overlay, this.props, prevProps);
}
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) { if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
this.present(prevProps); this.present(prevProps);
} }
@ -68,31 +78,34 @@ export const createOverlayComponent = <OverlayComponent extends object, OverlayT
} }
async present(prevProps?: Props) { async present(prevProps?: Props) {
const { children, isOpen, onDidDismiss, ...cProps } = this.props; const { children, isOpen, onDidDismiss, onDidPresent, onWillDismiss, onWillPresent, ...cProps } = this.props;
const elementProps = { const elementProps = {
...cProps, ...cProps,
ref: this.props.forwardedRef, ref: this.props.forwardedRef,
[dismissEventName]: this.handleDismiss [didDismissEventName]: this.handleDismiss,
[didPresentEventName]: (e: CustomEvent) => this.props.onDidPresent && this.props.onDidPresent(e),
[willDismissEventName]: (e: CustomEvent) => this.props.onWillDismiss && this.props.onWillDismiss(e),
[willPresentEventName]: (e: CustomEvent) => this.props.onWillPresent && this.props.onWillPresent(e)
}; };
const overlay = this.overlay = await controller.create({ this.overlay = await controller.create({
...elementProps, ...elementProps,
component: this.el, component: this.el,
componentProps: {} componentProps: {}
}); });
if (this.props.forwardedRef) { if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = overlay; (this.props.forwardedRef as any).current = this.overlay;
} }
attachProps(overlay, elementProps, prevProps); attachProps(this.overlay, elementProps, prevProps);
await overlay.present(); await this.overlay.present();
} }
render() { render() {
return ReactDOM.createPortal( return ReactDOM.createPortal(
this.props.children, this.props.isOpen ? this.props.children : null,
this.el this.el
); );
} }

View File

@ -8,11 +8,11 @@ export * from './proxies';
// createControllerComponent // createControllerComponent
export { IonAlert } from './IonAlert'; export { IonAlert } from './IonAlert';
export { IonLoading } from './IonLoading'; export { IonLoading } from './IonLoading';
export { IonToast } from './IonToast'; export * from './IonToast';
export { IonPicker } from './IonPicker'; export { IonPicker } from './IonPicker';
// createOverlayComponent // createOverlayComponent
export { IonActionSheet } from './IonActionSheet'; export * from './IonActionSheet';
export { IonModal } from './IonModal'; export { IonModal } from './IonModal';
export { IonPopover } from './IonPopover'; export { IonPopover } from './IonPopover';

View File

@ -9,7 +9,7 @@ type Props = Omit<LocalJSX.IonBackButton, 'icon'> & IonicReactProps & {
icon?: { icon?: {
ios: string; ios: string;
md: string; md: string;
}; } | string;
ref?: React.RefObject<HTMLIonBackButtonElement>; ref?: React.RefObject<HTMLIonBackButtonElement>;
}; };