From 8ed45b10a68241002ceef4860f2a00ec6e2901cd Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Tue, 16 Aug 2022 16:12:46 -0300 Subject: [PATCH] feat: support adding TextNode elements (automatically sets text property by default) --- .../angular/src/lib/nativescript-renderer.ts | 5 +++- packages/angular/src/lib/view-util.ts | 26 +++++++++++++++++++ .../angular/src/lib/views/invisible-nodes.ts | 25 ++++++++++++++++++ packages/angular/src/lib/views/view-types.ts | 2 ++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/packages/angular/src/lib/nativescript-renderer.ts b/packages/angular/src/lib/nativescript-renderer.ts index c26452c..7b73893 100644 --- a/packages/angular/src/lib/nativescript-renderer.ts +++ b/packages/angular/src/lib/nativescript-renderer.ts @@ -1,7 +1,7 @@ import { Inject, Injectable, NgZone, Optional, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2, ViewEncapsulation } from '@angular/core'; import { addTaggedAdditionalCSS, Application, ContentView, Device, getViewById, Observable, profile, Utils, View } from '@nativescript/core'; import { getViewClass, isKnownView } from './element-registry'; -import { getFirstNativeLikeView, NgView } from './views'; +import { getFirstNativeLikeView, NgView, TextNode } from './views'; import { NamespaceFilter, NAMESPACE_FILTERS } from './property-filter'; import { APP_ROOT_VIEW, ENABLE_REUSABE_VIEWS, NATIVESCRIPT_ROOT_MODULE_ID } from './tokens'; @@ -281,6 +281,9 @@ class NativeScriptRenderer implements Renderer2 { if (NativeScriptDebug.enabled) { NativeScriptDebug.rendererLog(`NativeScriptRenderer.setValue renderNode: ${node}, value: ${value}`); } + if (node instanceof TextNode) { + node.text = value; + } // throw new Error("Method not implemented."); } listen(target: View, eventName: string, callback: (event: any) => boolean | void): () => void { diff --git a/packages/angular/src/lib/view-util.ts b/packages/angular/src/lib/view-util.ts index ed13166..ac6cdfa 100644 --- a/packages/angular/src/lib/view-util.ts +++ b/packages/angular/src/lib/view-util.ts @@ -99,6 +99,9 @@ export class ViewUtil { if (!isDetachedElement(child)) { const nextVisual = this.findNextVisual(next); this.addToVisualTree(extendedParent, extendedChild, nextVisual); + } else if (isInvisibleNode(extendedChild)) { + const nextVisual = this.findNextVisual(next); + this.addInvisibleNode(extendedParent, extendedChild, nextVisual); } // printNgTree(extendedChild); } @@ -160,6 +163,17 @@ export class ViewUtil { } } + private addInvisibleNode(parent: NgView, child: NgView, next: NgView): void { + if (parent.meta?.insertInvisibleNode) { + parent.meta.insertInvisibleNode(parent, child, next); + } else { + if (child instanceof TextNode) { + (parent as any).text = child.text; + child.registerTextChange((t) => ((parent as any).text = t), parent); + } + } + } + private insertToLayout(parent: NgLayoutBase, child: NgView, next: NgView): void { if (child.parent === parent) { this.removeLayoutChild(parent, child); @@ -199,6 +213,8 @@ export class ViewUtil { this.removeFromList(extendedParent, extendedChild); if (!isDetachedElement(extendedChild)) { this.removeFromVisualTree(extendedParent, extendedChild); + } else if (isInvisibleNode(extendedChild)) { + this.removeInvisibleNode(extendedParent, extendedChild); } } @@ -291,6 +307,16 @@ export class ViewUtil { } } + private removeInvisibleNode(parent: NgView, child: NgView) { + if (parent.meta?.removeInvisibleNode) { + parent.meta.removeInvisibleNode(parent, child); + } else { + if (child instanceof TextNode) { + child.unregisterTextChange(parent); + } + } + } + private removeLayoutChild(parent: NgLayoutBase, child: NgView): void { if (NativeScriptDebug.isLogEnabled()) { NativeScriptDebug.viewUtilLog(`ViewUtil.removeLayoutChild parent: ${parent} child: ${child}`); diff --git a/packages/angular/src/lib/views/invisible-nodes.ts b/packages/angular/src/lib/views/invisible-nodes.ts index 2b39c6f..0f950eb 100644 --- a/packages/angular/src/lib/views/invisible-nodes.ts +++ b/packages/angular/src/lib/views/invisible-nodes.ts @@ -41,10 +41,21 @@ export class CommentNode extends InvisibleNode { } export class TextNode extends InvisibleNode { + public static textChangeEvent = 'textChange'; protected static id = 0; + protected _text = ''; + get text() { + return this._text; + } + set text(t: string) { + this._text = t; + this.notify({ eventName: TextNode.textChangeEvent, object: this, value: t }); + } + callbackMap = new Map void>>(); constructor(value?: string) { super(value); + this._text = value; this.meta = { skipAddToDom: true, @@ -52,4 +63,18 @@ export class TextNode extends InvisibleNode { this.id = TextNode.id.toString(); TextNode.id += 1; } + + registerTextChange(callback: (text: string) => void, id: unknown) { + const cb = (evt) => callback(evt.value); + const cbArr = this.callbackMap.get(id) || []; + cbArr.push(cb); + this.callbackMap.set(id, cbArr); + this.on('textChange', cb); + } + + unregisterTextChange(id: unknown) { + const cbArr = this.callbackMap.get(id) || []; + cbArr.forEach((cb) => this.off('textChange', cb)); + this.callbackMap.delete(id); + } } diff --git a/packages/angular/src/lib/views/view-types.ts b/packages/angular/src/lib/views/view-types.ts index 0073f29..c0eea47 100644 --- a/packages/angular/src/lib/views/view-types.ts +++ b/packages/angular/src/lib/views/view-types.ts @@ -21,6 +21,8 @@ export interface ViewClassMeta { skipAddToDom?: boolean; insertChild?: (parent: any, child: any, next?: any) => void; removeChild?: (parent: any, child: any) => void; + insertInvisibleNode?: (parent: any, child: any, next?: any) => void; + removeInvisibleNode?: (parent: any, child: any) => void; } export type NgLayoutBase = LayoutBase & ViewExtensions;