UNPKG

react-nativescript

Version:
389 lines 13.7 kB
import { getViewClass, getViewMeta, normalizeElementName, } from './registry'; import { ELEMENT_REF } from './runtimeHelpers'; import { debug } from '../shared'; import { ObservableArray } from '@nativescript/core'; import { unsetValue } from '@nativescript/core/ui/core/properties'; /* * I had some difficulty importing this as: * import set from 'set-value'; * I believe that turning on `"esModuleInterop": true` in tsconfig.json would allow us to use the default import. * But maybe this is just a problem in the Webpack domain. * ... And later as: * import set = require('set-value'); */ import { default as set } from "set-value"; import { warn } from '../../shared/Logger'; let nodeId = 0; export class NSVNode { constructor(nodeType) { this.parentNode = null; this.childNodes = []; this.nextSibling = null; this.prevSibling = null; this.nodeType = nodeType; this.nodeId = nodeId++; } get text() { return this._text; } set text(t) { this._text = t; } get firstChild() { return this.childNodes.length ? this.childNodes[0] : null; } get lastChild() { return this.childNodes.length ? this.childNodes[this.childNodes.length - 1] : null; } toString() { return this.toString(); } } export class NSVElement extends NSVNode { constructor(tagName) { super("element" /* NSVNodeTypes.ELEMENT */); this._tagName = normalizeElementName(tagName); const viewClass = getViewClass(tagName); this._nativeView = new viewClass(); this._nativeView[ELEMENT_REF] = this; } get tagName() { return this._tagName; } get nativeView() { return this._nativeView; } get style() { return this.nativeView.style; } set style(inlineStyle) { this.nativeView.style = inlineStyle; } get text() { return this.nativeView.text; } set text(t) { super.text = t; this.nativeView.text = t; } get meta() { if (this._meta) { return this._meta; } return (this._meta = getViewMeta(this.tagName)); } get eventListeners() { if (!this._eventListeners) { this._eventListeners = new Map(); } return this._eventListeners; } addEventListener(event, handler, options = {}) { const { capture, once } = options; if (capture) { debug('Bubble propagation is not supported'); return; } if (once) { const oldHandler = handler; const self = this; handler = (...args) => { const res = oldHandler.call(null, ...args); if (res !== null) { self.removeEventListener(event, handler); } }; } this.nativeView.addEventListener(event, handler); this.eventListeners.set(event, handler); } removeEventListener(event, handler) { this.eventListeners.delete(event); this.nativeView.removeEventListener(event, handler); } dispatchEvent(event) { this.nativeView.notify({ eventName: event, object: this.nativeView }); } getAttribute(name) { return this.nativeView[name]; } setAttribute(name, value) { if (name === "nodeRole" && typeof value === "string") { this.nodeRole = value; return; } /** * The 'ios' and 'android' properties (e.g. on ActionItem) * are readonly, so we need to assign one level lower. */ if (name === "ios" && value) { Object.keys(value).forEach((key) => { set(this.nativeView.ios, key, value); }); return; } if (name === "android" && value) { Object.keys(value).forEach((key) => { set(this.nativeView.android, key, value); }); return; } set(this.nativeView, name, value); } removeAttribute(name) { if (name === "nodeRole") { this.nodeRole = void 0; return; } // potential issue: unsetValue is an empty object // not all properties/attributes may know/check for this set(this.nativeView, name, unsetValue); // originally we deleted the property, but in case of built-in properties // this would break them. For example, deleting the padding property // will prevent us from changing the padding once we deleted it // that's not the expected behaviour. // unset(this.nativeView, name) } insertBefore(el, anchor) { if (!anchor) { return this.appendChild(el); } const refIndex = this.childNodes.findIndex((node) => node.nodeId === anchor.nodeId); if (refIndex === -1) { return this.appendChild(el); } if (el.parentNode) { el.parentNode.removeChild(el); } this.childNodes.splice(refIndex, 0, el); el.parentNode = this; // find index to use for the native view, since non-visual nodes // (comment/text don't exist in the native view hierarchy) // todo: potentially refactor based on my benchmark: // https://www.measurethat.net/Benchmarks/Show/7450/0/filter-findindex const trueIndex = this.childNodes .filter((node) => node.nodeType === "element" /* NSVNodeTypes.ELEMENT */) .findIndex((node) => node.nodeId === el.nodeId); this.addChild(el, trueIndex); } appendChild(el) { this.childNodes.push(el); el.parentNode = this; this.addChild(el); } removeChild(el) { const index = this.childNodes.findIndex((node) => node.nodeId === el.nodeId); if (index > -1) { this.childNodes.splice(index, 1); el.parentNode = null; if (el.nodeType === "element" /* NSVNodeTypes.ELEMENT */) { removeChild(el, this); // Removing a child span takes us down here } else if (el.nodeType === "text" /* NSVNodeTypes.TEXT */) { this.updateText(); } } } // abstracted from appendChild, and insertBefore to avoid code duplication addChild(el, atIndex) { if (el.nodeType === "element" /* NSVNodeTypes.ELEMENT */) { addChild(el, this, atIndex); } else if (el.nodeType === "text" /* NSVNodeTypes.TEXT */) { this.updateText(); } } updateText() { this.setAttribute('text', this.childNodes .filter((node) => node.nodeType === "text" /* NSVNodeTypes.TEXT */) .reduce((text, currentNode) => { return text + currentNode.text; }, '')); } toString() { return "NSVElement:" + this.nativeView.toString(); } } export class NSVComment extends NSVNode { constructor(text) { super("comment" /* NSVNodeTypes.COMMENT */); this.text = text; } toString() { return "NSVComment:" + `"` + this.text + `"`; } } export class NSVText extends NSVNode { constructor(text) { super("text" /* NSVNodeTypes.TEXT */); this.text = text; } toString() { return "NSVText:" + `"` + this.text + `"`; } } export class NSVRoot extends NSVNode { constructor() { super("root" /* NSVNodeTypes.ROOT */); } setBaseRef(el) { // console.log(`NSVRoot->appendChild(${el.nodeType})`) if (el instanceof NSVElement) { this.baseRef = el; } // no-op } toString() { if (this.baseRef) { return "NSVRoot:" + this.baseRef.toString(); } else { return "NSVRoot:" + "null"; } } } function addChild(child, parent, atIndex) { if (__TEST__) return; // debug( // `...addChild( ${child.tagName}(${child.nodeId}), ${parent.tagName}(${ // parent.nodeId // }), ${atIndex} )` // ) if (child.meta.viewFlags & 1 /* NSVViewFlags.SKIP_ADD_TO_DOM */) { // debug('SKIP_ADD_TO_DOM') return; } const parentView = parent.nativeView; const childView = child.nativeView; if (parent.meta.viewFlags & 8 /* NSVViewFlags.NO_CHILDREN */) { // debug('NO_CHILDREN') return; } if (parent.meta.nodeOps) { return parent.meta.nodeOps.insert(child, parent, atIndex); } const nodeRole = child.nodeRole; if (nodeRole) { return addChildByNodeRole(nodeRole, childView, parentView, atIndex); } if (parent.meta.viewFlags & 4 /* NSVViewFlags.LAYOUT_VIEW */) { if (atIndex) { parentView.insertChild(childView, atIndex); } else { parentView.addChild(childView); } } else if (parent.meta.viewFlags & 2 /* NSVViewFlags.CONTENT_VIEW */) { parentView.content = childView; } else { parentView._addChildFromBuilder(childView.constructor.name, childView); } } function removeChild(child, parent) { if (__TEST__) return; // debug( // `...removeChild( ${child.tagName}(${child.nodeId}), ${parent.tagName}(${ // parent.nodeId // }) )` // ) if (child.meta.viewFlags & 1 /* NSVViewFlags.SKIP_ADD_TO_DOM */) { // debug('SKIP_ADD_TO_DOM') return; } if (parent.meta.viewFlags & 8 /* NSVViewFlags.NO_CHILDREN */) { // debug('NO_CHILDREN') return; } if (parent.meta.nodeOps) { return parent.meta.nodeOps.remove(child, parent); } const parentView = parent.nativeView; const childView = child.nativeView; const nodeRole = child.nodeRole; if (nodeRole) { return removeChildByNodeRole(nodeRole, childView, parentView); } if (parent.meta.viewFlags & 4 /* NSVViewFlags.LAYOUT_VIEW */) { parentView.removeChild(childView); } else if (parent.meta.viewFlags & 2 /* NSVViewFlags.CONTENT_VIEW */) { parentView.content = null; } else { // Removing a child span takes us down here parentView._removeView(childView); } } function addChildByNodeRole(nodeRole, childView, parentView, atIndex) { const childrenSetter = parentView[nodeRole]; if (typeof childrenSetter !== "undefined" && typeof childrenSetter.length !== "undefined") { // Treat as if it's an array. const childrenSetterLength = parentView[nodeRole].length; const atSafeIndex = typeof atIndex === "undefined" ? childrenSetterLength : atIndex; if (childrenSetter instanceof ObservableArray) { parentView[nodeRole].splice(atSafeIndex, 0, childView); } else if (Array.isArray(childrenSetter)) { parentView[nodeRole] = [...parentView[nodeRole]].splice(atSafeIndex, 0, childView); } else { if (__DEV__) { warn(`parentView "${parentView.constructor.name}" had a value for nodeRole "${nodeRole}" ` + `that had a "length" property yet did not conform to Array or ObservableArray. Cannot add child. ` + `Please explicitly implement nodeOps.insert() for the parentView.`); } } } else { /* * Treat it as if it's simply a setter. * This assumes (quite fairly) that the plugin author is not delegating to us the responsibility * of initialising an array for childrenSetter. */ parentView[nodeRole] = childView; } } function removeChildByNodeRole(nodeRole, childView, parentView) { const childrenSetter = parentView[nodeRole]; if (typeof childrenSetter !== "undefined" && typeof childrenSetter.indexOf === "function") { // Treat as if it's an array. const childIndex = parentView[nodeRole].indexOf(childView); if (childrenSetter instanceof ObservableArray) { parentView[nodeRole].splice(childIndex, 1); } else if (Array.isArray(childrenSetter)) { parentView[nodeRole] = [...parentView[nodeRole]].splice(childIndex, 1); } else { if (__DEV__) { warn(`parentView "${parentView.constructor.name}" had a value for nodeRole "${nodeRole}" ` + `that had an "indexOf" property yet did not conform to Array or ObservableArray. Cannot add childView "${childView.constructor.name}". ` + `Please explicitly implement nodeOps.remove() for the parentView.`); } } } else { /* * Treat it as if it's simply a setter. * We can't use unsetValue here, because the childrenSetter is not necessarily a Property (which indeed is the case for FormattedString.spans). * TODO: If there's a way to determine whether the childrenSetter is a Property, I'd be very happy to run that first check and use unsetValue. */ const defaultValueForChildrenSetter = parentView.__proto__[nodeRole]; try { parentView[nodeRole] = defaultValueForChildrenSetter; } catch (e) { if (__DEV__) { warn(`parentView "${parentView.constructor.name}" failed to remove childView "${childView.constructor.name}", given nodeRole "${nodeRole}" ` + `Please explicitly implement nodeOps.remove() for the parentView.`); } } } } //# sourceMappingURL=nodes.js.map