aframe-mesh-ui-components
Version:
A simple port of felixmariotto's three-mesh-ui package for use in A-Frame's environment
1,891 lines (1,275 loc) • 176 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require("THREE"));
else if(typeof define === 'function' && define.amd)
define(["THREE"], factory);
else {
var a = typeof exports === 'object' ? factory(require("THREE")) : factory(root["THREE"]);
for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
}
})(self, (__WEBPACK_EXTERNAL_MODULE_three__) => {
return /******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "./node_modules/three-mesh-ui/build/three-mesh-ui.module.js":
/*!******************************************************************!*\
!*** ./node_modules/three-mesh-ui/build/three-mesh-ui.module.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ AlignItems: () => (/* binding */ __webpack_exports__AlignItems),
/* harmony export */ Block: () => (/* binding */ __webpack_exports__Block),
/* harmony export */ ContentDirection: () => (/* binding */ __webpack_exports__ContentDirection),
/* harmony export */ FontLibrary: () => (/* binding */ __webpack_exports__FontLibrary),
/* harmony export */ InlineBlock: () => (/* binding */ __webpack_exports__InlineBlock),
/* harmony export */ JustifyContent: () => (/* binding */ __webpack_exports__JustifyContent),
/* harmony export */ Keyboard: () => (/* binding */ __webpack_exports__Keyboard),
/* harmony export */ Text: () => (/* binding */ __webpack_exports__Text),
/* harmony export */ TextAlign: () => (/* binding */ __webpack_exports__TextAlign),
/* harmony export */ Whitespace: () => (/* binding */ __webpack_exports__Whitespace),
/* harmony export */ "default": () => (/* binding */ __webpack_exports__default),
/* harmony export */ update: () => (/* binding */ __webpack_exports__update)
/* harmony export */ });
/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! three */ "three");
/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(three__WEBPACK_IMPORTED_MODULE_0__);
/******/ // The require scope
/******/ var __nested_webpack_require_103__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __nested_webpack_require_103__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__nested_webpack_require_103__.o(definition, key) && !__nested_webpack_require_103__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __nested_webpack_require_103__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __nested_webpack_require_103__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
var __nested_webpack_exports__ = {};
// EXPORTS
__nested_webpack_require_103__.d(__nested_webpack_exports__, {
"g1": () => (/* reexport */ AlignItems_namespaceObject),
"gO": () => (/* reexport */ Block),
"km": () => (/* reexport */ ContentDirection_namespaceObject),
"zV": () => (/* reexport */ core_FontLibrary),
"ol": () => (/* reexport */ InlineBlock),
"uM": () => (/* reexport */ JustifyContent_namespaceObject),
"N1": () => (/* reexport */ Keyboard),
"xv": () => (/* reexport */ Text),
"PH": () => (/* reexport */ TextAlign_namespaceObject),
"UH": () => (/* reexport */ Whitespace_namespaceObject),
"ZP": () => (/* binding */ three_mesh_ui),
"Vx": () => (/* binding */ update)
});
// NAMESPACE OBJECT: ./src/utils/block-layout/ContentDirection.js
var ContentDirection_namespaceObject = {};
__nested_webpack_require_103__.r(ContentDirection_namespaceObject);
__nested_webpack_require_103__.d(ContentDirection_namespaceObject, {
"COLUMN": () => (COLUMN),
"COLUMN_REVERSE": () => (COLUMN_REVERSE),
"ROW": () => (ROW),
"ROW_REVERSE": () => (ROW_REVERSE),
"contentDirection": () => (contentDirection)
});
// NAMESPACE OBJECT: ./src/utils/block-layout/AlignItems.js
var AlignItems_namespaceObject = {};
__nested_webpack_require_103__.r(AlignItems_namespaceObject);
__nested_webpack_require_103__.d(AlignItems_namespaceObject, {
"CENTER": () => (CENTER),
"END": () => (END),
"START": () => (START),
"STRETCH": () => (STRETCH),
"alignItems": () => (alignItems),
"warnAboutDeprecatedAlignItems": () => (warnAboutDeprecatedAlignItems)
});
// NAMESPACE OBJECT: ./src/utils/block-layout/JustifyContent.js
var JustifyContent_namespaceObject = {};
__nested_webpack_require_103__.r(JustifyContent_namespaceObject);
__nested_webpack_require_103__.d(JustifyContent_namespaceObject, {
"CENTER": () => (JustifyContent_CENTER),
"END": () => (JustifyContent_END),
"SPACE_AROUND": () => (SPACE_AROUND),
"SPACE_BETWEEN": () => (SPACE_BETWEEN),
"SPACE_EVENLY": () => (SPACE_EVENLY),
"START": () => (JustifyContent_START),
"justifyContent": () => (justifyContent)
});
// NAMESPACE OBJECT: ./src/utils/inline-layout/Whitespace.js
var Whitespace_namespaceObject = {};
__nested_webpack_require_103__.r(Whitespace_namespaceObject);
__nested_webpack_require_103__.d(Whitespace_namespaceObject, {
"NORMAL": () => (NORMAL),
"NOWRAP": () => (NOWRAP),
"PRE": () => (PRE),
"PRE_LINE": () => (PRE_LINE),
"PRE_WRAP": () => (PRE_WRAP),
"WHITE_CHARS": () => (WHITE_CHARS),
"collapseWhitespaceOnInlines": () => (collapseWhitespaceOnInlines),
"collapseWhitespaceOnString": () => (collapseWhitespaceOnString),
"newlineBreakability": () => (newlineBreakability),
"shouldBreak": () => (Whitespace_shouldBreak)
});
// NAMESPACE OBJECT: ./src/utils/inline-layout/TextAlign.js
var TextAlign_namespaceObject = {};
__nested_webpack_require_103__.r(TextAlign_namespaceObject);
__nested_webpack_require_103__.d(TextAlign_namespaceObject, {
"CENTER": () => (TextAlign_CENTER),
"JUSTIFY": () => (JUSTIFY),
"JUSTIFY_CENTER": () => (JUSTIFY_CENTER),
"JUSTIFY_LEFT": () => (JUSTIFY_LEFT),
"JUSTIFY_RIGHT": () => (JUSTIFY_RIGHT),
"LEFT": () => (LEFT),
"RIGHT": () => (RIGHT),
"textAlign": () => (textAlign)
});
;// CONCATENATED MODULE: external "three"
var x = y => { var x = {}; __nested_webpack_require_103__.d(x, y); return x; }
var y = x => () => x
const external_three_namespaceObject = x({ ["BufferAttribute"]: () => three__WEBPACK_IMPORTED_MODULE_0__.BufferAttribute, ["BufferGeometry"]: () => three__WEBPACK_IMPORTED_MODULE_0__.BufferGeometry, ["CanvasTexture"]: () => three__WEBPACK_IMPORTED_MODULE_0__.CanvasTexture, ["Color"]: () => three__WEBPACK_IMPORTED_MODULE_0__.Color, ["FileLoader"]: () => three__WEBPACK_IMPORTED_MODULE_0__.FileLoader, ["LinearFilter"]: () => three__WEBPACK_IMPORTED_MODULE_0__.LinearFilter, ["Mesh"]: () => three__WEBPACK_IMPORTED_MODULE_0__.Mesh, ["Object3D"]: () => three__WEBPACK_IMPORTED_MODULE_0__.Object3D, ["Plane"]: () => three__WEBPACK_IMPORTED_MODULE_0__.Plane, ["PlaneGeometry"]: () => three__WEBPACK_IMPORTED_MODULE_0__.PlaneGeometry, ["ShaderMaterial"]: () => three__WEBPACK_IMPORTED_MODULE_0__.ShaderMaterial, ["TextureLoader"]: () => three__WEBPACK_IMPORTED_MODULE_0__.TextureLoader, ["Vector2"]: () => three__WEBPACK_IMPORTED_MODULE_0__.Vector2, ["Vector3"]: () => three__WEBPACK_IMPORTED_MODULE_0__.Vector3 });
;// CONCATENATED MODULE: ./src/utils/block-layout/ContentDirection.js
const ROW = "row";
const ROW_REVERSE = "row-reverse";
const COLUMN = "column";
const COLUMN_REVERSE = "column-reverse";
function contentDirection( container, DIRECTION, startPos, REVERSE ){
// end to end children
let accu = startPos;
let childGetSize = "getWidth";
let axisPrimary = "x";
let axisSecondary = "y";
if( DIRECTION.indexOf( COLUMN ) === 0 ){
childGetSize = "getHeight";
axisPrimary = "y";
axisSecondary = "x";
}
// Refactor reduce into fori in order to get rid of this keyword
for ( let i = 0; i < container.childrenBoxes.length; i++ ) {
const child = container.childrenBoxes[ i ];
const CHILD_ID = child.id;
const CHILD_SIZE = child[childGetSize]();
const CHILD_MARGIN = child.margin || 0;
accu += CHILD_MARGIN * REVERSE;
container.childrenPos[ CHILD_ID ] = {
[axisPrimary]: accu + ( ( CHILD_SIZE / 2 ) * REVERSE ),
[axisSecondary]: 0
};
// update accu for next children
accu += ( REVERSE * ( CHILD_SIZE + CHILD_MARGIN ) );
}
}
;// CONCATENATED MODULE: ./src/utils/block-layout/AlignItems.js
const START = "start";
const CENTER = "center";
const END = "end";
const STRETCH = "stretch"; // Still bit experimental
function alignItems( boxComponent, DIRECTION){
const ALIGNMENT = boxComponent.getAlignItems();
if( AVAILABLE_ALIGN_ITEMS.indexOf(ALIGNMENT) === -1 ){
console.warn( `alignItems === '${ALIGNMENT}' is not supported` );
}
let getSizeMethod = "getWidth";
let axis = "x";
if( DIRECTION.indexOf( ROW ) === 0 ){
getSizeMethod = "getHeight";
axis = "y";
}
const AXIS_TARGET = ( boxComponent[getSizeMethod]() / 2 ) - ( boxComponent.padding || 0 );
boxComponent.childrenBoxes.forEach( ( child ) => {
let offset;
switch ( ALIGNMENT ){
case END:
case 'right': // @TODO : Deprecated and will be remove upon 7.x.x
case 'bottom': // @TODO : Deprecated and will be remove upon 7.x.x
if( DIRECTION.indexOf( ROW ) === 0 ){
offset = - AXIS_TARGET + ( child[getSizeMethod]() / 2 ) + ( child.margin || 0 );
}else{
offset = AXIS_TARGET - ( child[getSizeMethod]() / 2 ) - ( child.margin || 0 );
}
break;
case START:
case 'left': // @TODO : Deprecated and will be remove upon 7.x.x
case 'top': // @TODO : Deprecated and will be remove upon 7.x.x
if( DIRECTION.indexOf( ROW ) === 0 ){
offset = AXIS_TARGET - ( child[getSizeMethod]() / 2 ) - ( child.margin || 0 );
}else{
offset = - AXIS_TARGET + ( child[getSizeMethod]() / 2 ) + ( child.margin || 0 );
}
break;
}
boxComponent.childrenPos[ child.id ][axis] = offset || 0;
} );
}
/**
* @deprecated
* // @TODO: Be remove upon 7.x.x
* @param alignment
*/
function warnAboutDeprecatedAlignItems( alignment ){
if( DEPRECATED_ALIGN_ITEMS.indexOf(alignment) !== - 1){
console.warn(`alignItems === '${alignment}' is deprecated and will be remove in 7.x.x. Fallback are 'start'|'end'`)
}
}
const AVAILABLE_ALIGN_ITEMS = [
START,
CENTER,
END,
STRETCH,
'top', // @TODO: Be remove upon 7.x.x
'right', // @TODO: Be remove upon 7.x.x
'bottom', // @TODO: Be remove upon 7.x.x
'left' // @TODO: Be remove upon 7.x.x
];
// @TODO: Be remove upon 7.x.x
const DEPRECATED_ALIGN_ITEMS = [
'top',
'right',
'bottom',
'left'
];
;// CONCATENATED MODULE: ./src/utils/block-layout/JustifyContent.js
const JustifyContent_START = "start";
const JustifyContent_CENTER = "center";
const JustifyContent_END = "end";
const SPACE_AROUND = 'space-around';
const SPACE_BETWEEN = 'space-between';
const SPACE_EVENLY = 'space-evenly';
function justifyContent( boxComponent, direction, startPos, REVERSE){
const JUSTIFICATION = boxComponent.getJustifyContent();
if ( AVAILABLE_JUSTIFICATIONS.indexOf( JUSTIFICATION ) === -1 ) {
console.warn( `justifyContent === '${ JUSTIFICATION }' is not supported` );
}
const side = direction.indexOf('row') === 0 ? 'width' : 'height'
const usedDirectionSpace = boxComponent.getChildrenSideSum( side );
const INNER_SIZE = side === 'width' ? boxComponent.getInnerWidth() : boxComponent.getInnerHeight();
const remainingSpace = INNER_SIZE - usedDirectionSpace;
// Items Offset
const axisOffset = ( startPos * 2 ) - ( usedDirectionSpace * Math.sign( startPos ) );
// const axisOffset = ( startPos * 2 ) - ( usedDirectionSpace * REVERSE );
const justificationOffset = _getJustificationOffset( JUSTIFICATION, axisOffset );
// Items margin
const justificationMargins = _getJustificationMargin( boxComponent.childrenBoxes, remainingSpace, JUSTIFICATION, REVERSE );
// Apply
const axis = direction.indexOf( 'row' ) === 0 ? "x" : "y"
boxComponent.childrenBoxes.forEach( ( child , childIndex ) => {
boxComponent.childrenPos[ child.id ][axis] -= justificationOffset - justificationMargins[childIndex];
} );
}
const AVAILABLE_JUSTIFICATIONS = [
JustifyContent_START,
JustifyContent_CENTER,
JustifyContent_END,
SPACE_AROUND,
SPACE_BETWEEN,
SPACE_EVENLY
];
/**
*
* @param {string} justification
* @param {number} axisOffset
* @returns {number}
*/
function _getJustificationOffset( justification, axisOffset ){
// Only end and center have justification offset
switch ( justification ){
case JustifyContent_END:
return axisOffset;
case JustifyContent_CENTER:
return axisOffset / 2;
}
return 0;
}
/**
*
* @param items
* @param spaceToDistribute
* @param justification
* @param reverse
* @returns {any[]}
*/
function _getJustificationMargin( items, spaceToDistribute, justification, reverse ){
const justificationMargins = Array( items.length ).fill( 0 );
if ( spaceToDistribute > 0 ) {
// Only space-* have justification margin betweem items
switch ( justification ) {
case SPACE_BETWEEN:
// only one children would act as start
if ( items.length > 1 ) {
const margin = spaceToDistribute / ( items.length - 1 ) * reverse;
// set this margin for any children
// except for first child
justificationMargins[ 0 ] = 0;
for ( let i = 1; i < items.length; i++ ) {
justificationMargins[ i ] = margin * i;
}
}
break;
case SPACE_EVENLY:
// only one children would act as start
if ( items.length > 1 ) {
const margin = spaceToDistribute / ( items.length + 1 ) * reverse;
// set this margin for any children
for ( let i = 0; i < items.length; i++ ) {
justificationMargins[ i ] = margin * ( i + 1 );
}
}
break;
case SPACE_AROUND:
// only one children would act as start
if ( items.length > 1 ) {
const margin = spaceToDistribute / ( items.length ) * reverse;
const start = margin / 2;
justificationMargins[ 0 ] = start;
// set this margin for any children
for ( let i = 1; i < items.length; i++ ) {
justificationMargins[ i ] = start + margin * i;
}
}
break;
}
}
return justificationMargins;
}
;// CONCATENATED MODULE: ./src/components/core/BoxComponent.js
/**
Job: Handle everything related to a BoxComponent element dimensioning and positioning
Knows: Parents and children dimensions and positions
It's worth noting that in three-mesh-ui, it's the parent Block that computes
its children position. A Block can only have either only box components (Block)
as children, or only inline components (Text, InlineBlock).
*/
function BoxComponent( Base ) {
return class BoxComponent extends Base {
constructor( options ) {
super( options );
this.isBoxComponent = true;
this.childrenPos = {};
}
/** Get width of this component minus its padding */
getInnerWidth() {
const DIRECTION = this.getContentDirection();
switch ( DIRECTION ) {
case 'row' :
case 'row-reverse' :
return this.width - ( this.padding * 2 || 0 ) || this.getChildrenSideSum( 'width' );
case 'column' :
case 'column-reverse' :
return this.getHighestChildSizeOn( 'width' );
default :
console.error( `Invalid contentDirection : ${DIRECTION}` );
break;
}
}
/** Get height of this component minus its padding */
getInnerHeight() {
const DIRECTION = this.getContentDirection();
switch ( DIRECTION ) {
case 'row' :
case 'row-reverse' :
return this.getHighestChildSizeOn( 'height' );
case 'column' :
case 'column-reverse' :
return this.height - ( this.padding * 2 || 0 ) || this.getChildrenSideSum( 'height' );
default :
console.error( `Invalid contentDirection : ${DIRECTION}` );
break;
}
}
/** Return the sum of all this component's children sides + their margin */
getChildrenSideSum( dimension ) {
return this.childrenBoxes.reduce( ( accu, child ) => {
const margin = ( child.margin * 2 ) || 0;
const CHILD_SIZE = ( dimension === 'width' ) ?
( child.getWidth() + margin ) :
( child.getHeight() + margin );
return accu + CHILD_SIZE;
}, 0 );
}
/** Look in parent record what is the instructed position for this component, then set its position */
setPosFromParentRecords() {
if ( this.parentUI && this.parentUI.childrenPos[ this.id ] ) {
this.position.x = ( this.parentUI.childrenPos[ this.id ].x );
this.position.y = ( this.parentUI.childrenPos[ this.id ].y );
}
}
/** Position inner elements according to dimensions and layout parameters. */
computeChildrenPosition() {
if ( this.children.length > 0 ) {
const DIRECTION = this.getContentDirection();
let directionalOffset;
switch ( DIRECTION ) {
case ROW :
directionalOffset = - this.getInnerWidth() / 2;
break;
case ROW_REVERSE :
directionalOffset = this.getInnerWidth() / 2;
break;
case COLUMN :
directionalOffset = this.getInnerHeight() / 2;
break;
case COLUMN_REVERSE :
directionalOffset = - this.getInnerHeight() / 2;
break;
}
const REVERSE = - Math.sign( directionalOffset );
contentDirection(this, DIRECTION, directionalOffset, REVERSE );
justifyContent(this, DIRECTION, directionalOffset, REVERSE );
alignItems( this, DIRECTION );
}
}
/**
* Returns the highest linear dimension among all the children of the passed component
* MARGIN INCLUDED
*/
getHighestChildSizeOn( direction ) {
return this.childrenBoxes.reduce( ( accu, child ) => {
const margin = child.margin || 0;
const maxSize = direction === 'width' ?
child.getWidth() + ( margin * 2 ) :
child.getHeight() + ( margin * 2 );
return Math.max( accu, maxSize );
}, 0 );
}
/**
* Get width of this element
* With padding, without margin
*/
getWidth() {
// This is for stretch alignment
// @TODO : Conceive a better performant way
if( this.parentUI && this.parentUI.getAlignItems() === 'stretch' ){
if( this.parentUI.getContentDirection().indexOf('column') !== -1 ){
return this.parentUI.getWidth() - ( this.parentUI.padding * 2 || 0 );
}
}
return this.width || this.getInnerWidth() + ( this.padding * 2 || 0 );
}
/**
* Get height of this element
* With padding, without margin
*/
getHeight() {
// This is for stretch alignment
// @TODO : Conceive a better performant way
if( this.parentUI && this.parentUI.getAlignItems() === 'stretch' ){
if( this.parentUI.getContentDirection().indexOf('row') !== -1 ){
return this.parentUI.getHeight() - ( this.parentUI.padding * 2 || 0 );
}
}
return this.height || this.getInnerHeight() + ( this.padding * 2 || 0 );
}
};
}
;// CONCATENATED MODULE: ./src/utils/inline-layout/Whitespace.js
/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace#whitespace_helper_functions
*
* Throughout, whitespace is defined as one of the characters
* "\t" TAB \u0009
* "\n" LF \u000A
* "\r" CR \u000D
* " " SPC \u0020
*
* This does not use Javascript's "\s" because that includes non-breaking
* spaces (and also some other characters).
**/
const WHITE_CHARS = { '\t': '\u0009', '\n': '\u000A', '\r': '\u000D', ' ': '\u0020' };
const NORMAL = 'normal';
const NOWRAP = 'nowrap';
const PRE = 'pre';
const PRE_LINE = 'pre-line';
const PRE_WRAP = 'pre-wrap';
/**
* Collapse whitespaces and sequence of whitespaces on string
*
* @param textContent
* @param whiteSpace
* @returns {*}
*/
const collapseWhitespaceOnString = function ( textContent, whiteSpace ) {
switch ( whiteSpace ) {
case NOWRAP:
case NORMAL:
// newlines are treated as other whitespace characters
textContent = textContent.replace( /\n/g, ' ' );
//falls through
case PRE_LINE:
// collapsed white spaces sequences
textContent = textContent.replace( /[ ]{2,}/g, ' ' );
break;
default:
}
return textContent;
};
/**
* Get the breakability of a newline character according to white-space property
*
* @param whiteSpace
* @returns {string}
*/
const newlineBreakability = function ( whiteSpace ) {
switch ( whiteSpace ) {
case PRE:
case PRE_WRAP:
case PRE_LINE:
return 'mandatory';
case NOWRAP:
case NORMAL:
default:
// do not automatically break on newline
}
};
/**
* Check for breaks in inlines according to whiteSpace value
*
* @param inlines
* @param i
* @param lastInlineOffset
* @param options
* @returns {boolean}
*/
const Whitespace_shouldBreak = function( inlines, i, lastInlineOffset, options){
const inline = inlines[i];
switch ( options.WHITESPACE ){
case NORMAL:
case PRE_LINE:
case PRE_WRAP:
// prevent additional computation if line break is mandatory
if( inline.lineBreak === 'mandatory' ) return true;
const kerning = inline.kerning ? inline.kerning : 0;
const xoffset = inline.xoffset ? inline.xoffset : 0;
const xadvance = inline.xadvance ? inline.xadvance : inline.width;
// prevent additional computation if this character already exceed the available size
if( lastInlineOffset + xadvance + xoffset + kerning > options.INNER_WIDTH ) return true;
const nextBreak = _distanceToNextBreak( inlines, i, options );
return _shouldFriendlyBreak( inlines[ i - 1 ], lastInlineOffset, nextBreak, options );
case PRE:
return inline.lineBreak === 'mandatory';
case NOWRAP:
default:
return false;
}
}
/**
* Alter a line of inlines according to white-space property
* @param line
* @param {('normal'|'pre-wrap'|'pre-line')} whiteSpace
*/
const collapseWhitespaceOnInlines = function ( line, whiteSpace ) {
const firstInline = line[ 0 ];
const lastInline = line[ line.length - 1 ];
// @see https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
//
// current implementation is 'pre-line'
// if the breaking character is a space, get the previous one
switch ( whiteSpace ) {
// trim/collapse first and last whitespace characters of a line
case PRE_WRAP:
// only process whiteChars glyphs inlines
// if( firstInline.glyph && whiteChars[firstInline.glyph] && line.length > 1 ){
if ( firstInline.glyph && firstInline.glyph === '\n' && line.length > 1 ) {
_collapseLeftInlines( [ firstInline ], line[ 1 ] );
}
// if( lastInline.glyph && whiteChars[lastInline.glyph] && line.length > 1 ){
if ( lastInline.glyph && lastInline.glyph === '\n' && line.length > 1 ) {
_collapseRightInlines( [ lastInline ], line[ line.length - 2 ] );
}
break;
case PRE_LINE:
case NOWRAP:
case NORMAL:
let inlinesToCollapse = [];
let collapsingTarget;
// collect starting whitespaces to collapse
for ( let i = 0; i < line.length; i++ ) {
const inline = line[ i ];
if ( inline.glyph && WHITE_CHARS[ inline.glyph ] && line.length > i ) {
inlinesToCollapse.push( inline );
collapsingTarget = line[ i + 1 ];
continue;
}
break;
}
_collapseLeftInlines( inlinesToCollapse, collapsingTarget );
inlinesToCollapse = [];
collapsingTarget = null;
// collect ending whitespace to collapse
for ( let i = line.length - 1; i > 0; i-- ) {
const inline = line[ i ];
if ( inline.glyph && WHITE_CHARS[ inline.glyph ] && i > 0 ) {
inlinesToCollapse.push( inline );
collapsingTarget = line[ i - 1 ];
continue;
}
break;
}
_collapseRightInlines( inlinesToCollapse, collapsingTarget );
break;
case PRE:
break;
default:
console.warn( `whiteSpace: '${whiteSpace}' is not valid` );
return 0;
}
return firstInline.offsetX;
};
/***********************************************************************************************************************
* Internal logics
**********************************************************************************************************************/
/**
* Visually collapse inlines from right to left ( endtrim )
* @param {Array} inlines
* @param targetInline
* @private
*/
function _collapseRightInlines( inlines, targetInline ) {
if ( !targetInline ) return;
for ( let i = 0; i < inlines.length; i++ ) {
const inline = inlines[ i ];
inline.width = 0;
inline.height = 0;
inline.offsetX = targetInline.offsetX + targetInline.width;
}
}
/**
* Visually collapse inlines from left to right (starttrim)
* @param {Array} inlines
* @param targetInline
* @private
*/
function _collapseLeftInlines( inlines, targetInline ) {
if ( !targetInline ) return;
for ( let i = 0; i < inlines.length; i++ ) {
const inline = inlines[ i ];
inline.width = 0;
inline.height = 0;
inline.offsetX = targetInline.offsetX;
}
}
/**
* get the distance in world coord to the next glyph defined
* as break-line-safe ( like whitespace for instance )
* @private
*/
function _distanceToNextBreak( inlines, currentIdx, options, accu ) {
accu = accu || 0;
// end of the text
if ( !inlines[ currentIdx ] ) return accu;
const inline = inlines[ currentIdx ];
const kerning = inline.kerning ? inline.kerning : 0;
const xoffset = inline.xoffset ? inline.xoffset : 0;
const xadvance = inline.xadvance ? inline.xadvance : inline.width;
// if inline.lineBreak is set, it is 'mandatory' or 'possible'
if ( inline.lineBreak ) return accu + xadvance;
// no line break is possible on this character
return _distanceToNextBreak(
inlines,
currentIdx + 1,
options,
accu + xadvance + options.LETTERSPACING + xoffset + kerning
);
}
/**
* Test if we should line break here even if the current glyph is not out of boundary.
* It might be necessary if the last glyph was break-line-friendly (whitespace, hyphen..)
* and the distance to the next friendly glyph is out of boundary.
*/
function _shouldFriendlyBreak( prevChar, lastInlineOffset, nextBreak, options ) {
// We can't check if last glyph is break-line-friendly it does not exist
if ( !prevChar || !prevChar.glyph ) return false;
// Next break-line-friendly glyph is inside boundary
if ( lastInlineOffset + nextBreak < options.INNER_WIDTH ) return false;
// Previous glyph was break-line-friendly
return options.BREAKON.indexOf( prevChar.glyph ) > -1;
}
;// CONCATENATED MODULE: ./src/utils/inline-layout/TextAlign.js
const LEFT = 'left';
const RIGHT = 'right';
const TextAlign_CENTER = 'center';
const JUSTIFY = 'justify';
const JUSTIFY_LEFT = 'justify-left';
const JUSTIFY_RIGHT = 'justify-right';
const JUSTIFY_CENTER = 'justify-center';
function textAlign( lines, ALIGNMENT, INNER_WIDTH ) {
// Start the alignment by sticking to directions : left, right, center
for ( let i = 0; i < lines.length; i++ ) {
const line = lines[ i ];
// compute the alignment offset of the line
const offsetX = _computeLineOffset( line, ALIGNMENT, INNER_WIDTH, i === lines.length - 1 );
// apply the offset to each characters of the line
for ( let j = 0; j < line.length; j++ ) {
line[ j ].offsetX += offsetX;
}
line.x = offsetX;
}
// last operations for justifications alignments
if ( ALIGNMENT.indexOf( JUSTIFY ) === 0 ) {
for ( let i = 0; i < lines.length; i++ ) {
const line = lines[ i ];
// do not process last line for justify-left or justify-right
if ( ALIGNMENT.indexOf( '-' ) !== -1 && i === lines.length - 1 ) return;
// can only justify is space is remaining
const REMAINING_SPACE = INNER_WIDTH - line.width;
if ( REMAINING_SPACE <= 0 ) return;
// count the valid spaces to extend
// Do not take the first nor the last space into account
let validSpaces = 0;
for ( let j = 1; j < line.length - 1; j++ ) {
validSpaces += line[ j ].glyph === ' ' ? 1 : 0;
}
const additionalSpace = REMAINING_SPACE / validSpaces;
// for right justification, process the loop in reverse
let inverter = 1;
if ( ALIGNMENT === JUSTIFY_RIGHT ) {
line.reverse();
inverter = -1;
}
let incrementalOffsetX = 0;
// start at ONE to avoid first space
for ( let j = 1; j <= line.length - 1; j++ ) {
// apply offset on each char
const char = line[ j ];
char.offsetX += incrementalOffsetX * inverter;
// and increase it when space
incrementalOffsetX += char.glyph === ' ' ? additionalSpace : 0;
}
// for right justification, the loop was processed in reverse
if ( ALIGNMENT === JUSTIFY_RIGHT ) {
line.reverse();
}
}
}
}
const _computeLineOffset = ( line, ALIGNMENT, INNER_WIDTH, lastLine ) => {
switch ( ALIGNMENT ) {
case JUSTIFY_LEFT:
case JUSTIFY:
case LEFT:
return -INNER_WIDTH / 2;
case JUSTIFY_RIGHT:
case RIGHT:
return -line.width + ( INNER_WIDTH / 2 );
case TextAlign_CENTER:
return -line.width / 2;
case JUSTIFY_CENTER:
if ( lastLine ) {
// center alignement
return -line.width / 2;
}
// left alignment
return -INNER_WIDTH / 2;
default:
console.warn( `textAlign: '${ALIGNMENT}' is not valid` );
}
};
;// CONCATENATED MODULE: ./src/components/core/InlineManager.js
/**
Job: Positioning inline elements according to their dimensions inside this component
Knows: This component dimensions, and its children dimensions
This module is used for Block composition (Object.assign). A Block is responsible
for the positioning of its inline elements. In order for it to know what is the
size of these inline components, parseParams must be called on its children first.
It's worth noting that a Text is not positioned as a whole, but letter per letter,
in order to create a line break when necessary. It's Text that merge the various letters
in its own updateLayout function.
*/
function InlineManager( Base ) {
return class InlineManager extends Base {
/** Compute children .inlines objects position, according to their pre-computed dimensions */
computeInlinesPosition() {
// computed by BoxComponent
const INNER_WIDTH = this.getWidth() - ( this.padding * 2 || 0 );
const INNER_HEIGHT = this.getHeight() - ( this.padding * 2 || 0 );
// got by MeshUIComponent
const JUSTIFICATION = this.getJustifyContent();
const ALIGNMENT = this.getTextAlign();
const INTERLINE = this.getInterLine();
// Compute lines
const lines = this.computeLines();
lines.interLine = INTERLINE;
/////////////////////////////////////////////////////////////////
// Position lines according to justifyContent and contentAlign
/////////////////////////////////////////////////////////////////
const textHeight = Math.abs( lines.height );
// Line vertical positioning
const justificationOffset = ( () => {
switch ( JUSTIFICATION ) {
case 'start':
return (INNER_HEIGHT/2);
case 'end':
return textHeight - ( INNER_HEIGHT / 2 );
case 'center':
return ( textHeight / 2 );
default:
console.warn( `justifyContent: '${JUSTIFICATION}' is not valid` );
}
} )();
//
lines.forEach( ( line ) => {
line.y += justificationOffset;
line.forEach( ( inline ) => {
inline.offsetY += justificationOffset;
} );
} );
// Horizontal positioning
textAlign( lines, ALIGNMENT, INNER_WIDTH );
// Make lines accessible to provide helpful informations
this.lines = lines;
}
calculateBestFit( bestFit ) {
if ( this.childrenInlines.length === 0 ) return;
switch ( bestFit ) {
case 'grow':
this.calculateGrowFit();
break;
case 'shrink':
this.calculateShrinkFit();
break;
case 'auto':
this.calculateAutoFit();
break;
}
}
calculateGrowFit() {
const INNER_HEIGHT = this.getHeight() - ( this.padding * 2 || 0 );
//Iterative method to find a fontSize of text children that text will fit into container
let iterations = 1;
const heightTolerance = 0.075;
const firstText = this.childrenInlines.find( inlineComponent => inlineComponent.isText );
let minFontMultiplier = 1;
let maxFontMultiplier = 2;
let fontMultiplier = firstText._fitFontSize ? firstText._fitFontSize / firstText.getFontSize() : 1;
let textHeight;
do {
textHeight = this.calculateHeight( fontMultiplier );
if ( textHeight > INNER_HEIGHT ) {
if ( fontMultiplier <= minFontMultiplier ) { // can't shrink text
this.childrenInlines.forEach( inlineComponent => {
if ( inlineComponent.isInlineBlock ) return;
// ensure fontSize does not shrink
inlineComponent._fitFontSize = inlineComponent.getFontSize();
} );
break;
}
maxFontMultiplier = fontMultiplier;
fontMultiplier -= ( maxFontMultiplier - minFontMultiplier ) / 2;
} else {
if ( Math.abs( INNER_HEIGHT - textHeight ) < heightTolerance ) break;
if ( Math.abs( fontMultiplier - maxFontMultiplier ) < 5e-10 ) maxFontMultiplier *= 2;
minFontMultiplier = fontMultiplier;
fontMultiplier += ( maxFontMultiplier - minFontMultiplier ) / 2;
}
} while ( ++iterations <= 10 );
}
calculateShrinkFit() {
const INNER_HEIGHT = this.getHeight() - ( this.padding * 2 || 0 );
// Iterative method to find a fontSize of text children that text will fit into container
let iterations = 1;
const heightTolerance = 0.075;
const firstText = this.childrenInlines.find( inlineComponent => inlineComponent.isText );
let minFontMultiplier = 0;
let maxFontMultiplier = 1;
let fontMultiplier = firstText._fitFontSize ? firstText._fitFontSize / firstText.getFontSize() : 1;
let textHeight;
do {
textHeight = this.calculateHeight( fontMultiplier );
if ( textHeight > INNER_HEIGHT ) {
maxFontMultiplier = fontMultiplier;
fontMultiplier -= ( maxFontMultiplier - minFontMultiplier ) / 2;
} else {
if ( fontMultiplier >= maxFontMultiplier ) { // can't grow text
this.childrenInlines.forEach( inlineComponent => {
if ( inlineComponent.isInlineBlock ) return;
// ensure fontSize does not grow
inlineComponent._fitFontSize = inlineComponent.getFontSize();
} );
break;
}
if ( Math.abs( INNER_HEIGHT - textHeight ) < heightTolerance ) break;
minFontMultiplier = fontMultiplier;
fontMultiplier += ( maxFontMultiplier - minFontMultiplier ) / 2;
}
} while ( ++iterations <= 10 );
}
calculateAutoFit() {
const INNER_HEIGHT = this.getHeight() - ( this.padding * 2 || 0 );
//Iterative method to find a fontSize of text children that text will fit into container
let iterations = 1;
const heightTolerance = 0.075;
const firstText = this.childrenInlines.find( inlineComponent => inlineComponent.isText );
let minFontMultiplier = 0;
let maxFontMultiplier = 2;
let fontMultiplier = firstText._fitFontSize ? firstText._fitFontSize / firstText.getFontSize() : 1;
let textHeight;
do {
textHeight = this.calculateHeight( fontMultiplier );
if ( textHeight > INNER_HEIGHT ) {
maxFontMultiplier = fontMultiplier;
fontMultiplier -= ( maxFontMultiplier - minFontMultiplier ) / 2;
} else {
if ( Math.abs( INNER_HEIGHT - textHeight ) < heightTolerance ) break;
if ( Math.abs( fontMultiplier - maxFontMultiplier ) < 5e-10 ) maxFontMultiplier *= 2;
minFontMultiplier = fontMultiplier;
fontMultiplier += ( maxFontMultiplier - minFontMultiplier ) / 2;
}
} while ( ++iterations <= 10 );
}
/**
* computes lines based on children's inlines array.
* @private
*/
computeLines() {
// computed by BoxComponent
const INNER_WIDTH = this.getWidth() - ( this.padding * 2 || 0 );
// Will stock the characters of each line, so that we can
// correct lines position before to merge
const lines = [ [] ];
lines.height = 0;
const INTERLINE = this.getInterLine();
this.childrenInlines.reduce( ( lastInlineOffset, inlineComponent ) => {
// Abort condition
if ( !inlineComponent.inlines ) return;
//////////////////////////////////////////////////////////////
// Compute offset of each children according to its dimensions
//////////////////////////////////////////////////////////////
const FONTSIZE = inlineComponent._fitFontSize || inlineComponent.getFontSize();
const LETTERSPACING = inlineComponent.isText ? inlineComponent.getLetterSpacing() * FONTSIZE : 0;
const WHITESPACE = inlineComponent.getWhiteSpace();
const BREAKON = inlineComponent.getBreakOn();
const whiteSpaceOptions = {
WHITESPACE,
LETTERSPACING,
BREAKON,
INNER_WIDTH
}
const currentInlineInfo = inlineComponent.inlines.reduce( ( lastInlineOffset, inline, i, inlines ) => {
const kerning = inline.kerning ? inline.kerning : 0;
const xoffset = inline.xoffset ? inline.xoffset : 0;
const xadvance = inline.xadvance ? inline.xadvance : inline.width;
// Line break
const shouldBreak = Whitespace_shouldBreak(inlines,i,lastInlineOffset, whiteSpaceOptions );
if ( shouldBreak ) {
lines.push( [ inline ] );
inline.offsetX = xoffset;
// restart the lastInlineOffset as zero.
if ( inline.width === 0 ) return 0;
// compute lastInlineOffset normally
// except for kerning which won't apply
// as there is visually no lefthanded glyph to kern with
return xadvance + LETTERSPACING;
}
lines[ lines.length - 1 ].push( inline );
inline.offsetX = lastInlineOffset + xoffset + kerning;
return lastInlineOffset + xadvance + kerning + LETTERSPACING;
}, lastInlineOffset );
//
return currentInlineInfo;
}, 0 );
// Compute lines dimensions
let width = 0, height =0, lineOffsetY = -INTERLINE/2;
lines.forEach( ( line ) => {
//
line.lineHeight = line.reduce( ( height, inline ) => {
const charHeight = inline.lineHeight !== undefined ? inline.lineHeight : inline.height;
return Math.max( height, charHeight );
}, 0 );
//
line.lineBase = line.reduce( ( lineBase, inline ) => {
const newLineBase = inline.lineBase !== undefined ? inline.lineBase : inline.height;
return Math.max( lineBase, newLineBase );
}, 0 );
//
line.width = 0;
line.height = line.lineHeight;
const lineHasInlines = line[ 0 ];
if ( lineHasInlines ) {
// starts by processing whitespace, it will return a collapsed left offset
const WHITESPACE = this.getWhiteSpace();
const whiteSpaceOffset = collapseWhitespaceOnInlines( line, WHITESPACE );
// apply the collapsed left offset to ensure the starting offset is 0
line.forEach( ( inline ) => {
inline.offsetX -= whiteSpaceOffset;
} );
// compute its width: length from firstInline:LEFT to lastInline:RIGHT
line.width = this.computeLineWidth( line );
if( line.width > width ){
width = line.width;
}
line.forEach( ( inline ) => {
inline.offsetY = (lineOffsetY - inline.height) - inline.anchor;
if( inline.lineHeight < line.lineHeight ){
inline.offsetY -= line.lineBase- inline.lineBase;
}
} );
line.y = lineOffsetY;
// line.x will be set by textAlign
height += ( line.lineHeight + INTERLINE );
lineOffsetY = lineOffsetY - (line.lineHeight + INTERLINE );
}
} );
lines.height = height;
lines.width = width;
return lines;
}
calculateHeight( fontMultiplier ) {
this.childrenInlines.forEach( inlineComponent => {
if ( inlineComponent.isInlineBlock ) return;
// Set font size and recalculate dimensions
inlineComponent._fitFontSize = inlineComponent.getFontSize() * fontMultiplier;
inlineComponent.calculateInlines( inlineComponent._fitFontSize );
} );
const lines = this.computeLines();
return Math.abs( lines.height );
}
/**
* Compute the width of a line
* @param line
* @returns {number}
*/
computeLineWidth( line ) {
// only by the length of its extremities
const firstInline = line[ 0 ];
const lastInline = line[ line.length - 1 ];
// Right + Left ( left is negative )
return (lastInline.offsetX + lastInline.width) + firstInline.offsetX;
}
};
}
;// CONCATENATED MODULE: ./src/components/core/FontLibrary.js
/*
Job:
Keeping record of all the loaded fonts, which component use which font,
and load new fonts if necessary
Knows: Which component use which font, loaded fonts
This is one of the only modules in the 'component' folder that is not used
for composition (Object.assign). MeshUIComponent is the only module with
a reference to it, it uses FontLibrary for recording fonts accross components.
This way, if a component uses the same font as another, FontLibrary will skip
loading it twice, even if the two component are not in the same parent/child hierarchy
*/
const fileLoader = new external_three_namespaceObject.FileLoader();
const requiredFontFamilies = [];
const fontFamilies = {};
const textureLoader = new external_three_namespaceObject.TextureLoader();
const requiredFontTextures = [];
const fontTextures = {};
const records = {};
/**
Called by MeshUIComponent after fontFamily was set
When done, it calls MeshUIComponent.update, to actually display
the text with the loaded font.
*/
function setFontFamily( component, fontFamily ) {
if ( typeof fontFamily === 'string' ) {
loadFontJSON( component, fontFamily );
} else {
// keep record of the font that this component use
if ( !records[ component.id ] ) records[ component.id ] = { component };
// Ensure the font json is processed
_buildFriendlyKerningValues( fontFamily );
records[ component.id ].json = fontFamily;
component._updateFontFamily( fontFamily );
}
}
/**
Called by MeshUIComponent after fontTexture was set
When done, it calls MeshUIComponent.update, to actually display
the text with the loaded font.
*/
function setFontTexture( component, url ) {
// if this font was never asked for, we load it
if ( requiredFontTextures.indexOf( url ) === -1 ) {
requiredFontTextures.push( url );
textureLoader.load( url, ( texture ) => {
texture.generateMipmaps = false;
texture.minFilter = external_three_namespaceObject.LinearFilter;
texture.magFilter = external_three_namespaceObject.LinearFilter;
fontTextures[ url ] = texture;
for ( const recordID of Object.keys( records ) ) {
if ( url === records[ recordID ].textureURL ) {
// update all the components that were waiting for this font for an update
records[ recordID ].component._updateFontTexture( texture );
}
}
} );
}
// keep record of the font that this component use
if ( !records[ component.id ] ) records[ component.id ] = { component };
records[ component.id ].textureURL = url;
// update the component, only if the font is already requested and loaded
if ( fontTextures[ url ] ) {
component._updateFontTexture( fontTextures[ url ] );
}
}
/** used by Text to know if a warning must be thrown */
function getFontOf( component ) {
const record = records[ component.id ];
if ( !record && component.parentUI ) {
return getFontOf( component.parentUI );
}
return record;
}
/** Load JSON file at the url provided by the user at the component attribute 'fontFamily' */
function loadFontJSON( component, url ) {
// if this font was never asked for, we load it
if ( requiredFontFamilies.indexOf( url ) === -1 ) {
requiredFontFamilies.push( url );
fileLoader.load( url, ( text ) => {
// FileLoader import as a JSON string
const font = JSON.parse( text );
// Ensure the font json is processed
_buildFriendlyKerningValues( font );
fontFamilies[ url ] = font;
for ( const recordID of Object.keys( records ) ) {
if ( url === records[ recordID ].jsonURL ) {
// update all the components that were waiting for this font for an update
records[ recordID ].component._updateFontFamily( font );
}
}
} );
}
// keep record of the font that this component use
if ( !records[ component.id ] ) records[ component.id ] = { component };
records[ component.id ].jsonURL = url;
// update the component, only if the font is already requested and loaded
if ( fontFamilies[ url ] ) {
component._updateFontFamily( fontFamilies[ url ] );
}
}
/**
* From the original json font kernings array
* First : Reduce the number of values by ignoring any kerning defining an amount of 0
* Second : Update the data structure of kernings from
* {Array} : [{first: 97, second: 121, amount: 0},{first: 97, second: 122, amount: -1},...]
* to
* {Object}: {"ij":-2,"WA":-3,...}}
*
* @private
*/
function _buildFriendlyKerningValues( font ) {
// As "font registering" can comes from different paths : addFont, loadFontJSON, setFontFamily
// Be sure we don't repeat this operation
if ( font._kernings ) return;
const friendlyKernings = {};
for ( let i = 0; i < font.kernings.length; i++ ) {
const kerning = font.kernings[ i ];
// ignore zero kerned glyph pair
if ( kerning.amount === 0 ) continue;
// Build and store the glyph paired characters "ij","WA", ... as keys, referecing their kerning amount
const glyphPair = String.fromCharCode( kerning.first, kerning.second );
friendlyKernings[ glyphPair ] = kerning.amount;
}
// update the font to keep it
font._kernings = friendlyKernings;
}
/*
This method is intended for adding manually loaded fonts. Method assumes font hasn't been loaded or requested yet. If it was,
font with specified name will be overwritten, but components using it won't be updated.
*/
function addFont( name, json, texture ) {
texture.generateMipmaps = false;
texture.minFilter = external_three_namespaceObject.LinearFilter;
texture.magFilter = external_three_namespaceObject.LinearFilter;
requiredFontFamilies.push( name );
fontFamilies[ name ] = json;
// Ensure the font json is processed
_buildFriendlyKerningValues( json );
if ( texture ) {
requiredFontTextures.push( name );
fontTextures[ name ] = texture;
}
}
//
const FontLibrary = {
setFontFamily,
setFontTexture,
getFontOf,
addFont
};
/* harmony default export */ const core_FontLibrary = (FontLibrary);
;// CONCATENATED MODULE: ./src/components/core/UpdateManager.js
/**
* Job:
* - recording components required updates
* - trigger those updates when 'update' is called
*
* This module is a bit special. It is, with FontLibrary, one of the only modules in the 'component'
* directory not to be used in component composition (Object.assign).
*
* When MeshUIComponent is instanciated, it calls UpdateManager.register().
*
* Then when MeshUIComponent receives new attributes, it doesn't update the component right away.
* Instead, it calls UpdateManager.requestUpdate(), so that the component is updated when the user
* decides it (usually in the render loop).
*
* This is best for performance, because when a UI is created, thousands of componants can
* potentially be instantiated. If they called updates function on their ancestors right away,
* a given component could be updated thousands of times in one frame, which is very ineficient.
*
* Instead, redundant update request are moot, the component will update once when the use calls
* update() in their render loop.
*/
class UpdateManager {
/*
* get called by MeshUIComponent when component.set has been used.
* It registers this component and all its descendants for the different types of updates that were required.
*/
static requestUpdate( component, updateParsing, updateLayout, updateInner ) {
component.traverse( ( child ) => {
if ( !child.isUI ) return;
// request updates for all descendants of the passed components
if ( !this.requestedUpdates[ child.id ] ) {
this.requestedUpdates[ child.id ] = {
updateParsing,
updateLayout,
updateInner,
needCallback: ( updateParsing || updateLayout || updateInner )
};
} else {
if ( updateParsing ) this.requestedUpdates[ child.id ].updateParsing = true;
if ( updateLayout ) this.requestedUpdates[ child.id ].updateLayout = true;
if ( updateInner ) this.requestedUpdates[ child.id ].updateInner = true;
}
} );
}
/** Register a passed component for later updates */
static register( component ) {
if ( !this.components.includes( component ) ) {
this.components.push( component );
}
}
/** Unregister a component (when it's deleted for instance) */
static disposeOf( component ) {
const idx = this.components.indexOf( component );
if ( idx > -1 ) {
this.components.splice( idx, 1 );
}
}
/** Trigger all requested updates of registered components */
static update() {
if ( Object.keys( this.requestedUpdates ).length > 0 ) {
const roots = this.components.filter( ( component ) => {
return !component.parentUI;
} );
roots.forEach( root => this.traverseParsing( root ) );
roots.forEach( root => this.traverseUpdates( root