import { extend, isFunction, isNumber } from '../core/util'; import { trim } from '../core/util/strings'; import { on, off, removeDomNode, stopPropagation, TRANSFORM, TRANSFORMORIGIN, TRANSITION } from '../core/util/dom'; import Browser from '../core/Browser'; import Class from '../core/Class'; import Eventable from '../core/Eventable'; import Size from '../geo/Size'; import Geometry from '../geometry/Geometry'; import Coordinate from '../geo/Coordinate'; import type { Map } from './../map/Map'; import { Point } from '../geo'; import { MapStateCache } from '../map/MapStateCache'; /** * @property {Object} options * @property {Boolean} [options.eventsPropagation=false] - whether stop ALL events' propagation. * @property {Boolean} [options.eventsToStop=null] - UI's dom events to stop propagation if eventsPropagation is true. * @property {Number} [options.dx=0] - pixel offset on x axis * @property {Number} [options.dy=0] - pixel offset on y axis * @property {Boolean} [options.autoPan=false] - set it to false if you don't want the map to do panning animation to fit the opened UI. * @property {Boolean} [options.autoPanDuration=600] - duration for auto panning animation. * @property {Boolean} [options.single=true] - whether the UI is a global single one, only one UI will be shown at the same time if set to true. * @property {Boolean} [options.animation=null] - fade | scale | fade,scale, add animation effect when showing and hiding. * @property {Number} [options.animationDuration=300] - animation duration, in milliseconds. * @property {Number} [options.animationOnHide=false] - if calls animation on hiding. * @property {Boolean} [options.pitchWithMap=false] - whether tilt with map * @property {Boolean} [options.rotateWithMap=false] - whether rotate with map * @property {Boolean} [options.collision=false] - whether collision * @property {Number} [options.collisionBufferSize=2] - collision buffer size * @property {Number} [options.collisionWeight=0] - Collision weight, large priority collision * @property {Boolean} [options.collisionFadeIn=false] - Collision fade in animation * @property {Number} [options.zIndex=0] - dom zindex * @memberOf ui.UIComponent * @instance */ const options: UIComponentOptionsType = { 'eventsPropagation': false, 'eventsToStop': null, 'dx': 0, 'dy': 0, 'autoPan': false, 'autoPanDuration': 600, 'single': true, 'animation': 'scale', 'animationOnHide': false, 'animationDuration': 500, 'pitchWithMap': false, 'rotateWithMap': false, 'visible': true, 'roundPoint': false, 'collision': false, 'collisionBufferSize': 2, 'collisionWeight': 0, 'collisionFadeIn': false, 'zIndex': 0, 'enableScrollbar': true }; const COLLISION_STATES = ['collision', 'collisionBufferSize', 'collisionWeight', 'collisionFadeIn'] /** * @classdesc * Base class for all the UI component classes, a UI component is a HTMLElement positioned with geographic coordinate.
* It is abstract and not intended to be instantiated. * * @category ui * @abstract * @mixes Eventable * @memberOf ui * @extends Class */ class UIComponent extends Eventable(Class) { options: UIComponentOptionsType; //@internal _owner: Map | Geometry; //@internal _coordinate: Coordinate; //@internal _onlyUpdatePosition: boolean; //@internal _mapEventsOn: boolean; //@internal __uiDOM: HTMLElement; //@internal _pos: Point; //@internal _autoPanId: NodeJS.Timeout; //@internal _domContentRect: { width: number, height: number }; //@internal _size: Size; /** * Some instance methods subclasses needs to implement:
*
* 1. Optional, returns the Dom element's position offset
* function getOffset : Point
*
* 2. Method to create UI's Dom element
* function buildOn : HTMLElement
*
* 3 Optional, to provide an event map to register event listeners.
* function getEvents : void
* 4 Optional, a callback when dom is removed.
* function onDomRemove : void
* 5 Optional, a callback when UI Component is removed.
* function onRemove : void
* @param {Object} options configuration options */ constructor(options: UIComponentOptionsType) { super(options); this.proxyOptions(); } //@internal _appendCustomClass(dom: HTMLElement) { if (!dom) { console.warn('dom is null:', dom); return this; } if (this.options.cssName) { let cssName = this.options.cssName; if (!Array.isArray(cssName)) { cssName = [cssName]; } cssName.forEach(name => { dom.classList.add(name); }); } return this; } onAdd() { } onRemove() { } onDomRemove() { } getEvents(): { [key: string]: () => void } { return {}; } getOwnerEvents(): { [key: string]: () => void } { return {}; } buildOn(): HTMLElement { return null; } /** * Adds the UI Component to a geometry or a map * @param {Geometry|Map} owner - geometry or map to addto. * @returns {ui.UIComponent} this * @fires ui.UIComponent#add */ addTo(owner: Geometry | Map) { this._owner = owner; // first time this._switchEvents('on'); if (this.onAdd) { this.onAdd(); } /** * add event. * * @event ui.UIComponent#add * @type {Object} * @property {String} type - add * @property {ui.UIComponent} target - UIComponent */ this.fire('add'); return this; } /** * Get the map it added to * @return {Map} map instance * @override */ getMap(): Map { if (!this._owner) { return null; } // is a map if ((this._owner as Map).getBaseLayer) { return this._owner as Map; } return (this._owner as Geometry).getMap(); } //@internal _collides() { const map = this.getMap(); if (!map) { return this; } map._addUI(this); map._insertUICollidesQueue(); return this; } //@internal _collidesEffect(show: boolean) { const dom = this.getDOM(); if (!dom) { return this; } const visibility = show ? 'visible' : 'hidden'; dom.style.visibility = visibility; if (!dom.classList || !dom.classList.add) { return this; } if (!this.options['collisionFadeIn']) { return this; } const classList = dom.classList; const className = 'mtk-ui-fadein'; const hasClass = classList.contains(className); if (show && !hasClass) { dom.classList.add(className); } else if (!show && hasClass) { dom.classList.remove(className); } return this; } /** * Show the UI Component, if it is a global single one, it will close previous one. * @param {Coordinate} [coordinate=null] - coordinate to show, default is owner's center * @return {ui.UIComponent} this * @fires ui.UIComponent#showstart * @fires ui.UIComponent#showend */ show(coordinate: Coordinate) { const map = this.getMap(); if (!map) { return this; } this.options['visible'] = true; coordinate = coordinate || this._coordinate || this._owner.getCenter(); if (!(coordinate instanceof Coordinate)) { coordinate = new Coordinate(coordinate); } const visible = this.isVisible(); /** * showstart event. * * @event ui.UIComponent#showstart * @type {Object} * @property {String} type - showstart * @property {ui.UIComponent} target - UIComponent */ if (!this._onlyUpdatePosition) { this.fire('showstart'); } const container = this._getUIContainer(); this._coordinate = coordinate; //only update postion not remove dom if (!this._onlyUpdatePosition) { //when single will off map events this._removePrevDOM(); } //bind map events if (!this._mapEventsOn) { this._switchMapEvents('on'); } let dom: HTMLElement; if (!this._onlyUpdatePosition) { dom = this.__uiDOM = this.buildOn(); } else { dom = this.__uiDOM; } dom['eventsPropagation'] = this.options['eventsPropagation']; observerUIDomResize(dom, this); const zIndex = this.options.zIndex; if (!dom) { /** * showend event. * * @event ui.UIComponent#showend * @type {Object} * @property {String} type - showend * @property {ui.UIComponent} target - UIComponent */ if (!this._onlyUpdatePosition) { this.fire('showend'); } this._collides(); this.setZIndex(zIndex); return this; } if (!this._onlyUpdatePosition) { this._measureSize(dom); } if (this._singleton()) { (dom as any)._uiComponent = this; map[this._uiDomKey()] = dom; } this._setPosition(); dom.style[TRANSITION as string] = null; if (!this._onlyUpdatePosition) { container.appendChild(dom); } const anim = this._getAnimation(); if (visible) { anim.ok = false; } if (anim.ok) { if (anim.fade) { dom.style.opacity = 0 + ''; } if (anim.scale) { if ((this as any).getTransformOrigin) { const origin = (this as any).getTransformOrigin(); dom.style[TRANSFORMORIGIN as string] = origin; } dom.style[TRANSFORM as string] = this._toCSSTranslate(this._pos) + ' scale(0)'; } } //not support zoom filter show dom if (!this.isSupportZoomFilter()) { dom.style.display = ''; } if (this.options['eventsToStop']) { on(dom, this.options['eventsToStop'], stopPropagation); } // //autoPan // if (this.options['autoPan']) { // this._autoPan(); // } const transition = anim.transition; if (anim.ok && transition) { /* eslint-disable no-unused-expressions */ // trigger transition dom.offsetHeight; /* eslint-enable no-unused-expressions */ if (transition) { dom.style[TRANSITION as string] = transition; } if (anim.fade) { dom.style.opacity = 1 + ''; } if (anim.scale) { dom.style[TRANSFORM] = this._toCSSTranslate(this._pos) + ' scale(1)'; } } if (!this._onlyUpdatePosition) { this.fire('showend'); } this._collides(); //autoPan clearTimeout(this._autoPanId); if (this.options['autoPan']) { this._autoPanId = setTimeout(() => { this._autoPan(); }, 32); } this.setZIndex(zIndex); return this; } /** * Hide the UI Component. * @return {ui.UIComponent} this * @fires ui.UIComponent#hide */ hide() { if (!this.getDOM()) { return this; } if (this._onDomMouseout) { this._onDomMouseout(); } this.options['visible'] = false; const anim = this._getAnimation(), dom = this.getDOM(); if (!this.options['animationOnHide']) { anim.ok = false; } if (!anim.ok) { dom.style.display = 'none'; /** * hide event. * * @event ui.UIComponent#hide * @type {Object} * @property {String} type - hide * @property {ui.UIComponent} target - UIComponent */ this.fire('hide'); } else { /* eslint-disable no-unused-expressions */ dom.offsetHeight; /* eslint-enable no-unused-expressions */ dom.style[TRANSITION] = anim.transition; setTimeout(() => { dom.style.display = 'none'; this.fire('hide'); }, this.options['animationDuration']); } if (anim.fade) { dom.style.opacity = 0 + ''; } if (anim.scale) { dom.style[TRANSFORM] = this._toCSSTranslate(this._pos) + ' scale(0)'; } //remove map bind events this._switchMapEvents('off'); this._collides(); return this; } /** * Decide whether the ui component is open * @returns {Boolean} true|false */ isVisible() { if (!this.options['visible']) { return false; } const dom = this.getDOM(); return this.getMap() && dom && dom.parentNode && dom.style.display !== 'none'; } /** * Remove the UI Component * @return {ui.UIComponent} this * @fires ui.UIComponent#hide * @fires ui.UIComponent#remove */ remove() { delete this._mapEventsOn; if (!this._owner) { return this; } const map = this.getMap(); if (map) { map._removeUI(this); } this.hide(); //remove map bind events this._switchEvents('off'); if (this.onRemove) { this.onRemove(); } if (!this._singleton() && this.__uiDOM) { this._removePrevDOM(); } delete this._owner; /** * remove event. * * @event ui.UIComponent#remove * @type {Object} * @property {String} type - remove * @property {ui.UIComponent} target - UIComponent */ this.fire('remove'); this._collides(); return this; } /** * Get pixel size of the UI Component. * @return {Size} size */ getSize() { if (this._domContentRect && this._size) { //update size by resizeObserver result this._size.width = this._domContentRect.width; this._size.height = this._domContentRect.height; } if (this._size) { return this._size.copy(); } else { return null; } } getOwner() { return this._owner; } /** * get Dom Node * @returns {HTMLDivElement} dom|null */ getDOM() { return this.__uiDOM; } /** * set Dom Node zIndex * */ setZIndex(zIndex: number) { if (!isNumber(zIndex)) { return this; } const dom = this.getDOM(); if (!dom) { return this; } dom.style.zIndex = zIndex + ''; if (zIndex !== this.options.zIndex) { this.options.zIndex = zIndex; } return this; } //@internal _roundPoint(point: Point) { if (this.options.roundPoint) { point = point._round(); } return point; } getPosition() { if (!this.getMap()) { return null; } const p = this._roundPoint(this._getViewPoint()); if ((this as any).getOffset) { const o = this._roundPoint((this as any).getOffset()); if (o) { p._add(o); } } return p; } //@internal _getAnimation() { const anim = { 'fade': false, 'scale': false, 'ok': false, 'transition': '' }; const animations = this.options['animation'] ? this.options['animation'].split(',') : []; for (let i = 0; i < animations.length; i++) { const trimed = trim(animations[i]); if (trimed === 'fade') { anim.fade = true; } else if (trimed === 'scale') { anim.scale = true; } } let transition = null; if (anim.fade) { transition = 'opacity ' + this.options['animationDuration'] + 'ms'; } if (anim.scale) { transition = transition ? transition + ',' : ''; transition += TRANSFORM + ' ' + this.options['animationDuration'] + 'ms'; } anim.transition = transition; anim.ok = (transition !== null); return anim; } //@internal _getViewPoint() { let altitude = 0; //后期有了地形后,拿到的数据会带altitude,这里适配下,以后点击地图拿到的数据应该带海拔的(lng,lat,alt) const coordinates = this._coordinate || {}; if (isNumber((coordinates as Coordinate).z)) { altitude = (coordinates as Coordinate).z; } else if (this._owner && (this._owner as Geometry).getAltitude) { altitude = (this._owner as Geometry).getAltitude() as number || 0; //altitude is array from linestring ,polygon etc when coordinates carry z value [[x,y,z],[x,y,z],....]; if (!isNumber(altitude)) { altitude = 0; } } if (this._owner.getLayer) { const layer = (this._owner as Geometry).getLayer(); //VectorLayer if (layer && (layer as any).isVectorLayer) { altitude = (this._owner as Geometry)._getAltitude() as number || 0; if (!isNumber(altitude)) { altitude = 0; } } } const alt = this._meterToPoint(this._coordinate, altitude); return this.getMap().coordToViewPoint(this._coordinate, undefined, alt) ._add(this.options['dx'], this.options['dy']); } //@internal _meterToPoint(center: Coordinate, altitude: number) { return altitude; // const map = this.getMap(); // return map.altitudeToPoint(altitude, map._getResolution()) * sign(altitude); } //@internal _autoPan() { const map = this.getMap(), dom = this.getDOM(); if (!dom || !map || map.isMoving()) { return; } const point = this._getViewPoint()._round(); const mapWidth = map.width; const mapHeight = map.height; const mapContainer = map.getContainer(); if (dom && mapContainer && dom.getBoundingClientRect) { const mapRect = mapContainer.getBoundingClientRect(); //map left ,top value const mapLeft = mapRect.left; const mapTop = mapRect.top; const margin = 50; const rect = dom.getBoundingClientRect(); let offsetX = 0, offsetY = 0; let { left, right, top, bottom } = rect; const { width, height } = rect; //sub map left,top,Position values relative to the map should be used left -= mapLeft; right -= mapLeft; top -= mapTop; bottom -= mapTop; if (width > 0 && height > 0) { if (left < margin) { offsetX = margin - left; } if (offsetX === 0 && (right + margin) > mapWidth) { offsetX = -((right + margin) - mapWidth); } if (top < margin) { offsetY = margin - top; } if (offsetY === 0 && (bottom + margin) > mapHeight) { offsetY = -((bottom + margin) - mapHeight); } if (offsetX !== 0 || offsetY !== 0) { const cache = MapStateCache[map.id]; const pitch = cache ? cache.pitch : map.getPitch(); if (pitch > 40 && offsetY !== 0 && this._coordinate) { map.animateTo({ center: this._coordinate }, { duration: map.options['panAnimationDuration'] }); } else { map.panBy([Math.ceil(offsetX), Math.ceil(offsetY)]); } } return; } } const containerPoint0 = map.viewPointToContainerPoint(point); const offset = (this as any).getOffset(); const containerPoint = containerPoint0.add(offset); const prjCoord = map.viewPointToPrj(point); const domWidth = parseInt(dom.clientWidth + ''); const domHeight = parseInt(dom.clientHeight + ''); const margin = 50; let left = 0, top = 0; if ((containerPoint.x) < 0) { left = -containerPoint.x + margin; } else if ((containerPoint.x + domWidth) > mapWidth) { left = -((containerPoint.x + domWidth) - mapWidth) - margin; } if (containerPoint.y - domHeight < 0) { top = Math.abs(containerPoint.y - domHeight) + margin; } else if (containerPoint.y + domHeight > mapHeight) { top = (mapHeight - (containerPoint.y + domHeight)) - margin; } //if dom width > map width if (domWidth >= mapWidth) { left = mapWidth / 2 - containerPoint0.x; } if (top !== 0 || left !== 0) { const newContainerPoint = containerPoint0.add(left, top); const t = map._containerPointToPoint(newContainerPoint)._sub(map._prjToPoint(map._getPrjCenter())); const target = map._pointToPrj(map._prjToPoint(prjCoord).sub(t)); // map.panBy(new Point(left, top), { 'duration': this.options['autoPanDuration'] }); map._panAnimation(target); } } /** * Measure dom's size * @param {HTMLElement} dom - element to measure * @return {Size} size * @private */ //@internal _measureSize(dom: HTMLElement) { const container = this._getUIContainer(); dom.style.position = 'absolute'; // dom.style.left = -99999 + 'px'; const anchor = dom.style.bottom ? 'bottom' : 'top'; // dom.style[anchor] = -99999 + 'px'; dom.style.display = ''; container.appendChild(dom); if (dom.getBoundingClientRect) { const rect = dom.getBoundingClientRect(); this._size = new Size(rect.width, rect.height); } else { this._size = new Size(dom.clientWidth, dom.clientHeight); } dom.style.display = 'none'; dom.style.left = '0px'; dom.style[anchor] = '0px'; return this._size; } /** * Remove previous UI DOM if it has. * * @private */ //@internal _removePrevDOM() { if (this.onDomRemove) { this.onDomRemove(); } const eventsToStop = this.options['eventsToStop']; unobserveUIDomResize(this.__uiDOM); if (this._singleton()) { const map = this.getMap(), key = this._uiDomKey(); if (map[key]) { if (eventsToStop) { off(map[key], eventsToStop, stopPropagation); } const uiComponent = map[key]._uiComponent; //fire pre uicomponent(when it isVisible) hide event if (uiComponent && uiComponent !== this && uiComponent.isVisible()) { uiComponent.fire('hide'); } removeDomNode(map[key]); //remove map bind events if (uiComponent && !(this as any).hideDom) { uiComponent._switchMapEvents('off'); } delete map[key]; } delete this.__uiDOM; } else if (this.__uiDOM) { if (eventsToStop) { off(this.__uiDOM, eventsToStop, stopPropagation); } removeDomNode(this.__uiDOM); delete this.__uiDOM; } delete this._domContentRect; } /** * generate the cache key to store the singletong UI DOM * @private * @return {String} cache key */ //@internal _uiDomKey() { return '__ui_' + this._getClassName(); } //@internal _singleton() { return this.options['single']; } //@internal _getUIContainer() { return this.getMap().getPanels()['ui']; } //@internal _getClassName() { return 'UIComponent'; } //@internal _switchMapEvents(to: string) { const map = this.getMap(); if (!map) { return; } this._mapEventsOn = (to === 'on'); const events = this._getDefaultEvents(); if (this.getEvents) { extend(events, this.getEvents()); } if (events) { for (const p in events) { if (events.hasOwnProperty(p)) { map[to](p, events[p], this); } } } } //@internal _switchEvents(to: string) { //At the beginning,not bind map events,bind evetns when show // this._switchMapEvents(to); const ownerEvents = this._getOwnerEvents(); if (this._owner) { for (const p in ownerEvents) { if (ownerEvents.hasOwnProperty(p)) { this._owner[to](p, ownerEvents[p], this); } } } } //@internal _getDefaultEvents() { return { 'zooming rotate pitch': this.onEvent, 'zoomend': this.onZoomEnd, 'moving': this.onMoving, 'moveend': this.onMoving, 'resize': this.onResize }; } //@internal _getOwnerEvents() { const events: { [key: string]: (...args) => void } = {}; if (this._owner && (this._owner instanceof Geometry)) { events.positionchange = this.onGeometryPositionChange; events.symbolchange = this._updatePosition; } if (this.getOwnerEvents) { extend(events, this.getOwnerEvents()); } return events; } onGeometryPositionChange(param) { if (this._owner && this.isVisible()) { this._onlyUpdatePosition = true; const target = param.target; const center = target.getCenter(); if (target._getAltitude) { const altitude = target._getAltitude(); if (isNumber(altitude)) { center.z = altitude; } } this.show(center); this._onlyUpdatePosition = false; } } onMoving() { if (this.isVisible() && this.getMap().isTransforming()) { this._updatePosition(); } } onEvent() { if (this.isVisible()) { this._updatePosition(); } } onZoomEnd() { if (this.isVisible()) { // when zoomend, map container is reset, position should be updated in current frame this._setPosition(); } } onResize() { if (this.isVisible()) { //when map resize , update position this._setPosition(); } } onDomSizeChange() { if (this.isVisible()) { //when dom resize , update position this._setPosition(); this._collides(); } } //@internal _updatePosition() { if (!this.getMap()) { return this; } // update position in the next frame to sync with layers const renderer = this.getMap()._getRenderer(); renderer.callInNextFrame(this._setPosition.bind(this)); return this; } //@internal _setPosition() { const dom = this.getDOM(); if (!dom) return; dom.style[TRANSITION] = null; const p = this.getPosition(); this._pos = p; dom.style[TRANSFORM] = this._toCSSTranslate(p) + ' scale(1)'; } //@internal _toCSSTranslate(p: Point) { if (!p) { return ''; } if (Browser.any3d) { const map = this.getMap(); let bearing = 0, pitch = 0; if (map) { const cache = MapStateCache[map.id]; bearing = cache ? cache.bearing : map.getBearing(); pitch = cache ? cache.pitch : map.getPitch(); } let r = ''; if (this.options['pitchWithMap'] && pitch) { r += ` rotateX(${Math.round(pitch)}deg)`; } if (this.options['rotateWithMap'] && bearing) { r += ` rotateZ(${Math.round(-bearing)}deg)`; } return 'translate3d(' + Math.round(p.x) + 'px,' + Math.round(p.y) + 'px, 0px)' + r; } else { return 'translate(' + Math.round(p.x) + 'px,' + Math.round(p.y) + 'px)'; } } isSupportZoomFilter() { return false; } onConfig(config: Record) { let collisionStateChange = false; if (config) { for (let i = 0, len = COLLISION_STATES.length; i < len; i++) { const key = COLLISION_STATES[i]; if (key in config) { collisionStateChange = true; break; } } } this._updatePosition(); //https://github.com/maptalks/maptalks.js/issues/2609 if (collisionStateChange) { this._collides(); const map = this.getMap(); if (map && map._sortUI) { map._sortUI(); } } return this; } /* * * @param {Geometry||ui.UIMarker} owner * @return {Boolean} */ static isSupport(owner: Geometry | Map) { if (owner && isFunction(owner.on) && isFunction(owner.off) && isFunction(owner.getCenter)) { return true; } return false; } //@internal _bindDomEvents(dom: HTMLElement, to: string) { if (!dom) { return; } const events = this._getDomEvents() || {}; const bindEvent = to === 'on' ? on : off; for (const eventName in events) { if (to === 'on') { //remove old handler off(dom, eventName, events[eventName]); } bindEvent(dom, eventName, events[eventName], this); } } //@internal _getDomEvents() { return { 'mouseover': this._onDomMouseover, 'mouseout': this._onDomMouseout }; } //@internal _configMapPreventWheelScroll(preventWheelScroll: boolean) { const map = this.getMap(); if (!map) { return; } if (!this.options.enableScrollbar) { return; } if (this.options.eventsPropagation) { return; } map.options['preventWheelScroll'] = preventWheelScroll; } // eslint-disable-next-line no-unused-vars //@internal _onDomMouseover() { this._configMapPreventWheelScroll(false); /** * mouseover event. * * @event ui.UIComponent#mouseover * @type {Object} * @property {String} type - mouseover * @property {ui.UIComponent} target - UIComponent */ this.fire('mouseover'); } // eslint-disable-next-line no-unused-vars //@internal _onDomMouseout() { this._configMapPreventWheelScroll(true); /** * mouseout event. * * @event ui.UIComponent#mouseout * @type {Object} * @property {String} type - mouseout * @property {ui.UIComponent} target - UIComponent */ this.fire('mouseout'); } } UIComponent.mergeOptions(options); export default UIComponent; export type UIComponentOptionsType = { eventsPropagation?: boolean; eventsToStop?: string; dx?: number; dy?: number; autoPan?: boolean; autoPanDuration?: number; single?: boolean; animation?: string; animationOnHide?: boolean; animationDuration?: number; pitchWithMap?: boolean; rotateWithMap?: boolean; visible?: boolean; roundPoint?: boolean; collision?: boolean; collisionBufferSize?: number; collisionWeight?: number; collisionFadeIn?: boolean; zIndex?: number; cssName?: string | Array; enableScrollbar?: boolean; } let resizeObserver: ResizeObserver; function observerUIDomResize(dom: HTMLElement, uiComponent: UIComponent) { if (!resizeObserver && Browser.resizeObserver) { resizeObserver = new ResizeObserver((entries) => { entries = entries || []; entries.forEach(element => { const { target, borderBoxSize, contentRect } = element; let uicom: UIComponent; if (target) { uicom = (target as any)._uiComponent; } if (!uicom) { return; } if (borderBoxSize && borderBoxSize.length) { uicom._domContentRect = { width: borderBoxSize[0].inlineSize, height: borderBoxSize[0].blockSize }; } else { uicom._domContentRect = contentRect; } if (uicom.onDomSizeChange) { uicom.onDomSizeChange(); } }); }); } if (resizeObserver && dom) { (dom as any)._uiComponent = uiComponent; resizeObserver.observe(dom); } } function unobserveUIDomResize(dom: HTMLElement) { if (dom) { delete (dom as any)._uiComponent; if (resizeObserver) { resizeObserver.unobserve(dom); } } } export type UIComponentAlignOptionsType = { horizontalAlignment?: 'middle' | 'left' | 'right'; verticalAlignment?: 'middle' | 'top' | 'bottom'; }