UNPKG

igniteui-react-core

Version:
948 lines (947 loc) 31.5 kB
/* THIS INFRAGISTICS ULTIMATE SOFTWARE LICENSE AGREEMENT ("AGREEMENT") LOCATED HERE: https://www.infragistics.com/legal/license/igultimate-la https://www.infragistics.com/legal/license/igultimate-eula GOVERNS THE LICENSING, INSTALLATION AND USE OF INFRAGISTICS SOFTWARE. BY DOWNLOADING AND/OR INSTALLING AND USING INFRAGISTICS SOFTWARE: you are indicating that you have read and understand this Agreement, and agree to be legally bound by it on behalf of the yourself and your company. */ import { List$1 } from "./List$1"; import { String_$type } from './type'; import { CssHelper } from './CssHelper'; import * as ReactDOM from 'react-dom'; import * as React from 'react'; import { TypeRegistrar } from "./type"; import { fromSpinal } from './componentUtil'; import { Localization } from './Localization'; export class ReactRenderer { constructor(container, document, useDefaultsSource, defaultsSource, portalManager = null) { this.container = container; this._useDefaultsSource = false; this._checkResized = null; this._resizeListener = null; this._resizeTicking = false; this._portalManager = null; this._destroyed = false; this._resizeTickId = -1; this._discStack = []; this._toUnbind = []; this._currentFontQuery = null; this._document = document; this._container = container; this._rootElement = container; this._rootWrapper = new ReactWrapper(this._rootElement, this); this._cssHelper = new ReactCssHelper(this, this._document); this._useDefaultsSource = useDefaultsSource; this._defaultsSource = defaultsSource; this._portalManager = portalManager; if (this._portalManager) { this._portalManager.renderer = this; } } addSizeWatcher(onResized) { let previousWidth = this._rootElement.offsetWidth; let previousHeight = this._rootElement.offsetHeight; let previousRatio = window.devicePixelRatio; if (this._checkResized == null) { this._checkResized = () => { let currWidth = this._rootElement.offsetWidth; let currHeight = this._rootElement.offsetHeight; let currRatio = window.devicePixelRatio; let changed = false; if (previousWidth != currWidth || previousHeight != currHeight || previousRatio != currRatio) { changed = true; } previousWidth = currWidth; previousHeight = currHeight; previousRatio = currRatio; if (changed) { onResized(); } }; } this._resizeListener = this.globalListen("window", "resize", (ev) => this._checkResized()); let self = this; this._resizeTicking = true; function resizeTick() { self._resizeTickId = -1; if (self._resizeTicking) { self._checkResized(); self._resizeTickId = self.setTimeout(resizeTick, 100); } } this._resizeTickId = this.setTimeout(resizeTick, 100); } destroy() { this._destroyed = true; for (let i = 0; i < this._toUnbind.length; i++) { this._toUnbind[i](); } this._toUnbind = []; this.removeSizeWatcher(); this._rootWrapper.destroy(); this._rootWrapper = null; this._rootElement = null; } removeSizeWatcher() { this._resizeTicking = false; if (this._resizeTickId != -1) { window.clearTimeout(this._resizeTickId); this._resizeTickId = -1; } if (this._resizeListener != null) { this._resizeListener(); this._resizeListener = null; } } updateRoot(root) { this._rootWrapper = root; this._rootElement = root.getNativeElement(); } withRenderer(act) { act(this); } supportsAnimation() { return true; } getRequestAnimationFrame() { let ret = (act) => { requestAnimationFrame(act); }; return ret; } getSetTimeout() { let ret = (act, millisecondsDelay) => { let val = window.setTimeout(act, millisecondsDelay); return val; }; return ret; } getClearTimeout() { let ret = (timerId) => { window.clearTimeout(timerId); }; return ret; } setTimeout(act, millisecondsDelay) { let val; val = window.setTimeout(act, millisecondsDelay); return val; } clearTimeout(timerId) { window.clearTimeout(timerId); } get rootWrapper() { return this._rootWrapper; } querySelector(selector) { let wrapper = null; this.withRenderer((ren) => { let ele = this.rootWrapper.getNativeElement().querySelector(selector); if (ele === null) { return null; } wrapper = new ReactWrapper(ele, ren); }); return wrapper; } getWrapper(ele) { if (!ele) { return null; } let wrapper = null; this.withRenderer((ren) => { wrapper = new ReactWrapper(ele, ren); }); return wrapper; } getResourceString(resourceId) { // check if the resource culture is available. It is a combination of the bundle ID and culture ID let grouping = this._resourceBundleId + "-" + this._cultureId; let isRegistered = Localization.isRegistered(grouping); if (isRegistered) { return Localization.getString(grouping, resourceId); } else { return ""; } } setResourceBundleId(bundle) { this._resourceBundleId = bundle; } setCultureId(culture) { this._cultureId = culture; } append(child) { this._rootWrapper.append(child); return this; } appendToBody(element) { if (this._document !== null && this._document.body !== null) { this._document.body.appendChild(element.getNativeElement()); } } createElement(elementName) { let ele; this.withRenderer((ren) => { ele = this._document.createElement(elementName); }); let wrap = new ReactWrapper(ele, this); return wrap; } createElementNS(elementName, ns) { let ele; this.withRenderer((ren) => { ele = this._document.createElementNS(ns, elementName); }); let wrap = new ReactWrapper(ele, this); return wrap; } endCSSQuery() { this._currentFontQuery = null; if (!this.hasBody()) { return; } if (this._discStack.length > 0) { var toRemove = this._discStack.pop(); toRemove.remove(); } } expandTemplate(template, args) { throw new Error("not implemented"); } get2DCanvasContext(canvas) { let context = null; this.withRenderer((ren) => { var canv = (canvas.getNativeElement()); context = canv.getContext("2d"); }); return context; } get isComputedFontQuery() { return this._currentFontQuery != null && this.hasBody(); } getCssDefaultPropertyValue(className, propertyName) { if (this._useDefaultsSource && !this.isComputedFontQuery) { let c = this._defaultsSource[className]; if (className == "" || className == null) { c = this._defaultsSource; } if (c === undefined) { return null; } let v = c[propertyName]; if (v === undefined) { return null; } return v; } return this._cssHelper.getPropertyValue(this.getCurrentDiscovery(), className, propertyName); } getCssDefaultValuesForClassCollection(classPrefix, propertyNames) { if (this._useDefaultsSource) { let ret = []; for (let k in this._defaultsSource) { if (k.indexOf(classPrefix) === 0) { let subRet = []; ret.push(subRet); for (let i = 0; i < propertyNames.length; i++) { let v = this._defaultsSource[k][propertyNames[i]]; if (v === undefined) { subRet.push(null); } else { subRet.push(v); } } } } return ret; } return this._cssHelper.getValuesForClassCollection(this.getCurrentDiscovery(), classPrefix, propertyNames); } getDefaultFontHeight() { if (this._defaultsSource["default-font-height"] !== undefined) { return +this._defaultsSource["default-font-height"]; } return 16; } getHeightForFontString(fontString, text, useOffsetHeight) { if (this._rootWrapper.getNativeElement() == null) { //TODO: some voodoo here. return 12; } this.startCSSQuery(); let disc = this.getCurrentDiscovery(); if (disc == null || !this.hasWindow) { return this.getDefaultFontHeight(); } if (disc.getNativeElement().parentElement) { disc.getNativeElement().parentElement.removeChild(disc.getNativeElement()); } disc.setStyleProperty("position", "absolute"); disc.setStyleProperty("visibility", "hidden"); disc.setStyleProperty("font", fontString); document.body.appendChild(disc.getNativeElement()); let span; this.withRenderer((ren) => { span = ren.createElement("span"); }); var spanWrap = new ReactWrapper(span, this); this.getCurrentDiscovery().append(spanWrap); spanWrap.setText(text); var height; if (useOffsetHeight) { var offHeight = spanWrap.getNativeElement().offsetHeight; height = offHeight; } else { height = spanWrap.getNativeElement().height(); } if (height == 0) { //HACK: HACK! HACK! HACK! height = this.getDefaultFontHeight(); } //console.log("font height: " + height); document.body.removeChild(disc.getNativeElement()); this.endCSSQuery(); return height; } getSubRenderer(root) { var elem = root; elem = elem.getNativeElement(); return new ReactRenderer(elem, this._document, this._useDefaultsSource, this._defaultsSource, this._portalManager); } listen(element, eventName, handler) { let retVal = null; if (element == "window") { element = window; } if (element == "document") { element = document; } var $self = this; var ret = element.addEventListener(eventName, handler); var self = this; retVal = function () { var ind = self._toUnbind.indexOf(retVal); if (ind >= 0) { self._toUnbind.splice(ind, 1); } element.removeEventListener(eventName, handler); handler = null; }; this._toUnbind.push(retVal); return retVal; } runInMainZone(action) { action(); } globalListen(element, eventName, handler) { let retVal = null; //this._ngZone.runOutsideAngular(() => { var $self = this; var a = function (e) { var inner = {}; //TODO: normalize here? inner.originalEvent = e; inner.altKey = e.altKey; inner.button = e.button; inner.ctrlKey = e.ctrlKey; inner.offsetX = e.offsetX; inner.offsetY = e.offsetY; inner.pageX = e.pageX; inner.pageY = e.pageY; if (e.touches && e.touches.length > 0) { inner.pageX = e.touches[0].pageX; inner.pageY = e.touches[0].pageY; } inner.shiftKey = e.shiftKey; inner.which = e.which; inner.preventDefault = function () { e.preventDefault(); }; inner.stopPropagation = function () { e.stopPropagation(); }; //console.log(e); handler(inner); }; var ret = this.listen(element, eventName, a); var self = this; retVal = function () { var ind = self._toUnbind.indexOf(retVal); if (ind >= 0) { self._toUnbind.splice(ind, 1); } ret(); a = null; }; this._toUnbind.push(retVal); //}); return retVal; } hasBody() { return this._document !== null && this._document.body !== null; } hasWindow() { //todo: hmmm return true; } getCurrentDiscovery() { if (this._discStack.length <= 0) { return null; } return this._discStack[this._discStack.length - 1]; } setCssQueryFontString(fontString) { if (this._useDefaultsSource) { this._currentFontQuery = fontString; } if (!document.body.contains(this.getCurrentDiscovery().getNativeElement())) { this.getCurrentDiscovery().remove(); this.appendToBody(this.getCurrentDiscovery()); } this.getCurrentDiscovery().setStyleProperty("font", fontString); } startCSSQuery() { if (!this.hasBody()) { return; } var disc = this._cssHelper.getDiscoveryElement(this._rootElement); this._discStack.push(disc); this._rootWrapper.append(disc); } supportsDOMEvents() { //todo: hmmm. return true; } getPortal(hostElement, elementTag, portalCallback, isContentPortal) { if (this._portalManager) { this._portalManager.getPortal(hostElement, elementTag, portalCallback, isContentPortal); } } getExternal(internalComponent, styleSourceElement, optionalParent = null) { if (internalComponent.externalObject) { return internalComponent.externalObject; } let ext = null; if (!internalComponent.$type) { return null; } let name = internalComponent.$type.name; let externalName = "Igr" + name; if (!TypeRegistrar.isRegistered(externalName)) { return null; } ext = TypeRegistrar.create(externalName); if (ext) { ext._implementation = internalComponent; internalComponent.externalObject = ext; } if (styleSourceElement) { if (ext._styling) { ext._styling(styleSourceElement.getNativeElement(), ext, optionalParent); } } return ext; } } export class ReactWrapper { constructor(element, renderer) { this.renderer = renderer; this._toUnbind = []; this._attrCache = new Map(); this._styleCache = new Map(); this._currentDisplay = ""; this._innerText = undefined; if (element && element.getNativeElement) { element = element.getNativeElement(); } this.ele = element; this._toUnbind = []; } destroy() { this.unlistenAll(); this.ele = null; } withRenderer(act) { act(this.renderer); } addClass(className) { if (className == null || className.length == 0) { return this; } this.withRenderer((ren) => { className.split(" ").forEach((item) => this.ele.classList.add(item)); }); return this; } append(child) { this.withRenderer((ren) => { this.ele.appendChild(child.getNativeElement()); }); return this; } getAttribute(propertyName) { if (this._attrCache.has(propertyName)) { return this._attrCache.get(propertyName); } return null; } setAttribute(propertyName, value) { this._attrCache.set(propertyName, value); this.withRenderer((ren) => { this.ele.setAttribute(propertyName, value); }); return this; } before(child) { if (this.ele.parentElement) { this.ele.parentElement.insertBefore(child.getNativeElement(), this.ele); // this.insertBefore(child.getNativeElement()); } return this; } clone() { return new ReactWrapper(this.getNativeElement().cloneNode(true), this.renderer); } getStyleProperty(propertyName) { if (this.getNativeElement() !== null) { if (getComputedStyle !== undefined) { var computed = getComputedStyle(this.getNativeElement()); //console.log("getting computed value for: " + propertyName + ": " + computed[propertyName]); return computed[propertyName]; } } if (this._styleCache.has(propertyName)) { return this._styleCache.get(propertyName); } return null; } setStyleProperty(propertyName, value) { if (propertyName == "display") { if (value != "none") { this._currentDisplay = value; } } this._styleCache.set(propertyName, value); this.withRenderer((ren) => { this.ele.style[propertyName] = value; }); return this; } findByClass(className) { if (className.substring(0, 1) == '.') { className = className.substring(1); } let ret = this.ele.getElementsByClassName(className); let retVal = []; retVal.length = ret.length; for (var i = 0; i < ret.length; i++) { retVal[i] = new ReactWrapper(ret[i], this.renderer); } return retVal; } focus(preventScroll) { if (this.getNativeElement() !== null && this.getNativeElement().focus !== undefined) { if (preventScroll) { this.getNativeElement().focus({ preventScroll: true }); } else { this.getNativeElement().focus(); } } return this; } getChildAt(i) { let child = this.ele.children[i]; return new ReactWrapper(child, this.renderer); } getChildCount() { return this.ele.children.length; } getNativeElement() { let nativeElement = null; this.withRenderer((ren) => { if (this.ele == null) { nativeElement = null; return; } if (this.ele.getNativeElement == undefined) { nativeElement = this.ele; return nativeElement; } nativeElement = (this.ele.getNativeElement()); }); return nativeElement; } height() { var ret = this.getStyleProperty("height"); if (ret == null) { //todo: some voodoo here. return 500; } let val = parseFloat(ret.replace("px", "")); if (isNaN(val)) { return 0; } return val; } hide() { var currDisplay = this.getStyleProperty("display"); if (currDisplay == '') { currDisplay = this._currentDisplay; } if (currDisplay != 'none') { this._currentDisplay = currDisplay; } this.setStyleProperty("display", "none"); return this; } listen(eventName, handler) { let retVal = null; //this.ngZone.runOutsideAngular(() => { var $self = this; var a = function (e) { var inner = {}; //TODO: normalize here? inner.originalEvent = e; inner.altKey = e.altKey; inner.button = e.button; inner.ctrlKey = e.ctrlKey; inner.offsetX = e.offsetX; inner.offsetY = e.offsetY; inner.pageX = e.pageX; inner.pageY = e.pageY; if (e.touches && e.touches.length > 0) { inner.pageX = e.touches[0].pageX; inner.pageY = e.touches[0].pageY; } inner.shiftKey = e.shiftKey; inner.which = e.which; inner.preventDefault = function () { e.preventDefault(); }; inner.stopPropagation = function () { e.stopPropagation(); }; //console.log(e); handler(inner); }; var ret = this.renderer.listen(this.ele, eventName, a); var self = this; retVal = function () { var ind = self._toUnbind.indexOf(retVal); if (ind >= 0) { self._toUnbind.splice(ind, 1); } ret(); a = null; }; this._toUnbind.push(retVal); //}); return retVal; } getOffsetHelper(ele) { var clientRect = ele.getBoundingClientRect(); return { top: clientRect.top + window.pageYOffset, left: clientRect.left + window.pageXOffset }; } getOffset() { return this.getOffsetHelper(this.getNativeElement()); } setOffset(x, y) { let par = this.getNativeElement().offsetParent || this.getNativeElement().parentElement; let parentOffset = this.getOffsetHelper(par); return this.setRawPosition(x - parentOffset.left, y - parentOffset.top); //return this; } outerHeight() { return this.getProperty("offsetHeight"); } outerHeightWithMargin() { let height = this.outerHeight(); height += parseInt(this.getStyleProperty("marginTop")); height += parseInt(this.getStyleProperty("marginBottom")); return height; } outerWidth() { return this.getProperty("offsetWidth"); } outerWidthWithMargin() { let width = this.outerWidth(); width += parseInt(this.getStyleProperty("marginLeft")); width += parseInt(this.getStyleProperty("marginRight")); return width; } getProperty(propertyName) { let native = this.getNativeElement(); return native[propertyName]; } setProperty(propertyName, value) { this.withRenderer((ren) => { this.ele[propertyName] = value; }); return this; } remove() { this.withRenderer((ren) => { let ele = this.getNativeElement(); if (ele.parentElement != null) { this.ele.parentElement.removeChild(ele); } //ren.detachView([this.getNativeElement()]); }); return this; } removeChild(child) { this.withRenderer((ren) => { this.ele.removeChild(child.getNativeElement()); //ren.detachView([child.getNativeElement()]); }); return this; } removeChildren() { for (let i = this.getChildCount() - 1; i >= 0; i--) { this.removeChild(this.getChildAt(i)); } return this; } removeClass(className) { if (className == null || className.length == 0) { return this; } this.withRenderer((ren) => { className.split(" ").forEach((item) => this.ele.classList.remove(item)); }); return this; } setRawStyleProperty(propertyName, value) { //this.withRenderer((ren) => { this.ele.style[propertyName] = value; //}) return this; } setRawPosition(x, y) { this.ele.style.left = x + "px"; this.ele.style.top = y + "px"; return this; } setRawXPosition(x) { this.ele.style.left = x + "px"; return this; } setRawYPosition(y) { this.ele.style.top = y + "px"; return this; } setRawSize(width, height) { this.ele.style.width = width + "px"; this.ele.style.height = height + "px"; return this; } show() { this.setStyleProperty("display", this._currentDisplay); return this; } getText() { if (this._innerText != undefined) { return this._innerText; } return this.getNativeElement().innerText; } setText(value) { this._innerText = value; this.withRenderer((ren) => { //workaround warning: this.getNativeElement().innerText = value; //ren.setText(this.ele, value); }); return this; } setRawText(value) { this._innerText = value; this.ele.textContent = value; return this; } unlistenAll() { var toUnbind = []; for (var i = 0; i < this._toUnbind.length; i++) { toUnbind.push(this._toUnbind[i]); } for (var i = 0; i < toUnbind.length; i++) { toUnbind[i](); } this._toUnbind.length = 0; return this; } width() { var ret = this.getStyleProperty("width"); if (ret == null) { //todo: some voodoo here. return 500; } let val = parseFloat(ret.replace("px", "")); if (isNaN(val)) { return 0; } return val; } parent() { return this.ele == null || this.ele.parentElement == null ? null : new ReactWrapper(this.ele.parentElement, this.renderer); } querySelectorAll(selector) { let elements = this.ele.querySelectorAll(selector); let result = []; for (let ii = 0; ii < elements.length; ii++) { result.push(new ReactWrapper(elements[ii], this.renderer)); } return result; } } class ReactCssHelper { getDiscoveryElement(container) { //console.log(container); var ele = this.renderer.createElement("fakediscoveryelement"); var wrapper = new ReactWrapper(ele, this.renderer); wrapper.setStyleProperty("box-sizing", "content-box"); return wrapper; } constructor(renderer, document) { this.renderer = renderer; this.document = document; } getPropertyValue(discoveryElement, className, propertyName) { //console.log("fetching: " + className + ", prop: " + propertyName); var ret = CssHelper.getPropertyValue1(discoveryElement, className, propertyName); //console.log("for: " + className + ", prop: " + propertyName + " value: " + ret); return ret; } getValuesForClassCollection(discoveryElement, classPrefix, propertyNames) { var prop = new List$1(String_$type, 0); for (var i = 0; i < propertyNames.length; i++) { prop.add(propertyNames[i]); } var ret = CssHelper.getValuesForClassCollection(discoveryElement, classPrefix, prop); var retVal = new Array(ret.count); for (var i1 = 0; i1 < ret.count; i1++) { retVal[i1] = new Array(ret._inner[i1].count); for (var j = 0; j < ret._inner[i1].count; j++) { retVal[i1][j] = ret._inner[i1]._inner[j]; } } return retVal; } } export class PortalManager { constructor(id, requestRender) { this.disableContentPortal = false; this._portals = []; this._portalsMap = new Map(); this._id = null; this._currId = 0; this._requestRender = requestRender; this._onPortalRef = this._onPortalRef.bind(this); this._id = id; } onRender(children) { if (this._portals && this._portals.length > 0) { //children = React.Children.toArray(this.props.children); for (let i = 0; i < this._portals.length; i++) { let p = this._portals[i]; if (p.isContentPortal && !this.disableContentPortal) { var mine = children.slice(0); children.length = 0; var n = p._createPortal(mine); p._nativePortal = n; } else { var n = p._createPortal(null); p._nativePortal = n; } children.push(p._nativePortal); } } } getId() { return this._id + "__" + (this._currId++).toString(); } getPortal(hostElement, elementTag, portalCallback, isContentPortal) { const existingPortal = this._portals.find(x => x.portalContainer.getNativeElement() === hostElement.getNativeElement()); if (existingPortal) { return; } let portal = new ReactDomPortal(this, hostElement, elementTag, portalCallback, this.getId(), this._onPortalRef, isContentPortal); this._portals.push(portal); this._portalsMap.set(portal._portalId, portal); this._requestRender(); } getPortals() { return this._portals; } _onPortalRef(ele) { if (ele != null) { var key = ele.props.id; if (this._portalsMap.has(key)) { var p = this._portalsMap.get(key); p._onComponentProvided(ele); } } } _destroy(portal) { let ind = this._portals.indexOf(portal); this._portalsMap.delete(portal._portalId); this._portals.splice(ind, 1); this._requestRender(); } } class ReactDomPortal { destroy() { this._owner._destroy(this); } _onComponentProvided(ele) { var oldValue = this.componentRef; this.componentRef = ele; if (oldValue !== this.componentRef) { this._portalCallback(this); } } toPascal(name) { var val = fromSpinal(name); return val.substr(0, 1).toUpperCase() + val.substr(1); } createElementFromTag(elementTag, portalId, portalRef, children) { if (elementTag.indexOf("-") >= 0) { elementTag = this.toPascal(elementTag); } var name = "Igr" + elementTag; var ele = TypeRegistrar.get(name); var rEle = React.createElement(ele, { ref: portalRef, key: portalId, id: portalId, children: children }); return rEle; } _createPortal(children) { var reactEle = this.createElementFromTag(this._elementTag, this._portalId, this._portalRef, children); this._nativePortal = ReactDOM.createPortal(reactEle, this.portalContainer.getNativeElement(), this._portalId); return this._nativePortal; } constructor(portalManager, hostElement, elementTag, portalCallback, portalId, portalRef, isContentPortal) { this._portalRef = null; this.portalContainer = hostElement; this._elementTag = elementTag; this._portalCallback = portalCallback; this._portalId = portalId; this.isContentPortal = isContentPortal; this._portalRef = portalRef; this._owner = portalManager; } }