UNPKG

@nativescript/core

Version:

A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.

1,175 lines • 41.3 kB
import { Property, InheritedProperty, clearInheritedProperties, propagateInheritableProperties, propagateInheritableCssProperties, initNativeView } from '../properties'; import { CSSUtils } from '../../../css/system-classes'; import { Source } from '../../../utils/debug'; import { Binding } from '../bindable'; import { Trace } from '../../../trace'; import { Observable, WrappedValue } from '../../../data/observable'; import { Style } from '../../styling/style'; import { paddingTopProperty, paddingRightProperty, paddingBottomProperty, paddingLeftProperty } from '../../styling/style-properties'; // TODO: Remove this import! import { getClass } from '../../../utils/types'; import { profile } from '../../../profiling'; import * as ssm from '../../styling/style-scope'; let domNodeModule; function ensuredomNodeModule() { if (!domNodeModule) { domNodeModule = require('../../../debugger/dom-node'); } } let styleScopeModule; function ensureStyleScopeModule() { if (!styleScopeModule) { styleScopeModule = require('../../styling/style-scope'); } } const defaultBindingSource = {}; /** * Gets an ancestor from a given type. * @param view - Starting view (child view). * @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, criterion) { let matcher = null; if (typeof criterion === 'string') { matcher = (view) => view.typeName === criterion; } else { matcher = (view) => view instanceof criterion; } for (let parent = view.parent; parent != null; parent = parent.parent) { if (matcher(parent)) { return parent; } } return null; } /** * Gets a child view by id. * @param view - The parent (container) view of the view to look for. * @param id - The id of the view to look for. * Returns an instance of a view (if found), otherwise undefined. */ export function getViewById(view, id) { if (!view) { return undefined; } if (view.id === id) { return view; } let retVal; const descendantsCallback = function (child) { if (child.id === id) { retVal = child; // break the iteration by returning false return false; } return true; }; eachDescendant(view, descendantsCallback); return retVal; } /** * Gets a child view by domId. * @param view - The parent (container) view of the view to look for. * @param domId - The id of the view to look for. * Returns an instance of a view (if found), otherwise undefined. */ export function getViewByDomId(view, domId) { if (!view) { return undefined; } if (view._domId === domId) { return view; } let retVal; const descendantsCallback = function (child) { if (view._domId === domId) { retVal = child; // break the iteration by returning false return false; } return true; }; eachDescendant(view, descendantsCallback); return retVal; } // TODO: allow all selector types (just using attributes now) /** * Gets a child view by selector. * @param view - The parent (container) view of the view to look for. * @param selector - The selector of the view to look for. * Returns an instance of a view (if found), otherwise undefined. */ export function querySelectorAll(view, selector) { if (!view) { return []; } const retVal = []; if (view[selector]) { retVal.push(view); } const descendantsCallback = function (child) { if (child[selector]) { retVal.push(child); } return true; }; eachDescendant(view, descendantsCallback); return retVal; } /** * Iterates through all child views (via visual tree) and executes a function. * @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, callback) { if (!callback || !view) { return; } let continueIteration; const localCallback = function (child) { continueIteration = callback(child); if (continueIteration) { child.eachChild(localCallback); } return continueIteration; }; view.eachChild(localCallback); } let viewIdCounter = 1; // const contextMap = new WeakMap<Object, Map<string, WeakRef<Object>[]>>(); // function getNativeView(context: Object, typeName: string): Object { // let typeMap = contextMap.get(context); // if (!typeMap) { // typeMap = new Map<string, WeakRef<Object>[]>(); // contextMap.set(context, typeMap); // return undefined; // } // const array = typeMap.get(typeName); // if (array) { // let nativeView; // while (array.length > 0) { // const weakRef = array.pop(); // nativeView = weakRef.get(); // if (nativeView) { // return nativeView; // } // } // } // return undefined; // } // function putNativeView(context: Object, view: ViewBase): void { // const typeMap = contextMap.get(context); // const typeName = view.typeName; // let list = typeMap.get(typeName); // if (!list) { // list = []; // typeMap.set(typeName, list); // } // list.push(new WeakRef(view.nativeViewProtected)); // } var Flags; (function (Flags) { Flags["superOnLoadedCalled"] = "Loaded"; Flags["superOnUnloadedCalled"] = "Unloaded"; })(Flags || (Flags = {})); var SuspendType; (function (SuspendType) { SuspendType[SuspendType["Incremental"] = 0] = "Incremental"; SuspendType[SuspendType["Loaded"] = 1048576] = "Loaded"; SuspendType[SuspendType["NativeView"] = 2097152] = "NativeView"; SuspendType[SuspendType["UISetup"] = 4194304] = "UISetup"; SuspendType[SuspendType["IncrementalCountMask"] = -7340033] = "IncrementalCountMask"; })(SuspendType || (SuspendType = {})); (function (SuspendType) { function toString(type) { return (type ? 'suspended' : 'resumed') + '(' + 'Incremental: ' + (type & SuspendType.IncrementalCountMask) + ', ' + 'Loaded: ' + !(type & SuspendType.Loaded) + ', ' + 'NativeView: ' + !(type & SuspendType.NativeView) + ', ' + 'UISetup: ' + !(type & SuspendType.UISetup) + ')'; } SuspendType.toString = toString; })(SuspendType || (SuspendType = {})); const DEFAULT_VIEW_PADDINGS = new Map(); /** * * @nsView ViewBase */ export class ViewBase extends Observable { constructor() { super(); this._onLoadedCalled = false; this._onUnloadedCalled = false; /** * Default visual state, defaults to 'normal' * * @nsProperty */ this.defaultVisualState = 'normal'; /* "ui/styling/style-scope" */ this._cssState = new ssm.CssState(new WeakRef(this)); this.pseudoClassAliases = { highlighted: ['active', 'pressed'], }; /** * if _setupAsRootView is called it means it is not supposed to be * added to a parent. However parent can be set before for the purpose * of CSS variables/classes. That variable ensures that _addViewToNativeVisualTree * is not called in _setupAsRootView */ this.mIsRootView = false; this._domId = viewIdCounter++; this._style = new Style(new WeakRef(this)); this.cssClasses = new Set(); this.cssPseudoClasses = new Set(); this.cssPseudoClasses.add(this.defaultVisualState); this.notify({ eventName: ViewBase.createdEvent, type: this.constructor.name, object: this }); } // Used in Angular. TODO: remove from here /** * Gets the template parent or the native parent. Sets the template parent. */ get parentNode() { return this._templateParent || this.parent; } set parentNode(node) { this._templateParent = node; } get nativeView() { // this._disableNativeViewRecycling = true; return this.nativeViewProtected; } set nativeView(value) { this.setNativeView(value); } // TODO: Use Type.prototype.typeName instead. /** * Gets the name of the constructor function for this instance. E.g. for a Button class this will return "Button". */ get typeName() { return getClass(this); } /** * Gets the style object associated to this view. */ get style() { return this._style; } /** * * @nsProperty */ set style(inlineStyle /* | string */) { if (typeof inlineStyle === 'string') { this.setInlineStyle(inlineStyle); } else { throw new Error('View.style property is read-only.'); } } get android() { // this._disableNativeViewRecycling = true; return this._androidView; } get ios() { // this._disableNativeViewRecycling = true; return this._iosView; } get isLoaded() { return this._isLoaded; } get ['class']() { return this.className; } set ['class'](v) { this.className = v; } /** * Returns the child view with the specified id. */ getViewById(id) { return getViewById(this, id); } /** * Returns the child view with the specified domId. */ getViewByDomId(domId) { return getViewByDomId(this, domId); } /** * Gets owner page. This is a read-only property. */ get page() { if (this.parent) { return this.parent.page; } return null; } /** * @unstable * Ensures a dom-node for this view. */ ensureDomNode() { if (!this.domNode) { ensuredomNodeModule(); this.domNode = new domNodeModule.DOMNode(this); } } // Overridden so we don't raise `propertyChange` // The property will raise its own event. set(name, value) { this[name] = WrappedValue.unwrap(value); } onLoaded() { this.setFlag(Flags.superOnLoadedCalled, true); if (this._isLoaded) { return; } this._isLoaded = true; this._cssState.onLoaded(); this._resumeNativeUpdates(SuspendType.Loaded); this.eachChild((child) => { this.loadView(child); return true; }); this._emit('loaded'); } onUnloaded() { this.setFlag(Flags.superOnUnloadedCalled, true); if (!this._isLoaded) { return; } this._suspendNativeUpdates(SuspendType.Loaded); this.eachChild((child) => { this.unloadView(child); return true; }); this._isLoaded = false; this._cssState.onUnloaded(); this._emit('unloaded'); } _layoutParent() { if (this.parent) { this.parent._layoutParent(); } } _suspendNativeUpdates(type) { if (type) { this._suspendNativeUpdatesCount = this._suspendNativeUpdatesCount | type; } else { this._suspendNativeUpdatesCount++; } } _resumeNativeUpdates(type) { if (type) { this._suspendNativeUpdatesCount = this._suspendNativeUpdatesCount & ~type; } else { if ((this._suspendNativeUpdatesCount & SuspendType.IncrementalCountMask) === 0) { throw new Error(`Invalid call to ${this}._resumeNativeUpdates`); } this._suspendNativeUpdatesCount--; } if (!this._suspendNativeUpdatesCount) { this.onResumeNativeUpdates(); } } /** * Allow multiple updates to be performed on the instance at once. */ _batchUpdate(callback) { try { this._suspendNativeUpdates(SuspendType.Incremental); return callback(); } finally { this._resumeNativeUpdates(SuspendType.Incremental); } } setFlag(flag, value) { switch (flag) { case Flags.superOnLoadedCalled: this._onLoadedCalled = value; break; case Flags.superOnUnloadedCalled: this._onUnloadedCalled = value; break; } } isFlagSet(flag) { switch (flag) { case Flags.superOnLoadedCalled: return this._onLoadedCalled; case Flags.superOnUnloadedCalled: return this._onUnloadedCalled; } } callFunctionWithSuper(flag, func) { this.setFlag(flag, false); func(); if (!this.isFlagSet(flag)) { throw new Error(`super.${flag} not called in ${this}`); } } callLoaded() { this.callFunctionWithSuper(Flags.superOnLoadedCalled, () => this.onLoaded()); } callUnloaded() { this.callFunctionWithSuper(Flags.superOnUnloadedCalled, () => this.onUnloaded()); } notifyPseudoClassChanged(pseudoClass) { this.notify({ eventName: ':' + pseudoClass, object: this }); } getAllAliasedStates(name) { const allStates = [name]; if (name in this.pseudoClassAliases) { for (let i = 0, length = this.pseudoClassAliases[name].length; i < length; i++) { allStates.push(this.pseudoClassAliases[name][i]); } } return allStates; } /** * @protected * @unstable * A widget can call this method to add a matching css pseudo class. */ addPseudoClass(name) { const allStates = this.getAllAliasedStates(name); for (let i = 0, length = allStates.length; i < length; i++) { if (!this.cssPseudoClasses.has(allStates[i])) { this.cssPseudoClasses.add(allStates[i]); this.notifyPseudoClassChanged(allStates[i]); } } } /** * @protected * @unstable * A widget can call this method to discard matching css pseudo class. */ deletePseudoClass(name) { const allStates = this.getAllAliasedStates(name); for (let i = 0, length = allStates.length; i < length; i++) { if (this.cssPseudoClasses.has(allStates[i])) { this.cssPseudoClasses.delete(allStates[i]); this.notifyPseudoClassChanged(allStates[i]); } } } bindingContextChanged(data) { this.bindings.get('bindingContext').bind(data.value); } bind(options, source = defaultBindingSource) { const targetProperty = options.targetProperty; this.unbind(targetProperty); if (!this.bindings) { this.bindings = new Map(); } const binding = new Binding(this, options); this.bindings.set(targetProperty, binding); let bindingSource = source; if (bindingSource === defaultBindingSource) { bindingSource = this.bindingContext; binding.sourceIsBindingContext = true; if (targetProperty === 'bindingContext') { this.bindingContextBoundToParentBindingContextChanged = true; const parent = this.parent; if (parent) { parent.on('bindingContextChange', this.bindingContextChanged, this); } else { this.shouldAddHandlerToParentBindingContextChanged = true; } } } binding.bind(bindingSource); } unbind(property) { const bindings = this.bindings; if (!bindings) { return; } const binding = bindings.get(property); if (binding) { binding.unbind(); bindings.delete(property); if (binding.sourceIsBindingContext) { if (property === 'bindingContext') { this.shouldAddHandlerToParentBindingContextChanged = false; this.bindingContextBoundToParentBindingContextChanged = false; const parent = this.parent; if (parent) { parent.off('bindingContextChange', this.bindingContextChanged, this); } } } } } performLayout(currentRun = 0) { // if there's an animation in progress we need to delay the layout // we've added a guard of 5000 milliseconds execution // to make sure that the layout will happen even if the animation haven't finished in 5 seconds if (this._shouldDelayLayout() && currentRun < 100) { setTimeout(() => this.performLayout(currentRun), currentRun); currentRun++; } else { this.parent.requestLayout(); } } /** * Invalidates the layout of the view and triggers a new layout pass. */ requestLayout() { // Default implementation for non View instances (like TabViewItem). const parent = this.parent; if (parent) { this.performLayout(); } } /** * Iterates over children of type ViewBase. * @param callback Called for each child of type ViewBase. Iteration stops if this method returns falsy value. */ eachChild(callback) { // } _inheritStyles(view) { propagateInheritableProperties(this, view); view._inheritStyleScope(this._styleScope); propagateInheritableCssProperties(this.style, view.style); } _addView(view, atIndex) { if (Trace.isEnabled()) { Trace.write(`${this}._addView(${view}, ${atIndex})`, Trace.categories.ViewHierarchy); } if (!view) { throw new Error('Expecting a valid View instance.'); } if (!(view instanceof ViewBase)) { throw new Error(view + ' is not a valid View instance.'); } if (view.parent) { throw new Error('View already has a parent. View: ' + view + ' Parent: ' + view.parent); } view.parent = this; this._addViewCore(view, atIndex); view._parentChanged(null); if (this.domNode) { this.domNode.onChildAdded(view); } } /** * Method is intended to be overridden by inheritors and used as "protected" */ _addViewCore(view, atIndex) { this._inheritStyles(view); if (this._context) { view._setupUI(this._context, atIndex); } if (this._isLoaded) { this.loadView(view); } } /** * Load view. * @param view to load. */ loadView(view) { if (view && !view.isLoaded) { view.callLoaded(); } } /** * When returning true the callLoaded method will be run in setTimeout * Method is intended to be overridden by inheritors and used as "protected" */ _shouldDelayLayout() { return false; } /** * Unload view. * @param view to unload. */ unloadView(view) { if (view && view.isLoaded) { view.callUnloaded(); } } /** * Core logic for removing a child view from this instance. Used by the framework to handle lifecycle events more centralized. Do not use outside the UI Stack implementation. */ _removeView(view) { if (Trace.isEnabled()) { Trace.write(`${this}._removeView(${view})`, Trace.categories.ViewHierarchy); } if (view.parent !== this) { throw new Error('View not added to this instance. View: ' + view + ' CurrentParent: ' + view.parent + ' ExpectedParent: ' + this); } if (this.domNode) { this.domNode.onChildRemoved(view); } this._removeViewCore(view); view.parent = undefined; view._parentChanged(this); } /** * Method is intended to be overridden by inheritors and used as "protected" */ _removeViewCore(view) { this.unloadView(view); if (view._context) { view._tearDownUI(); } } /** * Creates a native view. * Returns either android.view.View or UIView. */ createNativeView() { return undefined; } /** * Clean up references to the native view. */ disposeNativeView() { // Unset these values so that view checks for resize the next time it's added back to view tree this._oldLeft = 0; this._oldTop = 0; this._oldRight = 0; this._oldBottom = 0; this.notify({ eventName: ViewBase.disposeNativeViewEvent, object: this, }); } /** * Initializes properties/listeners of the native view. */ initNativeView() { // } /** * Resets properties/listeners set to the native view. */ resetNativeView() { // } resetNativeViewInternal() { // const nativeView = this.nativeViewProtected; // if (nativeView && __ANDROID__) { // const recycle = this.recycleNativeView; // if (recycle === "always" || (recycle === "auto" && !this._disableNativeViewRecycling)) { // resetNativeView(this); // if (this._isPaddingRelative) { // nativeView.setPaddingRelative(this._defaultPaddingLeft, this._defaultPaddingTop, this._defaultPaddingRight, this._defaultPaddingBottom); // } else { // nativeView.setPadding(this._defaultPaddingLeft, this._defaultPaddingTop, this._defaultPaddingRight, this._defaultPaddingBottom); // } // this.resetNativeView(); // } // } // if (this._cssState) { // this._cancelAllAnimations(); // } } _setupAsRootView(context) { this.mIsRootView = true; this._setupUI(context); this.mIsRootView = false; } /** * Setups the UI for ViewBase and all its children recursively. * This method should *not* be overridden by derived views. */ _setupUI(context /* android.content.Context */, atIndex, parentIsLoaded) { if (this._context === context) { // this check is unnecessary as this function should never be called when this._context === context as it means the view was somehow detached, // which is only possible by setting reusable = true. Adding it either way for feature flag safety if (this.reusable) { if (!this.mIsRootView && this.parent && !this._isAddedToNativeVisualTree) { const nativeIndex = this.parent._childIndexToNativeChildIndex(atIndex); this._isAddedToNativeVisualTree = this.parent._addViewToNativeVisualTree(this, nativeIndex); } } return; } else if (this._context) { this._tearDownUI(true); } this._context = context; // This will account for nativeView that is created in createNativeView, recycled // or for backward compatibility - set before _setupUI in iOS constructor. let nativeView = this.nativeViewProtected; // if (__ANDROID__) { // const recycle = this.recycleNativeView; // if (recycle === "always" || (recycle === "auto" && !this._disableNativeViewRecycling)) { // nativeView = <android.view.View>getNativeView(context, this.typeName); // } // } if (!nativeView) { nativeView = this.createNativeView(); } if (__ANDROID__) { // this check is also unecessary as this code should never be reached with _androidView != null unless reusable = true // also adding this check for feature flag safety if (this._androidView !== nativeView || !this.reusable) { this._androidView = nativeView; if (nativeView) { const className = this.constructor.name; if (this._isPaddingRelative === undefined) { this._isPaddingRelative = nativeView.isPaddingRelative(); } let result /* android.graphics.Rect */ = DEFAULT_VIEW_PADDINGS.get(className) || nativeView.defaultPaddings; if (result === undefined) { DEFAULT_VIEW_PADDINGS.set(className, org.nativescript.widgets.ViewHelper.getPadding(nativeView)); nativeView.defaultPaddings = DEFAULT_VIEW_PADDINGS.get(className); result = DEFAULT_VIEW_PADDINGS.get(className); } if (!nativeView.defaultPaddings) { nativeView.defaultPaddings = DEFAULT_VIEW_PADDINGS.get(className); } this._defaultPaddingTop = result.top; this._defaultPaddingRight = result.right; this._defaultPaddingBottom = result.bottom; this._defaultPaddingLeft = result.left; const style = this.style; if (!paddingTopProperty.isSet(style)) { this.effectivePaddingTop = this._defaultPaddingTop; } if (!paddingRightProperty.isSet(style)) { this.effectivePaddingRight = this._defaultPaddingRight; } if (!paddingBottomProperty.isSet(style)) { this.effectivePaddingBottom = this._defaultPaddingBottom; } if (!paddingLeftProperty.isSet(style)) { this.effectivePaddingLeft = this._defaultPaddingLeft; } } } } else { this._iosView = nativeView; } this.setNativeView(nativeView); if (!this.mIsRootView && this.parent) { const nativeIndex = this.parent._childIndexToNativeChildIndex(atIndex); this._isAddedToNativeVisualTree = this.parent._addViewToNativeVisualTree(this, nativeIndex); } this._resumeNativeUpdates(SuspendType.UISetup); this.eachChild((child) => { child._setupUI(context); return true; }); } /** * 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(value) { if (this.__nativeView === value) { return; } if (this.__nativeView) { this._suspendNativeUpdates(SuspendType.NativeView); // We may do a `this.resetNativeView()` here? } this.__nativeView = this.nativeViewProtected = value; if (this.__nativeView) { this._suspendedUpdates = undefined; this.initNativeView(); this._resumeNativeUpdates(SuspendType.NativeView); } } /** * Tears down the UI of a reusable node by making it no longer reusable. * @see _tearDownUI * @param forceDestroyChildren Force destroy the children (even if they are reusable) */ destroyNode(forceDestroyChildren) { this.reusable = false; this.callUnloaded(); this._tearDownUI(forceDestroyChildren); } /** * Tears down the UI for ViewBase and all its children recursively. * This method should *not* be overridden by derived views. */ _tearDownUI(force) { // No context means we are already teared down. if (!this._context) { return; } const preserveNativeView = this.reusable && !force; this.resetNativeViewInternal(); if (!preserveNativeView) { this.eachChild((child) => { child._tearDownUI(force); return true; }); } if (this.parent) { this.parent._removeViewFromNativeVisualTree(this); } // const nativeView = this.nativeViewProtected; // if (nativeView && __ANDROID__) { // const recycle = this.recycleNativeView; // let shouldRecycle = false; // if (recycle === "always") { // shouldRecycle = true; // } else if (recycle === "auto" && !this._disableNativeViewRecycling) { // const propertiesSet = Object.getOwnPropertySymbols(this).length + Object.getOwnPropertySymbols(this.style).length / 2; // shouldRecycle = propertiesSet <= this.recyclePropertyCounter; // } // // const nativeParent = __ANDROID__ ? (<android.view.View>nativeView).getParent() : (<UIView>nativeView).superview; // const nativeParent = (<android.view.View>nativeView).getParent(); // const animation = (<android.view.View>nativeView).getAnimation(); // if (shouldRecycle && !nativeParent && !animation) { // putNativeView(this._context, this); // } // } if (!preserveNativeView) { this.disposeNativeView(); this._suspendNativeUpdates(SuspendType.UISetup); this.setNativeView(null); this._androidView = null; this._iosView = null; this._context = null; } if (this.domNode) { this.domNode.dispose(); this.domNode = undefined; } } _childIndexToNativeChildIndex(index) { return index; } /** * 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. * Method is intended to be overridden by inheritors and used as "protected". */ _addViewToNativeVisualTree(view, atIndex) { if (view._isAddedToNativeVisualTree) { throw new Error('Child already added to the native visual tree.'); } return true; } /** * Method is intended to be overridden by inheritors and used as "protected" */ _removeViewFromNativeVisualTree(view) { view._isAddedToNativeVisualTree = false; } /** * @deprecated */ get visualState() { return this._visualState; } _addVisualState(state) { this.deletePseudoClass(this.defaultVisualState); this.addPseudoClass(state); } _removeVisualState(state) { this.deletePseudoClass(state); if (!this.cssPseudoClasses.size) { this.addPseudoClass(this.defaultVisualState); } } /** * @deprecated Use View._addVisualState() and View._removeVisualState() instead. */ _goToVisualState(state) { console.log('_goToVisualState() is deprecated. Use View._addVisualState() and View._removeVisualState() instead.'); if (Trace.isEnabled()) { Trace.write(this + ' going to state: ' + state, Trace.categories.Style); } if (state === this._visualState) { return; } this.deletePseudoClass(this._visualState); this._visualState = state; this.addPseudoClass(state); } /** * @deprecated * * This used to be the way to set attribute values in early {N} versions. * Now attributes are expected to be set as plain properties on the view instances. */ _applyXmlAttribute(attribute, value) { console.log('ViewBase._applyXmlAttribute(...) is deprecated; set attributes as plain properties instead'); if (attribute === 'style' || attribute === 'rows' || attribute === 'columns' || attribute === 'fontAttributes') { this[attribute] = value; return true; } return false; } setInlineStyle(style) { if (typeof style !== 'string') { throw new Error('Parameter should be valid CSS string!'); } ensureStyleScopeModule(); styleScopeModule.applyInlineStyle(this, style, undefined); } _parentChanged(oldParent) { const newParent = this.parent; //Overridden if (oldParent) { clearInheritedProperties(this); if (this.bindingContextBoundToParentBindingContextChanged) { oldParent.off('bindingContextChange', this.bindingContextChanged, this); } } else if (this.shouldAddHandlerToParentBindingContextChanged) { newParent.on('bindingContextChange', this.bindingContextChanged, this); this.bindings.get('bindingContext').bind(newParent.bindingContext); } } onResumeNativeUpdates() { // Apply native setters... initNativeView(this, undefined, undefined); } toString() { let str = this.typeName; if (this.id) { str += `<${this.id}>`; } else { str += `(${this._domId})`; } const source = Source.get(this); if (source) { str += `@${source};`; } return str; } /** * @private * Notifies each child's css state for change, recursively. * Either the style scope, className or id properties were changed. */ _onCssStateChange() { this._cssState.onChange(); eachDescendant(this, (child) => { child._cssState.onChange(); return true; }); } _inheritStyleScope(styleScope) { // If we are styleScope don't inherit parent stylescope. // TODO: Consider adding parent scope and merge selectors. if (this._isStyleScopeHost) { return; } if (this._styleScope !== styleScope) { this._styleScope = styleScope; this._onCssStateChange(); this.eachChild((child) => { child._inheritStyleScope(styleScope); return true; }); } } showModal(moduleOrView, modalOptions) { const parent = this.parent; return parent && parent.showModal(moduleOrView, modalOptions); } /** * 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(...args) { const parent = this.parent; if (parent) { parent.closeModal(...args); } } /** * Method is intended to be overridden by inheritors and used as "protected" */ _dialogClosed() { eachDescendant(this, (child) => { child._dialogClosed(); return true; }); } /** * Method is intended to be overridden by inheritors and used as "protected" */ _onRootViewReset() { eachDescendant(this, (child) => { child._onRootViewReset(); return true; }); } } /** * String value used when hooking to loaded event. * * @nsEvent loaded */ ViewBase.loadedEvent = 'loaded'; /** * String value used when hooking to unloaded event. * * @nsEvent unloaded */ ViewBase.unloadedEvent = 'unloaded'; /** * String value used when hooking to creation event * * @nsEvent created */ ViewBase.createdEvent = 'created'; /** * String value used when hooking to disposeNativeView event * * @nsEvent disposeNativeView */ ViewBase.disposeNativeViewEvent = 'disposeNativeView'; __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], ViewBase.prototype, "onLoaded", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], ViewBase.prototype, "onUnloaded", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [String]), __metadata("design:returntype", void 0) ], ViewBase.prototype, "addPseudoClass", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [String]), __metadata("design:returntype", void 0) ], ViewBase.prototype, "deletePseudoClass", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], ViewBase.prototype, "requestLayout", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [ViewBase, Number]), __metadata("design:returntype", void 0) ], ViewBase.prototype, "_addView", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Number, Boolean]), __metadata("design:returntype", void 0) ], ViewBase.prototype, "_setupUI", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [Boolean]), __metadata("design:returntype", void 0) ], ViewBase.prototype, "_tearDownUI", null); ViewBase.prototype.isCollapsed = false; ViewBase.prototype._oldLeft = 0; ViewBase.prototype._oldTop = 0; ViewBase.prototype._oldRight = 0; ViewBase.prototype._oldBottom = 0; ViewBase.prototype.effectiveMinWidth = 0; ViewBase.prototype.effectiveMinHeight = 0; ViewBase.prototype.effectiveWidth = 0; ViewBase.prototype.effectiveHeight = 0; ViewBase.prototype.effectiveMarginTop = 0; ViewBase.prototype.effectiveMarginRight = 0; ViewBase.prototype.effectiveMarginBottom = 0; ViewBase.prototype.effectiveMarginLeft = 0; ViewBase.prototype.effectivePaddingTop = 0; ViewBase.prototype.effectivePaddingRight = 0; ViewBase.prototype.effectivePaddingBottom = 0; ViewBase.prototype.effectivePaddingLeft = 0; ViewBase.prototype.effectiveBorderTopWidth = 0; ViewBase.prototype.effectiveBorderRightWidth = 0; ViewBase.prototype.effectiveBorderBottomWidth = 0; ViewBase.prototype.effectiveBorderLeftWidth = 0; ViewBase.prototype._defaultPaddingTop = 0; ViewBase.prototype._defaultPaddingRight = 0; ViewBase.prototype._defaultPaddingBottom = 0; ViewBase.prototype._defaultPaddingLeft = 0; ViewBase.prototype._isViewBase = true; ViewBase.prototype.recycleNativeView = 'never'; ViewBase.prototype.reusable = false; ViewBase.prototype._suspendNativeUpdatesCount = SuspendType.Loaded | SuspendType.NativeView | SuspendType.UISetup; export const bindingContextProperty = new InheritedProperty({ name: 'bindingContext', }); bindingContextProperty.register(ViewBase); export const hiddenProperty = new Property({ name: 'hidden', defaultValue: false, affectsLayout: __APPLE__, valueConverter: booleanConverter, valueChanged: (target, oldValue, newValue) => { if (target) { target.isCollapsed = !!newValue; } }, }); hiddenProperty.register(ViewBase); export const classNameProperty = new Property({ name: 'className', valueChanged(view, oldValue, newValue) { const cssClasses = view.cssClasses; const rootViewsCssClasses = CSSUtils.getSystemCssClasses(); const shouldAddModalRootViewCssClasses = cssClasses.has(CSSUtils.MODAL_ROOT_VIEW_CSS_CLASS); const shouldAddRootViewCssClasses = cssClasses.has(CSSUtils.ROOT_VIEW_CSS_CLASS); cssClasses.clear(); if (shouldAddModalRootViewCssClasses) { cssClasses.add(CSSUtils.MODAL_ROOT_VIEW_CSS_CLASS); } else if (shouldAddRootViewCssClasses) { cssClasses.add(CSSUtils.ROOT_VIEW_CSS_CLASS); } rootViewsCssClasses.forEach((c) => cssClasses.add(c)); if (typeof newValue === 'string' && newValue !== '') { newValue.split(' ').forEach((c) => cssClasses.add(c)); } view._onCssStateChange(); }, }); classNameProperty.register(ViewBase); export const idProperty = new Property({ name: 'id', valueChanged: (view, oldValue, newValue) => view._onCssStateChange(), }); idProperty.register(ViewBase); export const defaultVisualStateProperty = new Property({ name: 'defaultVisualState', defaultValue: 'normal', valueChanged(target, oldValue, newValue) { const value = newValue || 'normal'; // Append new default if old one is currently applied if (target.cssPseudoClasses && target.cssPseudoClasses.has(oldValue)) { target.deletePseudoClass(oldValue); target.addPseudoClass(newValue); } }, }); defaultVisualStateProperty.register(ViewBase); export function booleanConverter(v) { const lowercase = (v + '').toLowerCase(); if (lowercase === 'true') { return true; } else if (lowercase === 'false') { return false; } throw new Error(`Invalid boolean: ${v}`); } //# sourceMappingURL=index.js.map