chore: format

This commit is contained in:
Nathan Walker
2020-07-05 14:57:06 -07:00
parent 8f2900ad0b
commit da3c0b973d
379 changed files with 71587 additions and 54582 deletions

View File

@@ -1,9 +1,9 @@
let resources: any = {};
export function get() {
return resources;
return resources;
}
export function set(res: any) {
resources = res;
}
resources = res;
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,16 @@
/* tslint:disable:no-empty */
import { ControlStateChangeListener as ControlStateChangeListenerDefinition } from "./control-state-change";
export class ControlStateChangeListener implements ControlStateChangeListenerDefinition {
constructor(control: any /* UIControl */, callback: (state: string) => void) {
console.log("ControlStateChangeListener is intended for IOS usage only.");
}
public start() { }
public stop() { }
export class ControlStateChangeListener
implements ControlStateChangeListenerDefinition {
constructor(
control: any /* UIControl */,
callback: (state: string) => void
) {
console.log(
"ControlStateChangeListener is intended for IOS usage only."
);
}
public start() {}
public stop() {}
}

View File

@@ -3,14 +3,16 @@
* WARNING: This class is intended for IOS only.
*/
export class ControlStateChangeListener {
/**
* Initializes an instance of ControlStateChangeListener class.
* @param control An instance of the UIControl which state will be watched.
* @param callback A callback called when a visual state of the UIControl is changed.
*/
constructor(
control: any /* UIControl */,
callback: (state: string) => void
);
/**
* Initializes an instance of ControlStateChangeListener class.
* @param control An instance of the UIControl which state will be watched.
* @param callback A callback called when a visual state of the UIControl is changed.
*/
constructor(control: any /* UIControl */, callback: (state: string) => void);
start();
stop();
start();
stop();
}

View File

@@ -2,67 +2,76 @@
import { ControlStateChangeListener as ControlStateChangeListenerDefinition } from ".";
class ObserverClass extends NSObject {
// NOTE: Refactor this - use Typescript property instead of strings....
observeValueForKeyPathOfObjectChangeContext(path: string) {
if (path === "selected") {
this["_owner"]._onSelectedChanged();
} else if (path === "enabled") {
this["_owner"]._onEnabledChanged();
} else if (path === "highlighted") {
this["_owner"]._onHighlightedChanged();
}
}
// NOTE: Refactor this - use Typescript property instead of strings....
observeValueForKeyPathOfObjectChangeContext(path: string) {
if (path === "selected") {
this["_owner"]._onSelectedChanged();
} else if (path === "enabled") {
this["_owner"]._onEnabledChanged();
} else if (path === "highlighted") {
this["_owner"]._onHighlightedChanged();
}
}
}
export class ControlStateChangeListener implements ControlStateChangeListenerDefinition {
private _observer: NSObject;
private _control: UIControl;
private _observing: boolean = false;
export class ControlStateChangeListener
implements ControlStateChangeListenerDefinition {
private _observer: NSObject;
private _control: UIControl;
private _observing: boolean = false;
private _callback: (state: string) => void;
private _callback: (state: string) => void;
constructor(control: UIControl, callback: (state: string) => void) {
this._observer = ObserverClass.alloc().init();
this._observer["_owner"] = this;
this._control = control;
this._callback = callback;
}
constructor(control: UIControl, callback: (state: string) => void) {
this._observer = ObserverClass.alloc().init();
this._observer["_owner"] = this;
this._control = control;
this._callback = callback;
}
public start() {
if (!this._observing) {
this._control.addObserverForKeyPathOptionsContext(this._observer, "highlighted", NSKeyValueObservingOptions.New, null);
this._observing = true;
this._updateState();
}
}
public start() {
if (!this._observing) {
this._control.addObserverForKeyPathOptionsContext(
this._observer,
"highlighted",
NSKeyValueObservingOptions.New,
null
);
this._observing = true;
this._updateState();
}
}
public stop() {
if (this._observing) {
this._observing = false;
this._control.removeObserverForKeyPath(this._observer, "highlighted");
}
}
public stop() {
if (this._observing) {
this._observing = false;
this._control.removeObserverForKeyPath(
this._observer,
"highlighted"
);
}
}
//@ts-ignore
private _onEnabledChanged() {
this._updateState();
}
//@ts-ignore
private _onEnabledChanged() {
this._updateState();
}
//@ts-ignore
private _onSelectedChanged() {
this._updateState();
}
//@ts-ignore
private _onSelectedChanged() {
this._updateState();
}
//@ts-ignore
private _onHighlightedChanged() {
this._updateState();
}
//@ts-ignore
private _onHighlightedChanged() {
this._updateState();
}
private _updateState() {
let state = "normal";
if (this._control.highlighted) {
state = "highlighted";
}
this._callback(state);
}
private _updateState() {
let state = "normal";
if (this._control.highlighted) {
state = "highlighted";
}
this._callback(state);
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,22 @@
import { Property, CssProperty, CssAnimationProperty, InheritedProperty } from "../properties";
import {
Property,
CssProperty,
CssAnimationProperty,
InheritedProperty,
} from "../properties";
import { BindingOptions } from "../bindable";
import { Observable } from "../../../data/observable";
import { Style } from "../../styling/style";
import { Page } from "../../page";
import { Order, FlexGrow, FlexShrink, FlexWrapBefore, AlignSelf } from "../../layouts/flexbox-layout";
import {
Order,
FlexGrow,
FlexShrink,
FlexWrapBefore,
AlignSelf,
} from "../../layouts/flexbox-layout";
import { Length } from "../../styling/style-properties";
import { DOMNode } from "../../../debugger/dom-node";
@@ -14,7 +25,10 @@ import { DOMNode } from "../../../debugger/dom-node";
* @param view - Starting view (parent container).
* @param callback - A function to execute on every child. If function returns false it breaks the iteration.
*/
export function eachDescendant(view: ViewBase, callback: (child: ViewBase) => boolean);
export function eachDescendant(
view: ViewBase,
callback: (child: ViewBase) => boolean
);
/**
* Gets an ancestor from a given type.
@@ -22,7 +36,10 @@ export function eachDescendant(view: ViewBase, callback: (child: ViewBase) => bo
* @param criterion - The type of ancestor view we are looking for. Could be a string containing a class name or an actual type.
* Returns an instance of a view (if found), otherwise undefined.
*/
export function getAncestor(view: ViewBase, criterion: string | Function): ViewBase;
export function getAncestor(
view: ViewBase,
criterion: string | Function
): ViewBase;
export function isEventOrGesture(name: string, view: ViewBase): boolean;
@@ -35,463 +52,471 @@ export function isEventOrGesture(name: string, view: ViewBase): boolean;
export function getViewById(view: ViewBase, id: string): ViewBase;
export interface ShowModalOptions {
/**
* Any context you want to pass to the modally shown view. This same context will be available in the arguments of the shownModally event handler.
*/
context: any;
/**
* Any context you want to pass to the modally shown view. This same context will be available in the arguments of the shownModally event handler.
*/
context: any;
/**
* A function that will be called when the view is closed. Any arguments provided when calling ShownModallyData.closeCallback will be available here.
*/
closeCallback: Function;
/**
* A function that will be called when the view is closed. Any arguments provided when calling ShownModallyData.closeCallback will be available here.
*/
closeCallback: Function;
/**
* An optional parameter specifying whether to show the modal view in full-screen mode.
*/
fullscreen?: boolean;
/**
* An optional parameter specifying whether to show the modal view in full-screen mode.
*/
fullscreen?: boolean;
/**
* An optional parameter specifying whether to show the modal view with animation.
*/
animated?: boolean;
/**
* An optional parameter specifying whether to show the modal view with animation.
*/
animated?: boolean;
/**
* An optional parameter specifying whether to stretch the modal view when not in full-screen mode.
*/
stretched?: boolean;
/**
* An optional parameter specifying whether to stretch the modal view when not in full-screen mode.
*/
stretched?: boolean;
/**
* An optional parameter that specify options specific to iOS as an object.
*/
ios?: {
/**
* The UIModalPresentationStyle to be used when showing the dialog in iOS .
*/
presentationStyle?: any; /* UIModalPresentationStyle */
/**
* width of the popup dialog
*/
width?: number;
/**
* height of the popup dialog
*/
height?: number;
}
android?: {
/**
* @deprecated Use ShowModalOptions.cancelable instead.
* An optional parameter specifying whether the modal view can be dismissed when not in full-screen mode.
*/
cancelable?: boolean
}
/**
* An optional parameter specifying whether the modal view can be dismissed when not in full-screen mode.
*/
cancelable?: boolean
/**
* An optional parameter that specify options specific to iOS as an object.
*/
ios?: {
/**
* The UIModalPresentationStyle to be used when showing the dialog in iOS .
*/
presentationStyle?: any /* UIModalPresentationStyle */;
/**
* width of the popup dialog
*/
width?: number;
/**
* height of the popup dialog
*/
height?: number;
};
android?: {
/**
* @deprecated Use ShowModalOptions.cancelable instead.
* An optional parameter specifying whether the modal view can be dismissed when not in full-screen mode.
*/
cancelable?: boolean;
};
/**
* An optional parameter specifying whether the modal view can be dismissed when not in full-screen mode.
*/
cancelable?: boolean;
}
export abstract class ViewBase extends Observable {
// Dynamic properties.
left: Length;
top: Length;
effectiveLeft: number;
effectiveTop: number;
dock: "left" | "top" | "right" | "bottom";
row: number;
col: number;
/**
* Setting `column` property is the same as `col`
*/
column: number;
rowSpan: number;
colSpan: number;
/**
* Setting `columnSpan` property is the same as `colSpan`
*/
columnSpan: number;
domNode: DOMNode;
// Dynamic properties.
left: Length;
top: Length;
effectiveLeft: number;
effectiveTop: number;
dock: "left" | "top" | "right" | "bottom";
row: number;
col: number;
/**
* Setting `column` property is the same as `col`
*/
column: number;
rowSpan: number;
colSpan: number;
/**
* Setting `columnSpan` property is the same as `colSpan`
*/
columnSpan: number;
domNode: DOMNode;
order: Order;
flexGrow: FlexGrow;
flexShrink: FlexShrink;
flexWrapBefore: FlexWrapBefore;
alignSelf: AlignSelf;
order: Order;
flexGrow: FlexGrow;
flexShrink: FlexShrink;
flexWrapBefore: FlexWrapBefore;
alignSelf: AlignSelf;
/**
* @private
* Module name when the view is a module root. Otherwise, it is undefined.
*/
_moduleName?: string;
/**
* @private
* Module name when the view is a module root. Otherwise, it is undefined.
*/
_moduleName?: string;
//@private
/**
* @private
*/
_oldLeft: number;
/**
* @private
*/
_oldTop: number;
/**
* @private
*/
_oldRight: number;
/**
* @private
*/
_oldBottom: number;
/**
* @private
*/
_defaultPaddingTop: number;
/**
* @private
*/
_defaultPaddingRight: number;
/**
* @private
*/
_defaultPaddingBottom: number;
/**
* @private
*/
_defaultPaddingLeft: number;
//@private
/**
* @private
*/
_oldLeft: number;
/**
* @private
*/
_oldTop: number;
/**
* @private
*/
_oldRight: number;
/**
* @private
*/
_oldBottom: number;
/**
* @private
*/
_defaultPaddingTop: number;
/**
* @private
*/
_defaultPaddingRight: number;
/**
* @private
*/
_defaultPaddingBottom: number;
/**
* @private
*/
_defaultPaddingLeft: number;
/**
* A property bag holding suspended native updates.
* Native setters that had to execute while there was no native view,
* or the view was detached from the visual tree etc. will accumulate in this object,
* and will be applied when all prerequisites are met.
* @private
*/
_suspendedUpdates: { [propertyName: string]: Property<any, any> | CssProperty<Style, any> | CssAnimationProperty<Style, any> };
//@endprivate
/**
* A property bag holding suspended native updates.
* Native setters that had to execute while there was no native view,
* or the view was detached from the visual tree etc. will accumulate in this object,
* and will be applied when all prerequisites are met.
* @private
*/
_suspendedUpdates: {
[propertyName: string]:
| Property<any, any>
| CssProperty<Style, any>
| CssAnimationProperty<Style, any>;
};
//@endprivate
/**
* Shows the View contained in moduleName as a modal view.
* @param moduleName - The name of the module to load starting from the application root.
* @param modalOptions - A ShowModalOptions instance
*/
showModal(moduleName: string, modalOptions: ShowModalOptions): ViewBase;
/**
* Shows the View contained in moduleName as a modal view.
* @param moduleName - The name of the module to load starting from the application root.
* @param modalOptions - A ShowModalOptions instance
*/
showModal(moduleName: string, modalOptions: ShowModalOptions): ViewBase;
/**
* Shows the view passed as parameter as a modal view.
* @param view - View instance to be shown modally.
* @param modalOptions - A ShowModalOptions instance
*/
showModal(view: ViewBase, modalOptions: ShowModalOptions): ViewBase;
/**
* Shows the view passed as parameter as a modal view.
* @param view - View instance to be shown modally.
* @param modalOptions - A ShowModalOptions instance
*/
showModal(view: ViewBase, modalOptions: ShowModalOptions): ViewBase;
/**
* Closes the current modal view that this page is showing.
* @param context - Any context you want to pass back to the host when closing the modal view.
*/
closeModal(context?: any): void;
/**
* Closes the current modal view that this page is showing.
* @param context - Any context you want to pass back to the host when closing the modal view.
*/
closeModal(context?: any): void;
public effectiveMinWidth: number;
public effectiveMinHeight: number;
public effectiveWidth: number;
public effectiveHeight: number;
public effectiveMarginTop: number;
public effectiveMarginRight: number;
public effectiveMarginBottom: number;
public effectiveMarginLeft: number;
public effectivePaddingTop: number;
public effectivePaddingRight: number;
public effectivePaddingBottom: number;
public effectivePaddingLeft: number;
public effectiveBorderTopWidth: number;
public effectiveBorderRightWidth: number;
public effectiveBorderBottomWidth: number;
public effectiveBorderLeftWidth: number;
public effectiveMinWidth: number;
public effectiveMinHeight: number;
public effectiveWidth: number;
public effectiveHeight: number;
public effectiveMarginTop: number;
public effectiveMarginRight: number;
public effectiveMarginBottom: number;
public effectiveMarginLeft: number;
public effectivePaddingTop: number;
public effectivePaddingRight: number;
public effectivePaddingBottom: number;
public effectivePaddingLeft: number;
public effectiveBorderTopWidth: number;
public effectiveBorderRightWidth: number;
public effectiveBorderBottomWidth: number;
public effectiveBorderLeftWidth: number;
/**
* String value used when hooking to loaded event.
*/
public static loadedEvent: string;
/**
* String value used when hooking to loaded event.
*/
public static loadedEvent: string;
/**
* String value used when hooking to unloaded event.
*/
public static unloadedEvent: string;
/**
* String value used when hooking to unloaded event.
*/
public static unloadedEvent: string;
public ios: any;
public android: any;
public ios: any;
public android: any;
/**
* returns the native UIViewController.
*/
public viewController: any;
/**
* returns the native UIViewController.
*/
public viewController: any;
/**
* read-only. If you want to set out-of-band the nativeView use the setNativeView method.
*/
public nativeViewProtected: any;
public nativeView: any;
public bindingContext: any;
/**
* read-only. If you want to set out-of-band the nativeView use the setNativeView method.
*/
public nativeViewProtected: any;
public nativeView: any;
public bindingContext: any;
/**
* Gets the name of the constructor function for this instance. E.g. for a Button class this will return "Button".
*/
public typeName: string;
/**
* Gets the name of the constructor function for this instance. E.g. for a Button class this will return "Button".
*/
public typeName: string;
/**
* Gets the parent view. This property is read-only.
*/
public readonly parent: ViewBase;
/**
* Gets the parent view. This property is read-only.
*/
public readonly parent: ViewBase;
/**
* Gets the template parent or the native parent. Sets the template parent.
*/
public parentNode: ViewBase;
/**
* Gets the template parent or the native parent. Sets the template parent.
*/
public parentNode: ViewBase;
/**
* Gets or sets the id for this view.
*/
public id: string;
/**
* Gets or sets the id for this view.
*/
public id: string;
/**
* Gets or sets the CSS class name for this view.
*/
public className: string;
/**
* Gets or sets the CSS class name for this view.
*/
public className: string;
/**
* Gets owner page. This is a read-only property.
*/
public readonly page: Page;
/**
* Gets owner page. This is a read-only property.
*/
public readonly page: Page;
/**
* Gets the style object associated to this view.
*/
public readonly style: Style;
/**
* Gets the style object associated to this view.
*/
public readonly style: Style;
/**
* Returns true if visibility is set to 'collapse'.
* Readonly property
*/
public isCollapsed: boolean;
public readonly isLoaded: boolean;
/**
* Returns true if visibility is set to 'collapse'.
* Readonly property
*/
public isCollapsed: boolean;
public readonly isLoaded: boolean;
/**
* Returns the child view with the specified id.
*/
public getViewById<T extends ViewBase>(id: string): T;
/**
* Returns the child view with the specified id.
*/
public getViewById<T extends ViewBase>(id: string): T;
/**
* Load view.
* @param view to load.
*/
public loadView(view: ViewBase): void;
/**
* Load view.
* @param view to load.
*/
public loadView(view: ViewBase): void;
/**
* Unload view.
* @param view to unload.
*/
public unloadView(view: ViewBase): void;
/**
* Unload view.
* @param view to unload.
*/
public unloadView(view: ViewBase): void;
public onLoaded(): void;
public onUnloaded(): void;
public onResumeNativeUpdates(): void;
public onLoaded(): void;
public onUnloaded(): void;
public onResumeNativeUpdates(): void;
public bind(options: BindingOptions, source?: Object): void;
public unbind(property: string): void;
public bind(options: BindingOptions, source?: Object): void;
public unbind(property: string): void;
/**
* Invalidates the layout of the view and triggers a new layout pass.
*/
public requestLayout(): void;
/**
* Invalidates the layout of the view and triggers a new layout pass.
*/
public requestLayout(): void;
/**
* Iterates over children of type ViewBase.
* @param callback Called for each child of type ViewBase. Iteration stops if this method returns falsy value.
*/
public eachChild(callback: (child: ViewBase) => boolean): void;
/**
* Iterates over children of type ViewBase.
* @param callback Called for each child of type ViewBase. Iteration stops if this method returns falsy value.
*/
public eachChild(callback: (child: ViewBase) => boolean): void;
public _addView(view: ViewBase, atIndex?: number): void;
/**
* Method is intended to be overridden by inheritors and used as "protected"
*/
public _addViewCore(view: ViewBase, atIndex?: number): void;
public _addView(view: ViewBase, atIndex?: number): void;
/**
* Method is intended to be overridden by inheritors and used as "protected"
*/
public _addViewCore(view: ViewBase, atIndex?: number): void;
public _removeView(view: ViewBase): void;
/**
* Method is intended to be overridden by inheritors and used as "protected"
*/
public _removeViewCore(view: ViewBase): void;
public _parentChanged(oldParent: ViewBase): void;
/**
* Method is intended to be overridden by inheritors and used as "protected"
*/
public _dialogClosed(): void;
/**
* Method is intended to be overridden by inheritors and used as "protected"
*/
public _onRootViewReset(): void;
public _removeView(view: ViewBase): void;
/**
* Method is intended to be overridden by inheritors and used as "protected"
*/
public _removeViewCore(view: ViewBase): void;
public _parentChanged(oldParent: ViewBase): void;
/**
* Method is intended to be overridden by inheritors and used as "protected"
*/
public _dialogClosed(): void;
/**
* Method is intended to be overridden by inheritors and used as "protected"
*/
public _onRootViewReset(): void;
_domId: number;
_domId: number;
_cssState: any /* "ui/styling/style-scope" */;
/**
* @private
* Notifies each child's css state for change, recursively.
* Either the style scope, className or id properties were changed.
*/
_onCssStateChange(): void;
_cssState: any /* "ui/styling/style-scope" */;
/**
* @private
* Notifies each child's css state for change, recursively.
* Either the style scope, className or id properties were changed.
*/
_onCssStateChange(): void;
public cssClasses: Set<string>;
public cssPseudoClasses: Set<string>;
public cssClasses: Set<string>;
public cssPseudoClasses: Set<string>;
public _goToVisualState(state: string): void;
public _goToVisualState(state: string): void;
public setInlineStyle(style: string): void;
public setInlineStyle(style: string): void;
_context: any /* android.content.Context */;
_context: any /* android.content.Context */;
/**
* Setups the UI for ViewBase and all its children recursively.
* This method should *not* be overridden by derived views.
*/
_setupUI(context: any /* android.content.Context */, atIndex?: number): void;
/**
* Setups the UI for ViewBase and all its children recursively.
* This method should *not* be overridden by derived views.
*/
_setupUI(
context: any /* android.content.Context */,
atIndex?: number
): void;
/**
* Tears down the UI for ViewBase and all its children recursively.
* This method should *not* be overridden by derived views.
*/
_tearDownUI(force?: boolean): void;
/**
* Tears down the UI for ViewBase and all its children recursively.
* This method should *not* be overridden by derived views.
*/
_tearDownUI(force?: boolean): void;
/**
* Creates a native view.
* Returns either android.view.View or UIView.
*/
createNativeView(): Object;
/**
* Creates a native view.
* Returns either android.view.View or UIView.
*/
createNativeView(): Object;
/**
* Initializes properties/listeners of the native view.
*/
initNativeView(): void;
/**
* Initializes properties/listeners of the native view.
*/
initNativeView(): void;
/**
* Clean up references to the native view.
*/
disposeNativeView(): void;
/**
* Clean up references to the native view.
*/
disposeNativeView(): void;
/**
* Resets properties/listeners set to the native view.
*/
resetNativeView(): void;
/**
* Resets properties/listeners set to the native view.
*/
resetNativeView(): void;
/**
* Set the nativeView field performing extra checks and updates to the native properties on the new view.
* Use in cases where the createNativeView is not suitable.
* As an example use in item controls where the native parent view will create the native views for child items.
*/
setNativeView(view: any): void;
/**
* Set the nativeView field performing extra checks and updates to the native properties on the new view.
* Use in cases where the createNativeView is not suitable.
* As an example use in item controls where the native parent view will create the native views for child items.
*/
setNativeView(view: any): void;
_isAddedToNativeVisualTree: boolean;
_isAddedToNativeVisualTree: boolean;
/**
* Performs the core logic of adding a child view to the native visual tree. Returns true if the view's native representation has been successfully added, false otherwise.
*/
_addViewToNativeVisualTree(view: ViewBase, atIndex?: number): boolean;
_removeViewFromNativeVisualTree(view: ViewBase): void;
_childIndexToNativeChildIndex(index?: number): number;
/**
* Performs the core logic of adding a child view to the native visual tree. Returns true if the view's native representation has been successfully added, false otherwise.
*/
_addViewToNativeVisualTree(view: ViewBase, atIndex?: number): boolean;
_removeViewFromNativeVisualTree(view: ViewBase): void;
_childIndexToNativeChildIndex(index?: number): number;
/**
* @protected
* @unstable
* A widget can call this method to add a matching css pseudo class.
*/
public addPseudoClass(name: string): void;
/**
* @protected
* @unstable
* A widget can call this method to add a matching css pseudo class.
*/
public addPseudoClass(name: string): void;
/**
* @protected
* @unstable
* A widget can call this method to discard matching css pseudo class.
*/
public deletePseudoClass(name: string): void;
/**
* @protected
* @unstable
* A widget can call this method to discard matching css pseudo class.
*/
public deletePseudoClass(name: string): void;
/**
* @unstable
* Ensures a dom-node for this view.
*/
public ensureDomNode();
/**
* @unstable
* Ensures a dom-node for this view.
*/
public ensureDomNode();
//@private
/**
* @private
*/
public recycleNativeView: "always" | "never" | "auto";
/**
* @private
*/
public _isPaddingRelative: boolean;
//@private
/**
* @private
*/
public recycleNativeView: "always" | "never" | "auto";
/**
* @private
*/
public _ignoreFlexMinWidthHeightReset: boolean;
/**
* @private
*/
public _isPaddingRelative: boolean;
public _styleScope: any;
/**
* @private
*/
public _ignoreFlexMinWidthHeightReset: boolean;
/**
* @private
*/
public _automaticallyAdjustsScrollViewInsets: boolean;
/**
* @private
*/
_isStyleScopeHost: boolean;
public _styleScope: any;
/**
* @private
*/
public _layoutParent(): void;
/**
* @private
*/
public _automaticallyAdjustsScrollViewInsets: boolean;
/**
* @private
*/
_isStyleScopeHost: boolean;
/**
* Determines the depth of suspended updates.
* When the value is 0 the current property updates are not batched nor scoped and must be immediately applied.
* If the value is 1 or greater, the current updates are batched and does not have to provide immediate update.
* Do not set this field, the _batchUpdate method is responsible to keep the count up to date,
* as well as adding/rmoving the view to/from the visual tree.
*/
public _suspendNativeUpdatesCount: number;
/**
* @private
*/
public _layoutParent(): void;
/**
* Allow multiple updates to be performed on the instance at once.
*/
public _batchUpdate<T>(callback: () => T): T;
/**
* @private
*/
_setupAsRootView(context: any): void;
/**
* Determines the depth of suspended updates.
* When the value is 0 the current property updates are not batched nor scoped and must be immediately applied.
* If the value is 1 or greater, the current updates are batched and does not have to provide immediate update.
* Do not set this field, the _batchUpdate method is responsible to keep the count up to date,
* as well as adding/rmoving the view to/from the visual tree.
*/
public _suspendNativeUpdatesCount: number;
/**
* When returning true the callLoaded method will be run in setTimeout
* Method is intended to be overridden by inheritors and used as "protected"
*/
_shouldDelayLayout(): boolean;
/**
* Allow multiple updates to be performed on the instance at once.
*/
public _batchUpdate<T>(callback: () => T): T;
/**
* @private
*/
_setupAsRootView(context: any): void;
/**
* @private
*/
_inheritStyleScope(styleScope: any /* StyleScope */): void;
/**
* When returning true the callLoaded method will be run in setTimeout
* Method is intended to be overridden by inheritors and used as "protected"
*/
_shouldDelayLayout(): boolean;
/**
* @private
*/
callLoaded(): void;
/**
* @private
*/
_inheritStyleScope(styleScope: any /* StyleScope */): void;
/**
* @private
*/
callUnloaded(): void;
//@endprivate
/**
* @private
*/
callLoaded(): void;
/**
* @private
*/
callUnloaded(): void;
//@endprivate
}
export class Binding {
constructor(target: ViewBase, options: BindingOptions);
public bind(source: Object): void;
public unbind();
constructor(target: ViewBase, options: BindingOptions);
public bind(source: Object): void;
public unbind();
}
export const idProperty: Property<any, string>;

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,63 +1,108 @@
import { View } from "..";
export class ViewHelper {
/**
* Measure a child by taking into account its margins and a given measureSpecs.
* @param parent This parameter is not used. You can pass null.
* @param child The view to be measured.
* @param measuredWidth The measured width that the parent layout specifies for this view.
* @param measuredHeight The measured height that the parent layout specifies for this view.
*/
public static measureChild(parent: View, child: View, widthMeasureSpec: number, heightMeasureSpec: number): { measuredWidth: number; measuredHeight: number };
/**
* Measure a child by taking into account its margins and a given measureSpecs.
* @param parent This parameter is not used. You can pass null.
* @param child The view to be measured.
* @param measuredWidth The measured width that the parent layout specifies for this view.
* @param measuredHeight The measured height that the parent layout specifies for this view.
*/
public static measureChild(
parent: View,
child: View,
widthMeasureSpec: number,
heightMeasureSpec: number
): { measuredWidth: number; measuredHeight: number };
/**
* Layout a child by taking into account its margins, horizontal and vertical alignments and a given bounds.
* @param parent This parameter is not used. You can pass null.
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
public static layoutChild(parent: View, child: View, left: number, top: number, right: number, bottom: number): void;
/**
* Layout a child by taking into account its margins, horizontal and vertical alignments and a given bounds.
* @param parent This parameter is not used. You can pass null.
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
public static layoutChild(
parent: View,
child: View,
left: number,
top: number,
right: number,
bottom: number
): void;
/**
* Utility to reconcile a desired size and state, with constraints imposed
* by a MeasureSpec. Will take the desired size, unless a different size
* is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the MEASURED_SIZE_MASK bits and
* optionally the bit MEASURED_STATE_TOO_SMALL set if the resulting
* size is smaller than the size the view wants to be.
*/
public static resolveSizeAndState(size: number, specSize: number, specMode: number, childMeasuredState: number): number;
/**
* Utility to reconcile a desired size and state, with constraints imposed
* by a MeasureSpec. Will take the desired size, unless a different size
* is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the MEASURED_SIZE_MASK bits and
* optionally the bit MEASURED_STATE_TOO_SMALL set if the resulting
* size is smaller than the size the view wants to be.
*/
public static resolveSizeAndState(
size: number,
specSize: number,
specMode: number,
childMeasuredState: number
): number;
public static combineMeasuredStates(curState: number, newState): number;
public static combineMeasuredStates(curState: number, newState): number;
}
export namespace IOSHelper {
/**
* String value used when hooking to traitCollectionColorAppearanceChangedEvent event.
*/
export const traitCollectionColorAppearanceChangedEvent: string;
/**
* String value used when hooking to traitCollectionColorAppearanceChangedEvent event.
*/
export const traitCollectionColorAppearanceChangedEvent: string;
/**
* Returns a view with viewController or undefined if no such found along the view's parent chain.
* @param view The view form which to start the search.
*/
export function getParentWithViewController(view: View): View
export function updateAutoAdjustScrollInsets(controller: any /* UIViewController */, owner: View): void
export function updateConstraints(controller: any /* UIViewController */, owner: View): void;
export function layoutView(controller: any /* UIViewController */, owner: View): void;
export function getPositionFromFrame(frame: any /* CGRect */): { left, top, right, bottom };
export function getFrameFromPosition(position: { left, top, right, bottom }, insets?: { left, top, right, bottom }): any /* CGRect */;
export function shrinkToSafeArea(view: View, frame: any /* CGRect */): any /* CGRect */;
export function expandBeyondSafeArea(view: View, frame: any /* CGRect */): any /* CGRect */;
export class UILayoutViewController {
public static initWithOwner(owner: WeakRef<View>): UILayoutViewController;
}
export class UIAdaptivePresentationControllerDelegateImp {
public static initWithOwnerAndCallback(owner: WeakRef<View>, whenClosedCallback: Function): UIAdaptivePresentationControllerDelegateImp;
}
export class UIPopoverPresentationControllerDelegateImp {
public static initWithOwnerAndCallback(owner: WeakRef<View>, whenClosedCallback: Function): UIPopoverPresentationControllerDelegateImp;
}
/**
* Returns a view with viewController or undefined if no such found along the view's parent chain.
* @param view The view form which to start the search.
*/
export function getParentWithViewController(view: View): View;
export function updateAutoAdjustScrollInsets(
controller: any /* UIViewController */,
owner: View
): void;
export function updateConstraints(
controller: any /* UIViewController */,
owner: View
): void;
export function layoutView(
controller: any /* UIViewController */,
owner: View
): void;
export function getPositionFromFrame(
frame: any /* CGRect */
): { left; top; right; bottom };
export function getFrameFromPosition(
position: { left; top; right; bottom },
insets?: { left; top; right; bottom }
): any; /* CGRect */
export function shrinkToSafeArea(
view: View,
frame: any /* CGRect */
): any; /* CGRect */
export function expandBeyondSafeArea(
view: View,
frame: any /* CGRect */
): any; /* CGRect */
export class UILayoutViewController {
public static initWithOwner(
owner: WeakRef<View>
): UILayoutViewController;
}
export class UIAdaptivePresentationControllerDelegateImp {
public static initWithOwnerAndCallback(
owner: WeakRef<View>,
whenClosedCallback: Function
): UIAdaptivePresentationControllerDelegateImp;
}
export class UIPopoverPresentationControllerDelegateImp {
public static initWithOwnerAndCallback(
owner: WeakRef<View>,
whenClosedCallback: Function
): UIPopoverPresentationControllerDelegateImp;
}
}

View File

@@ -3,362 +3,518 @@ import { View } from "..";
// Requires
import { ViewHelper } from "./view-helper-common";
import {
ios as iosUtils,
layout
} from "../../../../utils/utils";
import {
Trace
} from "../../../../trace";
import { ios as iosUtils, layout } from "../../../../utils/utils";
import { Trace } from "../../../../trace";
export * from "./view-helper-common";
const majorVersion = iosUtils.MajorVersion;
export namespace IOSHelper {
export const traitCollectionColorAppearanceChangedEvent = "traitCollectionColorAppearanceChanged";
export function getParentWithViewController(view: View): View {
while (view && !view.viewController) {
view = view.parent as View;
}
// Note: Might return undefined if no parent with viewController is found
return view;
}
export function updateAutoAdjustScrollInsets(controller: UIViewController, owner: View): void {
if (majorVersion <= 10) {
owner._automaticallyAdjustsScrollViewInsets = false;
// This API is deprecated, but has no alternative for <= iOS 10
// Defaults to true and results to appliyng the insets twice together with our logic
// for iOS 11+ we use the contentInsetAdjustmentBehavior property in scrollview
// https://developer.apple.com/documentation/uikit/uiviewcontroller/1621372-automaticallyadjustsscrollviewin
controller.automaticallyAdjustsScrollViewInsets = false;
}
}
export function updateConstraints(controller: UIViewController, owner: View): void {
if (majorVersion <= 10) {
const layoutGuide = initLayoutGuide(controller);
(<any>controller.view).safeAreaLayoutGuide = layoutGuide;
}
}
function initLayoutGuide(controller: UIViewController) {
const rootView = controller.view;
const layoutGuide = UILayoutGuide.alloc().init();
rootView.addLayoutGuide(layoutGuide);
NSLayoutConstraint.activateConstraints(<any>[
layoutGuide.topAnchor.constraintEqualToAnchor(controller.topLayoutGuide.bottomAnchor),
layoutGuide.bottomAnchor.constraintEqualToAnchor(controller.bottomLayoutGuide.topAnchor),
layoutGuide.leadingAnchor.constraintEqualToAnchor(rootView.leadingAnchor),
layoutGuide.trailingAnchor.constraintEqualToAnchor(rootView.trailingAnchor)
]);
return layoutGuide;
}
export function layoutView(controller: UIViewController, owner: View): void {
let layoutGuide = controller.view.safeAreaLayoutGuide;
if (!layoutGuide) {
Trace.write(`safeAreaLayoutGuide during layout of ${owner}. Creating fallback constraints, but layout might be wrong.`,
Trace.categories.Layout, Trace.messageType.error);
layoutGuide = initLayoutGuide(controller);
}
const safeArea = layoutGuide.layoutFrame;
let position = IOSHelper.getPositionFromFrame(safeArea);
const safeAreaSize = safeArea.size;
const hasChildViewControllers = controller.childViewControllers.count > 0;
if (hasChildViewControllers) {
const fullscreen = controller.view.frame;
position = IOSHelper.getPositionFromFrame(fullscreen);
}
const safeAreaWidth = layout.round(layout.toDevicePixels(safeAreaSize.width));
const safeAreaHeight = layout.round(layout.toDevicePixels(safeAreaSize.height));
const widthSpec = layout.makeMeasureSpec(safeAreaWidth, layout.EXACTLY);
const heightSpec = layout.makeMeasureSpec(safeAreaHeight, layout.EXACTLY);
ViewHelper.measureChild(null, owner, widthSpec, heightSpec);
ViewHelper.layoutChild(null, owner, position.left, position.top, position.right, position.bottom);
if (owner.parent) {
owner.parent._layoutParent();
}
}
export function getPositionFromFrame(frame: CGRect): { left, top, right, bottom } {
const left = layout.round(layout.toDevicePixels(frame.origin.x));
const top = layout.round(layout.toDevicePixels(frame.origin.y));
const right = layout.round(layout.toDevicePixels(frame.origin.x + frame.size.width));
const bottom = layout.round(layout.toDevicePixels(frame.origin.y + frame.size.height));
return { left, right, top, bottom };
}
export function getFrameFromPosition(position: { left, top, right, bottom }, insets?: { left, top, right, bottom }): CGRect {
insets = insets || { left: 0, top: 0, right: 0, bottom: 0 };
const left = layout.toDeviceIndependentPixels(position.left + insets.left);
const top = layout.toDeviceIndependentPixels(position.top + insets.top);
const width = layout.toDeviceIndependentPixels(position.right - position.left - insets.left - insets.right);
const height = layout.toDeviceIndependentPixels(position.bottom - position.top - insets.top - insets.bottom);
return CGRectMake(left, top, width, height);
}
export function shrinkToSafeArea(view: View, frame: CGRect): CGRect {
const insets = view.getSafeAreaInsets();
if (insets.left || insets.top) {
const position = IOSHelper.getPositionFromFrame(frame);
const adjustedFrame = IOSHelper.getFrameFromPosition(position, insets);
if (Trace.isEnabled()) {
Trace.write(this + " :shrinkToSafeArea: " + JSON.stringify(IOSHelper.getPositionFromFrame(adjustedFrame)), Trace.categories.Layout);
}
return adjustedFrame;
}
return null;
}
export function expandBeyondSafeArea(view: View, frame: CGRect): CGRect {
const availableSpace = getAvailableSpaceFromParent(view, frame);
const safeArea = availableSpace.safeArea;
const fullscreen = availableSpace.fullscreen;
const inWindow = availableSpace.inWindow;
const position = IOSHelper.getPositionFromFrame(frame);
const safeAreaPosition = IOSHelper.getPositionFromFrame(safeArea);
const fullscreenPosition = IOSHelper.getPositionFromFrame(fullscreen);
const inWindowPosition = IOSHelper.getPositionFromFrame(inWindow);
const adjustedPosition = position;
if (position.left && inWindowPosition.left <= safeAreaPosition.left) {
adjustedPosition.left = fullscreenPosition.left;
}
if (position.top && inWindowPosition.top <= safeAreaPosition.top) {
adjustedPosition.top = fullscreenPosition.top;
}
if (inWindowPosition.right < fullscreenPosition.right && inWindowPosition.right >= safeAreaPosition.right + fullscreenPosition.left) {
adjustedPosition.right += fullscreenPosition.right - inWindowPosition.right;
}
if (inWindowPosition.bottom < fullscreenPosition.bottom && inWindowPosition.bottom >= safeAreaPosition.bottom + fullscreenPosition.top) {
adjustedPosition.bottom += fullscreenPosition.bottom - inWindowPosition.bottom;
}
const adjustedFrame = CGRectMake(layout.toDeviceIndependentPixels(adjustedPosition.left), layout.toDeviceIndependentPixels(adjustedPosition.top), layout.toDeviceIndependentPixels(adjustedPosition.right - adjustedPosition.left), layout.toDeviceIndependentPixels(adjustedPosition.bottom - adjustedPosition.top));
if (Trace.isEnabled()) {
Trace.write(view + " :expandBeyondSafeArea: " + JSON.stringify(IOSHelper.getPositionFromFrame(adjustedFrame)), Trace.categories.Layout);
}
return adjustedFrame;
}
function getAvailableSpaceFromParent(view: View, frame: CGRect): { safeArea: CGRect, fullscreen: CGRect, inWindow: CGRect } {
if (!view) {
return;
}
let scrollView = null;
let viewControllerView = null;
if (view.viewController) {
viewControllerView = view.viewController.view;
} else {
let parent = view.parent as View;
while (parent && !parent.viewController && !(parent.nativeViewProtected instanceof UIScrollView)) {
parent = parent.parent as View;
}
if (parent.nativeViewProtected instanceof UIScrollView) {
scrollView = parent.nativeViewProtected;
} else if (parent.viewController) {
viewControllerView = parent.viewController.view;
}
}
let fullscreen = null;
let safeArea = null;
if (viewControllerView) {
safeArea = viewControllerView.safeAreaLayoutGuide.layoutFrame;
fullscreen = viewControllerView.frame;
}
else if (scrollView) {
const insets = scrollView.safeAreaInsets;
safeArea = CGRectMake(insets.left, insets.top, scrollView.contentSize.width - insets.left - insets.right, scrollView.contentSize.height - insets.top - insets.bottom);
fullscreen = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height);
}
const locationInWindow = view.getLocationInWindow();
let inWindowLeft = locationInWindow.x;
let inWindowTop = locationInWindow.y;
if (scrollView) {
inWindowLeft += scrollView.contentOffset.x;
inWindowTop += scrollView.contentOffset.y;
}
const inWindow = CGRectMake(inWindowLeft, inWindowTop, frame.size.width, frame.size.height);
return { safeArea: safeArea, fullscreen: fullscreen, inWindow: inWindow };
}
export class UILayoutViewController extends UIViewController {
public owner: WeakRef<View>;
public static initWithOwner(owner: WeakRef<View>): UILayoutViewController {
const controller = <UILayoutViewController>UILayoutViewController.new();
controller.owner = owner;
return controller;
}
public viewDidLoad(): void {
super.viewDidLoad();
// Unify translucent and opaque bars layout
// this.edgesForExtendedLayout = UIRectEdgeBottom;
this.extendedLayoutIncludesOpaqueBars = true;
}
public viewWillLayoutSubviews(): void {
super.viewWillLayoutSubviews();
const owner = this.owner.get();
if (owner) {
updateConstraints(this, owner);
}
}
public viewDidLayoutSubviews(): void {
super.viewDidLayoutSubviews();
const owner = this.owner.get();
if (owner) {
if (majorVersion >= 11) {
// Handle nested UILayoutViewController safe area application.
// Currently, UILayoutViewController can be nested only in a TabView.
// The TabView itself is handled by the OS, so we check the TabView's parent (usually a Page, but can be a Layout).
const tabViewItem = owner.parent;
const tabView = tabViewItem && tabViewItem.parent;
let parent = tabView && tabView.parent;
// Handle Angular scenario where TabView is in a ProxyViewContainer
// It is possible to wrap components in ProxyViewContainers indefinitely
// Not using instanceof ProxyViewContainer to avoid circular dependency
// TODO: Try moving UILayoutViewController out of view module
while (parent && !parent.nativeViewProtected) {
parent = parent.parent;
}
if (parent) {
const parentPageInsetsTop = parent.nativeViewProtected.safeAreaInsets.top;
const currentInsetsTop = this.view.safeAreaInsets.top;
const additionalInsetsTop = Math.max(parentPageInsetsTop - currentInsetsTop, 0);
const parentPageInsetsBottom = parent.nativeViewProtected.safeAreaInsets.bottom;
const currentInsetsBottom = this.view.safeAreaInsets.bottom;
const additionalInsetsBottom = Math.max(parentPageInsetsBottom - currentInsetsBottom, 0);
if (additionalInsetsTop > 0 || additionalInsetsBottom > 0) {
const additionalInsets = new UIEdgeInsets({ top: additionalInsetsTop, left: 0, bottom: additionalInsetsBottom, right: 0 });
this.additionalSafeAreaInsets = additionalInsets;
}
}
}
layoutView(this, owner);
}
}
public viewWillAppear(animated: boolean): void {
super.viewWillAppear(animated);
const owner = this.owner.get();
if (!owner) {
return;
}
updateAutoAdjustScrollInsets(this, owner);
if (!owner.parent) {
owner.callLoaded();
}
}
public viewDidDisappear(animated: boolean): void {
super.viewDidDisappear(animated);
const owner = this.owner.get();
if (owner && !owner.parent) {
owner.callUnloaded();
}
}
// Mind implementation for other controllers
public traitCollectionDidChange(previousTraitCollection: UITraitCollection): void {
super.traitCollectionDidChange(previousTraitCollection);
if (majorVersion >= 13) {
const owner = this.owner.get();
if (owner &&
this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection &&
this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection(previousTraitCollection)) {
owner.notify({ eventName: traitCollectionColorAppearanceChangedEvent, object: owner });
}
}
}
}
export class UIAdaptivePresentationControllerDelegateImp extends NSObject implements UIAdaptivePresentationControllerDelegate {
public static ObjCProtocols = [UIAdaptivePresentationControllerDelegate];
private owner: WeakRef<View>;
private closedCallback: Function;
public static initWithOwnerAndCallback(owner: WeakRef<View>, whenClosedCallback: Function): UIAdaptivePresentationControllerDelegateImp {
const instance = <UIAdaptivePresentationControllerDelegateImp>super.new();
instance.owner = owner;
instance.closedCallback = whenClosedCallback;
return instance;
}
public presentationControllerDidDismiss(presentationController: UIPresentationController) {
const owner = this.owner.get();
if (owner && typeof this.closedCallback === "function") {
this.closedCallback();
}
}
}
export class UIPopoverPresentationControllerDelegateImp extends NSObject implements UIPopoverPresentationControllerDelegate {
public static ObjCProtocols = [UIPopoverPresentationControllerDelegate];
private owner: WeakRef<View>;
private closedCallback: Function;
public static initWithOwnerAndCallback(owner: WeakRef<View>, whenClosedCallback: Function): UIPopoverPresentationControllerDelegateImp {
const instance = <UIPopoverPresentationControllerDelegateImp>super.new();
instance.owner = owner;
instance.closedCallback = whenClosedCallback;
return instance;
}
public popoverPresentationControllerDidDismissPopover(popoverPresentationController: UIPopoverPresentationController) {
const owner = this.owner.get();
if (owner && typeof this.closedCallback === "function") {
this.closedCallback();
}
}
}
}
export const traitCollectionColorAppearanceChangedEvent =
"traitCollectionColorAppearanceChanged";
export function getParentWithViewController(view: View): View {
while (view && !view.viewController) {
view = view.parent as View;
}
// Note: Might return undefined if no parent with viewController is found
return view;
}
export function updateAutoAdjustScrollInsets(
controller: UIViewController,
owner: View
): void {
if (majorVersion <= 10) {
owner._automaticallyAdjustsScrollViewInsets = false;
// This API is deprecated, but has no alternative for <= iOS 10
// Defaults to true and results to appliyng the insets twice together with our logic
// for iOS 11+ we use the contentInsetAdjustmentBehavior property in scrollview
// https://developer.apple.com/documentation/uikit/uiviewcontroller/1621372-automaticallyadjustsscrollviewin
controller.automaticallyAdjustsScrollViewInsets = false;
}
}
export function updateConstraints(
controller: UIViewController,
owner: View
): void {
if (majorVersion <= 10) {
const layoutGuide = initLayoutGuide(controller);
(<any>controller.view).safeAreaLayoutGuide = layoutGuide;
}
}
function initLayoutGuide(controller: UIViewController) {
const rootView = controller.view;
const layoutGuide = UILayoutGuide.alloc().init();
rootView.addLayoutGuide(layoutGuide);
NSLayoutConstraint.activateConstraints(<any>[
layoutGuide.topAnchor.constraintEqualToAnchor(
controller.topLayoutGuide.bottomAnchor
),
layoutGuide.bottomAnchor.constraintEqualToAnchor(
controller.bottomLayoutGuide.topAnchor
),
layoutGuide.leadingAnchor.constraintEqualToAnchor(
rootView.leadingAnchor
),
layoutGuide.trailingAnchor.constraintEqualToAnchor(
rootView.trailingAnchor
),
]);
return layoutGuide;
}
export function layoutView(
controller: UIViewController,
owner: View
): void {
let layoutGuide = controller.view.safeAreaLayoutGuide;
if (!layoutGuide) {
Trace.write(
`safeAreaLayoutGuide during layout of ${owner}. Creating fallback constraints, but layout might be wrong.`,
Trace.categories.Layout,
Trace.messageType.error
);
layoutGuide = initLayoutGuide(controller);
}
const safeArea = layoutGuide.layoutFrame;
let position = IOSHelper.getPositionFromFrame(safeArea);
const safeAreaSize = safeArea.size;
const hasChildViewControllers =
controller.childViewControllers.count > 0;
if (hasChildViewControllers) {
const fullscreen = controller.view.frame;
position = IOSHelper.getPositionFromFrame(fullscreen);
}
const safeAreaWidth = layout.round(
layout.toDevicePixels(safeAreaSize.width)
);
const safeAreaHeight = layout.round(
layout.toDevicePixels(safeAreaSize.height)
);
const widthSpec = layout.makeMeasureSpec(safeAreaWidth, layout.EXACTLY);
const heightSpec = layout.makeMeasureSpec(
safeAreaHeight,
layout.EXACTLY
);
ViewHelper.measureChild(null, owner, widthSpec, heightSpec);
ViewHelper.layoutChild(
null,
owner,
position.left,
position.top,
position.right,
position.bottom
);
if (owner.parent) {
owner.parent._layoutParent();
}
}
export function getPositionFromFrame(
frame: CGRect
): { left; top; right; bottom } {
const left = layout.round(layout.toDevicePixels(frame.origin.x));
const top = layout.round(layout.toDevicePixels(frame.origin.y));
const right = layout.round(
layout.toDevicePixels(frame.origin.x + frame.size.width)
);
const bottom = layout.round(
layout.toDevicePixels(frame.origin.y + frame.size.height)
);
return { left, right, top, bottom };
}
export function getFrameFromPosition(
position: { left; top; right; bottom },
insets?: { left; top; right; bottom }
): CGRect {
insets = insets || { left: 0, top: 0, right: 0, bottom: 0 };
const left = layout.toDeviceIndependentPixels(
position.left + insets.left
);
const top = layout.toDeviceIndependentPixels(position.top + insets.top);
const width = layout.toDeviceIndependentPixels(
position.right - position.left - insets.left - insets.right
);
const height = layout.toDeviceIndependentPixels(
position.bottom - position.top - insets.top - insets.bottom
);
return CGRectMake(left, top, width, height);
}
export function shrinkToSafeArea(view: View, frame: CGRect): CGRect {
const insets = view.getSafeAreaInsets();
if (insets.left || insets.top) {
const position = IOSHelper.getPositionFromFrame(frame);
const adjustedFrame = IOSHelper.getFrameFromPosition(
position,
insets
);
if (Trace.isEnabled()) {
Trace.write(
this +
" :shrinkToSafeArea: " +
JSON.stringify(
IOSHelper.getPositionFromFrame(adjustedFrame)
),
Trace.categories.Layout
);
}
return adjustedFrame;
}
return null;
}
export function expandBeyondSafeArea(view: View, frame: CGRect): CGRect {
const availableSpace = getAvailableSpaceFromParent(view, frame);
const safeArea = availableSpace.safeArea;
const fullscreen = availableSpace.fullscreen;
const inWindow = availableSpace.inWindow;
const position = IOSHelper.getPositionFromFrame(frame);
const safeAreaPosition = IOSHelper.getPositionFromFrame(safeArea);
const fullscreenPosition = IOSHelper.getPositionFromFrame(fullscreen);
const inWindowPosition = IOSHelper.getPositionFromFrame(inWindow);
const adjustedPosition = position;
if (position.left && inWindowPosition.left <= safeAreaPosition.left) {
adjustedPosition.left = fullscreenPosition.left;
}
if (position.top && inWindowPosition.top <= safeAreaPosition.top) {
adjustedPosition.top = fullscreenPosition.top;
}
if (
inWindowPosition.right < fullscreenPosition.right &&
inWindowPosition.right >=
safeAreaPosition.right + fullscreenPosition.left
) {
adjustedPosition.right +=
fullscreenPosition.right - inWindowPosition.right;
}
if (
inWindowPosition.bottom < fullscreenPosition.bottom &&
inWindowPosition.bottom >=
safeAreaPosition.bottom + fullscreenPosition.top
) {
adjustedPosition.bottom +=
fullscreenPosition.bottom - inWindowPosition.bottom;
}
const adjustedFrame = CGRectMake(
layout.toDeviceIndependentPixels(adjustedPosition.left),
layout.toDeviceIndependentPixels(adjustedPosition.top),
layout.toDeviceIndependentPixels(
adjustedPosition.right - adjustedPosition.left
),
layout.toDeviceIndependentPixels(
adjustedPosition.bottom - adjustedPosition.top
)
);
if (Trace.isEnabled()) {
Trace.write(
view +
" :expandBeyondSafeArea: " +
JSON.stringify(
IOSHelper.getPositionFromFrame(adjustedFrame)
),
Trace.categories.Layout
);
}
return adjustedFrame;
}
function getAvailableSpaceFromParent(
view: View,
frame: CGRect
): { safeArea: CGRect; fullscreen: CGRect; inWindow: CGRect } {
if (!view) {
return;
}
let scrollView = null;
let viewControllerView = null;
if (view.viewController) {
viewControllerView = view.viewController.view;
} else {
let parent = view.parent as View;
while (
parent &&
!parent.viewController &&
!(parent.nativeViewProtected instanceof UIScrollView)
) {
parent = parent.parent as View;
}
if (parent.nativeViewProtected instanceof UIScrollView) {
scrollView = parent.nativeViewProtected;
} else if (parent.viewController) {
viewControllerView = parent.viewController.view;
}
}
let fullscreen = null;
let safeArea = null;
if (viewControllerView) {
safeArea = viewControllerView.safeAreaLayoutGuide.layoutFrame;
fullscreen = viewControllerView.frame;
} else if (scrollView) {
const insets = scrollView.safeAreaInsets;
safeArea = CGRectMake(
insets.left,
insets.top,
scrollView.contentSize.width - insets.left - insets.right,
scrollView.contentSize.height - insets.top - insets.bottom
);
fullscreen = CGRectMake(
0,
0,
scrollView.contentSize.width,
scrollView.contentSize.height
);
}
const locationInWindow = view.getLocationInWindow();
let inWindowLeft = locationInWindow.x;
let inWindowTop = locationInWindow.y;
if (scrollView) {
inWindowLeft += scrollView.contentOffset.x;
inWindowTop += scrollView.contentOffset.y;
}
const inWindow = CGRectMake(
inWindowLeft,
inWindowTop,
frame.size.width,
frame.size.height
);
return {
safeArea: safeArea,
fullscreen: fullscreen,
inWindow: inWindow,
};
}
export class UILayoutViewController extends UIViewController {
public owner: WeakRef<View>;
public static initWithOwner(
owner: WeakRef<View>
): UILayoutViewController {
const controller = <UILayoutViewController>(
UILayoutViewController.new()
);
controller.owner = owner;
return controller;
}
public viewDidLoad(): void {
super.viewDidLoad();
// Unify translucent and opaque bars layout
// this.edgesForExtendedLayout = UIRectEdgeBottom;
this.extendedLayoutIncludesOpaqueBars = true;
}
public viewWillLayoutSubviews(): void {
super.viewWillLayoutSubviews();
const owner = this.owner.get();
if (owner) {
updateConstraints(this, owner);
}
}
public viewDidLayoutSubviews(): void {
super.viewDidLayoutSubviews();
const owner = this.owner.get();
if (owner) {
if (majorVersion >= 11) {
// Handle nested UILayoutViewController safe area application.
// Currently, UILayoutViewController can be nested only in a TabView.
// The TabView itself is handled by the OS, so we check the TabView's parent (usually a Page, but can be a Layout).
const tabViewItem = owner.parent;
const tabView = tabViewItem && tabViewItem.parent;
let parent = tabView && tabView.parent;
// Handle Angular scenario where TabView is in a ProxyViewContainer
// It is possible to wrap components in ProxyViewContainers indefinitely
// Not using instanceof ProxyViewContainer to avoid circular dependency
// TODO: Try moving UILayoutViewController out of view module
while (parent && !parent.nativeViewProtected) {
parent = parent.parent;
}
if (parent) {
const parentPageInsetsTop =
parent.nativeViewProtected.safeAreaInsets.top;
const currentInsetsTop = this.view.safeAreaInsets.top;
const additionalInsetsTop = Math.max(
parentPageInsetsTop - currentInsetsTop,
0
);
const parentPageInsetsBottom =
parent.nativeViewProtected.safeAreaInsets.bottom;
const currentInsetsBottom = this.view.safeAreaInsets
.bottom;
const additionalInsetsBottom = Math.max(
parentPageInsetsBottom - currentInsetsBottom,
0
);
if (
additionalInsetsTop > 0 ||
additionalInsetsBottom > 0
) {
const additionalInsets = new UIEdgeInsets({
top: additionalInsetsTop,
left: 0,
bottom: additionalInsetsBottom,
right: 0,
});
this.additionalSafeAreaInsets = additionalInsets;
}
}
}
layoutView(this, owner);
}
}
public viewWillAppear(animated: boolean): void {
super.viewWillAppear(animated);
const owner = this.owner.get();
if (!owner) {
return;
}
updateAutoAdjustScrollInsets(this, owner);
if (!owner.parent) {
owner.callLoaded();
}
}
public viewDidDisappear(animated: boolean): void {
super.viewDidDisappear(animated);
const owner = this.owner.get();
if (owner && !owner.parent) {
owner.callUnloaded();
}
}
// Mind implementation for other controllers
public traitCollectionDidChange(
previousTraitCollection: UITraitCollection
): void {
super.traitCollectionDidChange(previousTraitCollection);
if (majorVersion >= 13) {
const owner = this.owner.get();
if (
owner &&
this.traitCollection
.hasDifferentColorAppearanceComparedToTraitCollection &&
this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection(
previousTraitCollection
)
) {
owner.notify({
eventName: traitCollectionColorAppearanceChangedEvent,
object: owner,
});
}
}
}
}
export class UIAdaptivePresentationControllerDelegateImp extends NSObject
implements UIAdaptivePresentationControllerDelegate {
public static ObjCProtocols = [
UIAdaptivePresentationControllerDelegate,
];
private owner: WeakRef<View>;
private closedCallback: Function;
public static initWithOwnerAndCallback(
owner: WeakRef<View>,
whenClosedCallback: Function
): UIAdaptivePresentationControllerDelegateImp {
const instance = <UIAdaptivePresentationControllerDelegateImp>(
super.new()
);
instance.owner = owner;
instance.closedCallback = whenClosedCallback;
return instance;
}
public presentationControllerDidDismiss(
presentationController: UIPresentationController
) {
const owner = this.owner.get();
if (owner && typeof this.closedCallback === "function") {
this.closedCallback();
}
}
}
export class UIPopoverPresentationControllerDelegateImp extends NSObject
implements UIPopoverPresentationControllerDelegate {
public static ObjCProtocols = [UIPopoverPresentationControllerDelegate];
private owner: WeakRef<View>;
private closedCallback: Function;
public static initWithOwnerAndCallback(
owner: WeakRef<View>,
whenClosedCallback: Function
): UIPopoverPresentationControllerDelegateImp {
const instance = <UIPopoverPresentationControllerDelegateImp>(
super.new()
);
instance.owner = owner;
instance.closedCallback = whenClosedCallback;
return instance;
}
public popoverPresentationControllerDidDismissPopover(
popoverPresentationController: UIPopoverPresentationController
) {
const owner = this.owner.get();
if (owner && typeof this.closedCallback === "function") {
this.closedCallback();
}
}
}
}

View File

@@ -1,205 +1,290 @@
// Types
import { View as ViewDefinition } from "..";
import {
HorizontalAlignment as HorizontalAlignmentDefinition,
VerticalAlignment as VerticalAlignmentDefinition
import {
HorizontalAlignment as HorizontalAlignmentDefinition,
VerticalAlignment as VerticalAlignmentDefinition,
} from "../../../styling/style-properties";
// Requires
import { layout } from "../../../../utils/utils";
import {
Trace
} from "../../../../trace";
import { Trace } from "../../../../trace";
export class ViewHelper {
public static measureChild(
parent: ViewDefinition,
child: ViewDefinition,
widthMeasureSpec: number,
heightMeasureSpec: number
): { measuredWidth: number; measuredHeight: number } {
let measureWidth = 0;
let measureHeight = 0;
public static measureChild(parent: ViewDefinition, child: ViewDefinition, widthMeasureSpec: number, heightMeasureSpec: number): { measuredWidth: number; measuredHeight: number } {
let measureWidth = 0;
let measureHeight = 0;
if (child && !child.isCollapsed) {
const widthSpec = parent
? parent._currentWidthMeasureSpec
: widthMeasureSpec;
const heightSpec = parent
? parent._currentHeightMeasureSpec
: heightMeasureSpec;
if (child && !child.isCollapsed) {
const width = layout.getMeasureSpecSize(widthSpec);
const widthMode = layout.getMeasureSpecMode(widthSpec);
const widthSpec = parent ? parent._currentWidthMeasureSpec : widthMeasureSpec;
const heightSpec = parent ? parent._currentHeightMeasureSpec : heightMeasureSpec;
const height = layout.getMeasureSpecSize(heightSpec);
const heightMode = layout.getMeasureSpecMode(heightSpec);
const width = layout.getMeasureSpecSize(widthSpec);
const widthMode = layout.getMeasureSpecMode(widthSpec);
child._updateEffectiveLayoutValues(
width,
widthMode,
height,
heightMode
);
const height = layout.getMeasureSpecSize(heightSpec);
const heightMode = layout.getMeasureSpecMode(heightSpec);
const style = child.style;
const horizontalMargins =
child.effectiveMarginLeft + child.effectiveMarginRight;
const verticalMargins =
child.effectiveMarginTop + child.effectiveMarginBottom;
child._updateEffectiveLayoutValues(width, widthMode, height, heightMode);
const childWidthMeasureSpec = ViewHelper.getMeasureSpec(
widthMeasureSpec,
horizontalMargins,
child.effectiveWidth,
style.horizontalAlignment === "stretch"
);
const childHeightMeasureSpec = ViewHelper.getMeasureSpec(
heightMeasureSpec,
verticalMargins,
child.effectiveHeight,
style.verticalAlignment === "stretch"
);
const style = child.style;
const horizontalMargins = child.effectiveMarginLeft + child.effectiveMarginRight;
const verticalMargins = child.effectiveMarginTop + child.effectiveMarginBottom;
if (Trace.isEnabled()) {
Trace.write(
`${
child.parent
} :measureChild: ${child} ${layout.measureSpecToString(
childWidthMeasureSpec
)}, ${layout.measureSpecToString(childHeightMeasureSpec)}}`,
Trace.categories.Layout
);
}
const childWidthMeasureSpec = ViewHelper.getMeasureSpec(widthMeasureSpec, horizontalMargins, child.effectiveWidth, style.horizontalAlignment === "stretch");
const childHeightMeasureSpec = ViewHelper.getMeasureSpec(heightMeasureSpec, verticalMargins, child.effectiveHeight, style.verticalAlignment === "stretch");
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
measureWidth = Math.round(
child.getMeasuredWidth() + horizontalMargins
);
measureHeight = Math.round(
child.getMeasuredHeight() + verticalMargins
);
}
if (Trace.isEnabled()) {
Trace.write(`${child.parent} :measureChild: ${child} ${layout.measureSpecToString(childWidthMeasureSpec)}, ${layout.measureSpecToString(childHeightMeasureSpec)}}`, Trace.categories.Layout);
}
return { measuredWidth: measureWidth, measuredHeight: measureHeight };
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
measureWidth = Math.round(child.getMeasuredWidth() + horizontalMargins);
measureHeight = Math.round(child.getMeasuredHeight() + verticalMargins);
}
public static layoutChild(
parent: ViewDefinition,
child: ViewDefinition,
left: number,
top: number,
right: number,
bottom: number,
setFrame: boolean = true
): void {
if (!child || child.isCollapsed) {
return;
}
return { measuredWidth: measureWidth, measuredHeight: measureHeight };
}
let childStyle = child.style;
public static layoutChild(parent: ViewDefinition, child: ViewDefinition, left: number, top: number, right: number, bottom: number, setFrame: boolean = true): void {
if (!child || child.isCollapsed) {
return;
}
let childTop: number;
let childLeft: number;
let childStyle = child.style;
let childWidth = child.getMeasuredWidth();
let childHeight = child.getMeasuredHeight();
let childTop: number;
let childLeft: number;
let effectiveMarginTop = child.effectiveMarginTop;
let effectiveMarginBottom = child.effectiveMarginBottom;
let childWidth = child.getMeasuredWidth();
let childHeight = child.getMeasuredHeight();
let vAlignment: VerticalAlignmentDefinition;
if (
child.effectiveHeight >= 0 &&
childStyle.verticalAlignment === "stretch"
) {
vAlignment = "middle";
} else {
vAlignment = childStyle.verticalAlignment;
}
let effectiveMarginTop = child.effectiveMarginTop;
let effectiveMarginBottom = child.effectiveMarginBottom;
switch (vAlignment) {
case "top":
childTop = top + effectiveMarginTop;
break;
let vAlignment: VerticalAlignmentDefinition;
if (child.effectiveHeight >= 0 && childStyle.verticalAlignment === "stretch") {
vAlignment = "middle";
}
else {
vAlignment = childStyle.verticalAlignment;
}
case "middle":
childTop =
top +
(bottom -
top -
childHeight +
(effectiveMarginTop - effectiveMarginBottom)) /
2;
break;
switch (vAlignment) {
case "top":
childTop = top + effectiveMarginTop;
break;
case "bottom":
childTop = bottom - childHeight - effectiveMarginBottom;
break;
case "middle":
childTop = top + (bottom - top - childHeight + (effectiveMarginTop - effectiveMarginBottom)) / 2;
break;
case "stretch":
default:
childTop = top + effectiveMarginTop;
childHeight =
bottom - top - (effectiveMarginTop + effectiveMarginBottom);
break;
}
case "bottom":
childTop = bottom - childHeight - effectiveMarginBottom;
break;
let effectiveMarginLeft = child.effectiveMarginLeft;
let effectiveMarginRight = child.effectiveMarginRight;
case "stretch":
default:
childTop = top + effectiveMarginTop;
childHeight = bottom - top - (effectiveMarginTop + effectiveMarginBottom);
break;
}
let hAlignment: HorizontalAlignmentDefinition;
if (
child.effectiveWidth >= 0 &&
childStyle.horizontalAlignment === "stretch"
) {
hAlignment = "center";
} else {
hAlignment = childStyle.horizontalAlignment;
}
let effectiveMarginLeft = child.effectiveMarginLeft;
let effectiveMarginRight = child.effectiveMarginRight;
switch (hAlignment) {
case "left":
childLeft = left + effectiveMarginLeft;
break;
let hAlignment: HorizontalAlignmentDefinition;
if (child.effectiveWidth >= 0 && childStyle.horizontalAlignment === "stretch") {
hAlignment = "center";
}
else {
hAlignment = childStyle.horizontalAlignment;
}
case "center":
childLeft =
left +
(right -
left -
childWidth +
(effectiveMarginLeft - effectiveMarginRight)) /
2;
break;
switch (hAlignment) {
case "left":
childLeft = left + effectiveMarginLeft;
break;
case "right":
childLeft = right - childWidth - effectiveMarginRight;
break;
case "center":
childLeft = left + (right - left - childWidth + (effectiveMarginLeft - effectiveMarginRight)) / 2;
break;
case "stretch":
default:
childLeft = left + effectiveMarginLeft;
childWidth =
right - left - (effectiveMarginLeft + effectiveMarginRight);
break;
}
case "right":
childLeft = right - childWidth - effectiveMarginRight;
break;
let childRight = Math.round(childLeft + childWidth);
let childBottom = Math.round(childTop + childHeight);
childLeft = Math.round(childLeft);
childTop = Math.round(childTop);
case "stretch":
default:
childLeft = left + effectiveMarginLeft;
childWidth = right - left - (effectiveMarginLeft + effectiveMarginRight);
break;
}
if (Trace.isEnabled()) {
Trace.write(
child.parent +
" :layoutChild: " +
child +
" " +
childLeft +
", " +
childTop +
", " +
childRight +
", " +
childBottom,
Trace.categories.Layout
);
}
let childRight = Math.round(childLeft + childWidth);
let childBottom = Math.round(childTop + childHeight);
childLeft = Math.round(childLeft);
childTop = Math.round(childTop);
child.layout(childLeft, childTop, childRight, childBottom, setFrame);
}
if (Trace.isEnabled()) {
Trace.write(child.parent + " :layoutChild: " + child + " " + childLeft + ", " + childTop + ", " + childRight + ", " + childBottom, Trace.categories.Layout);
}
public static resolveSizeAndState(
size: number,
specSize: number,
specMode: number,
childMeasuredState: number
): number {
let result = size;
switch (specMode) {
case layout.UNSPECIFIED:
result = Math.ceil(size);
break;
child.layout(childLeft, childTop, childRight, childBottom, setFrame);
}
case layout.AT_MOST:
if (specSize < size) {
result =
Math.ceil(specSize) | layout.MEASURED_STATE_TOO_SMALL;
}
break;
public static resolveSizeAndState(size: number, specSize: number, specMode: number, childMeasuredState: number): number {
let result = size;
switch (specMode) {
case layout.UNSPECIFIED:
result = Math.ceil(size);
break;
case layout.EXACTLY:
result = Math.ceil(specSize);
break;
}
case layout.AT_MOST:
if (specSize < size) {
result = Math.ceil(specSize) | layout.MEASURED_STATE_TOO_SMALL;
}
break;
return result | (childMeasuredState & layout.MEASURED_STATE_MASK);
}
case layout.EXACTLY:
result = Math.ceil(specSize);
break;
}
public static combineMeasuredStates(curState: number, newState): number {
return curState | newState;
}
return result | (childMeasuredState & layout.MEASURED_STATE_MASK);
}
private static getMeasureSpec(
parentSpec: number,
margins: number,
childLength: number,
stretched: boolean
): number {
const parentLength = layout.getMeasureSpecSize(parentSpec);
const parentSpecMode = layout.getMeasureSpecMode(parentSpec);
public static combineMeasuredStates(curState: number, newState): number {
return curState | newState;
}
let resultSize: number;
let resultMode: number;
private static getMeasureSpec(parentSpec: number, margins: number, childLength: number, stretched: boolean): number {
const parentLength = layout.getMeasureSpecSize(parentSpec);
const parentSpecMode = layout.getMeasureSpecMode(parentSpec);
// We want a specific size... let be it.
if (childLength >= 0) {
// If mode !== UNSPECIFIED we take the smaller of parentLength and childLength
// Otherwise we will need to clip the view but this is not possible in all Android API levels.
// TODO: remove Math.min(parentLength, childLength)
resultSize =
parentSpecMode === layout.UNSPECIFIED
? childLength
: Math.min(parentLength, childLength);
resultMode = layout.EXACTLY;
} else {
switch (parentSpecMode) {
// Parent has imposed an exact size on us
case layout.EXACTLY:
resultSize = Math.max(0, parentLength - margins);
// if stretched - nativeView wants to be our size. So be it.
// else - nativeView wants to determine its own size. It can't be bigger than us.
resultMode = stretched ? layout.EXACTLY : layout.AT_MOST;
break;
let resultSize: number;
let resultMode: number;
// Parent has imposed a maximum size on us
case layout.AT_MOST:
resultSize = Math.max(0, parentLength - margins);
resultMode = layout.AT_MOST;
break;
// We want a specific size... let be it.
if (childLength >= 0) {
// If mode !== UNSPECIFIED we take the smaller of parentLength and childLength
// Otherwise we will need to clip the view but this is not possible in all Android API levels.
// TODO: remove Math.min(parentLength, childLength)
resultSize = parentSpecMode === layout.UNSPECIFIED ? childLength : Math.min(parentLength, childLength);
resultMode = layout.EXACTLY;
}
else {
switch (parentSpecMode) {
// Parent has imposed an exact size on us
case layout.EXACTLY:
resultSize = Math.max(0, parentLength - margins);
// if stretched - nativeView wants to be our size. So be it.
// else - nativeView wants to determine its own size. It can't be bigger than us.
resultMode = stretched ? layout.EXACTLY : layout.AT_MOST;
break;
// Equivalent to measure with Infinity.
case layout.UNSPECIFIED:
resultSize = 0;
resultMode = layout.UNSPECIFIED;
break;
}
}
// Parent has imposed a maximum size on us
case layout.AT_MOST:
resultSize = Math.max(0, parentLength - margins);
resultMode = layout.AT_MOST;
break;
// Equivalent to measure with Infinity.
case layout.UNSPECIFIED:
resultSize = 0;
resultMode = layout.UNSPECIFIED;
break;
}
}
return layout.makeMeasureSpec(resultSize, resultMode);
}
return layout.makeMeasureSpec(resultSize, resultMode);
}
}

View File

@@ -7,7 +7,12 @@ import { Observable, EventData } from "../../../data/observable";
* @param handler The function which should be called when event occurs.
* @param target Subscriber (target) of the event listener. It will be used as a thisArg in the handler function.
*/
export function addWeakEventListener(source: Observable, eventName: string, handler: (eventData: EventData) => void, target: any): void;
export function addWeakEventListener(
source: Observable,
eventName: string,
handler: (eventData: EventData) => void,
target: any
): void;
/**
* Removes a WeakEventListener.
@@ -16,4 +21,9 @@ export function addWeakEventListener(source: Observable, eventName: string, hand
* @param handler The function which should be called when event occurs.
* @param target Subscriber (target) of the event listener. It will be used as a thisArg in the handler function.
*/
export function removeWeakEventListener(source: Observable, eventName: string, handler: (eventData: EventData) => void, target: any): void;
export function removeWeakEventListener(
source: Observable,
eventName: string,
handler: (eventData: EventData) => void,
target: any
): void;

View File

@@ -1,151 +1,177 @@
import { Observable, EventData } from "../../../data/observable";
const handlersForEventName = new Map<string, (eventData: EventData) => void>();
const sourcesMap = new WeakMap<Observable, Map<string, Array<TargetHandlerPair>>>();
const sourcesMap = new WeakMap<
Observable,
Map<string, Array<TargetHandlerPair>>
>();
class TargetHandlerPair {
tagetRef: WeakRef<Object>;
handler: (eventData: EventData) => void;
tagetRef: WeakRef<Object>;
handler: (eventData: EventData) => void;
constructor(target: Object, handler: (eventData: EventData) => void) {
this.tagetRef = new WeakRef(target);
this.handler = handler;
}
constructor(target: Object, handler: (eventData: EventData) => void) {
this.tagetRef = new WeakRef(target);
this.handler = handler;
}
}
function getHandlerForEventName(eventName: string): (eventData: EventData) => void {
let handler = handlersForEventName.get(eventName);
if (!handler) {
handler = function (eventData: EventData) {
const source = eventData.object;
const sourceEventMap = sourcesMap.get(source);
if (!sourceEventMap) {
// There is no event map for this source - it is safe to detach the listener;
source.removeEventListener(eventName, handlersForEventName.get(eventName));
function getHandlerForEventName(
eventName: string
): (eventData: EventData) => void {
let handler = handlersForEventName.get(eventName);
if (!handler) {
handler = function (eventData: EventData) {
const source = eventData.object;
const sourceEventMap = sourcesMap.get(source);
if (!sourceEventMap) {
// There is no event map for this source - it is safe to detach the listener;
source.removeEventListener(
eventName,
handlersForEventName.get(eventName)
);
return;
}
return;
}
const targetHandlerPairList = sourceEventMap.get(eventName);
if (!targetHandlerPairList) {
return;
}
const targetHandlerPairList = sourceEventMap.get(eventName);
if (!targetHandlerPairList) {
return;
}
const deadPairsIndexes = [];
let pair;
let target;
for (let i = 0; i < targetHandlerPairList.length; i++) {
pair = targetHandlerPairList[i];
target = pair.tagetRef.get();
if (target) {
pair.handler.call(target, eventData);
}
else {
deadPairsIndexes.push(i);
}
}
const deadPairsIndexes = [];
let pair;
let target;
for (let i = 0; i < targetHandlerPairList.length; i++) {
pair = targetHandlerPairList[i];
target = pair.tagetRef.get();
if (target) {
pair.handler.call(target, eventData);
} else {
deadPairsIndexes.push(i);
}
}
if (deadPairsIndexes.length === targetHandlerPairList.length) {
// There are no alive targets for this event - unsubscribe
source.removeEventListener(eventName, handlersForEventName.get(eventName));
sourceEventMap.delete(eventName);
}
else {
for (let j = deadPairsIndexes.length - 1; j >= 0; j--) {
targetHandlerPairList.splice(deadPairsIndexes[j], 1);
}
}
};
handlersForEventName.set(eventName, handler);
}
if (deadPairsIndexes.length === targetHandlerPairList.length) {
// There are no alive targets for this event - unsubscribe
source.removeEventListener(
eventName,
handlersForEventName.get(eventName)
);
sourceEventMap.delete(eventName);
} else {
for (let j = deadPairsIndexes.length - 1; j >= 0; j--) {
targetHandlerPairList.splice(deadPairsIndexes[j], 1);
}
}
};
handlersForEventName.set(eventName, handler);
}
return handler;
return handler;
}
function validateArgs(source: Observable, eventName: string, handler: (eventData: EventData) => void, target: any) {
if (!source) {
throw new Error("source is null or undefined");
}
function validateArgs(
source: Observable,
eventName: string,
handler: (eventData: EventData) => void,
target: any
) {
if (!source) {
throw new Error("source is null or undefined");
}
if (!target) {
throw new Error("target is null or undefined");
}
if (!target) {
throw new Error("target is null or undefined");
}
if (typeof eventName !== "string") {
throw new Error("eventName is not a string");
}
if (typeof eventName !== "string") {
throw new Error("eventName is not a string");
}
if (typeof handler !== "function") {
throw new Error("handler is not a function");
}
if (typeof handler !== "function") {
throw new Error("handler is not a function");
}
}
export function addWeakEventListener(source: Observable, eventName: string, handler: (eventData: EventData) => void, target: any) {
validateArgs(source, eventName, handler, target);
export function addWeakEventListener(
source: Observable,
eventName: string,
handler: (eventData: EventData) => void,
target: any
) {
validateArgs(source, eventName, handler, target);
let shouldAttach: boolean = false;
let shouldAttach: boolean = false;
let sourceEventMap = sourcesMap.get(source);
if (!sourceEventMap) {
sourceEventMap = new Map<string, Array<TargetHandlerPair>>();
sourcesMap.set(source, sourceEventMap);
shouldAttach = true;
}
let sourceEventMap = sourcesMap.get(source);
if (!sourceEventMap) {
sourceEventMap = new Map<string, Array<TargetHandlerPair>>();
sourcesMap.set(source, sourceEventMap);
shouldAttach = true;
}
let pairList = sourceEventMap.get(eventName);
if (!pairList) {
pairList = new Array<TargetHandlerPair>();
sourceEventMap.set(eventName, pairList);
shouldAttach = true;
}
let pairList = sourceEventMap.get(eventName);
if (!pairList) {
pairList = new Array<TargetHandlerPair>();
sourceEventMap.set(eventName, pairList);
shouldAttach = true;
}
pairList.push(new TargetHandlerPair(target, handler));
pairList.push(new TargetHandlerPair(target, handler));
if (shouldAttach) {
source.addEventListener(eventName, getHandlerForEventName(eventName));
}
if (shouldAttach) {
source.addEventListener(eventName, getHandlerForEventName(eventName));
}
}
export function removeWeakEventListener(source: Observable, eventName: string, handler: (eventData: EventData) => void, target: any) {
validateArgs(source, eventName, handler, target);
export function removeWeakEventListener(
source: Observable,
eventName: string,
handler: (eventData: EventData) => void,
target: any
) {
validateArgs(source, eventName, handler, target);
const handlerForEventWithName = handlersForEventName.get(eventName);
if (!handlerForEventWithName) {
// We have never created handler for event with this name;
return;
}
const handlerForEventWithName = handlersForEventName.get(eventName);
if (!handlerForEventWithName) {
// We have never created handler for event with this name;
return;
}
const sourceEventMap = sourcesMap.get(source);
if (!sourceEventMap) {
return;
}
const sourceEventMap = sourcesMap.get(source);
if (!sourceEventMap) {
return;
}
const targetHandlerPairList = sourceEventMap.get(eventName);
if (!targetHandlerPairList) {
return;
}
const targetHandlerPairList = sourceEventMap.get(eventName);
if (!targetHandlerPairList) {
return;
}
// Remove all pairs that match given target and handler or have a dead target
const targetHandlerPairsToRemove = [];
let pair;
let registeredTarget;
for (let i = 0; i < targetHandlerPairList.length; i++) {
pair = targetHandlerPairList[i];
// Remove all pairs that match given target and handler or have a dead target
const targetHandlerPairsToRemove = [];
let pair;
let registeredTarget;
for (let i = 0; i < targetHandlerPairList.length; i++) {
pair = targetHandlerPairList[i];
registeredTarget = pair.tagetRef.get();
if (!registeredTarget || (registeredTarget === target && handler === pair.handler)) {
targetHandlerPairsToRemove.push(i);
}
}
registeredTarget = pair.tagetRef.get();
if (
!registeredTarget ||
(registeredTarget === target && handler === pair.handler)
) {
targetHandlerPairsToRemove.push(i);
}
}
if (targetHandlerPairsToRemove.length === targetHandlerPairList.length) {
// There are no alive targets for this event - unsubscribe
source.removeEventListener(eventName, handlerForEventWithName);
sourceEventMap.delete(eventName);
}
else {
for (let j = targetHandlerPairsToRemove.length - 1; j >= 0; j--) {
targetHandlerPairList.splice(targetHandlerPairsToRemove[j], 1);
}
}
if (targetHandlerPairsToRemove.length === targetHandlerPairList.length) {
// There are no alive targets for this event - unsubscribe
source.removeEventListener(eventName, handlerForEventWithName);
sourceEventMap.delete(eventName);
} else {
for (let j = targetHandlerPairsToRemove.length - 1; j >= 0; j--) {
targetHandlerPairList.splice(targetHandlerPairsToRemove[j], 1);
}
}
}