From 919cd66f7c17282e566eed4c189b59efd854b6c0 Mon Sep 17 00:00:00 2001 From: hanbollar Date: Wed, 10 Apr 2024 10:53:22 -0700 Subject: [PATCH 01/27] start Signed-off-by: hanbollar --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 45591be7..a0dec386 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![The MRjs logo, an indigo and purple bowtie.](https://docs.mrjs.io/static/mrjs-logo.svg) - + An extensible library of Web Components for the spatial web. [![npm run build](https://github.com/Volumetrics-io/mrjs/actions/workflows/build.yml/badge.svg)](https://github.com/Volumetrics-io/mrjs/actions/workflows/build.yml) [![npm run test](https://github.com/Volumetrics-io/mrjs/actions/workflows/test.yml/badge.svg)](https://github.com/Volumetrics-io/mrjs/actions/workflows/test.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/Volumetrics-io/mrjs/blob/main/LICENSE) From 2bed98996a897ad32558ded13ee73e78d7ab9f16 Mon Sep 17 00:00:00 2001 From: hanbollar Date: Wed, 10 Apr 2024 11:02:54 -0700 Subject: [PATCH 02/27] move the one use set text color out of utils Signed-off-by: hanbollar --- src/core/componentSystems/TextSystem.js | 34 +++++++++++++++---------- src/utils/Color.js | 15 ----------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/core/componentSystems/TextSystem.js b/src/core/componentSystems/TextSystem.js index 1870f93c..fcfc6acb 100644 --- a/src/core/componentSystems/TextSystem.js +++ b/src/core/componentSystems/TextSystem.js @@ -1,8 +1,8 @@ import { getSelectionRects, preloadFont } from 'troika-three-text'; -import { MRSystem } from 'mrjs/core/MRSystem'; import { MRButtonEntity } from 'mrjs/core/entities/MRButtonEntity'; import { MREntity } from 'mrjs/core/MREntity'; +import { MRSystem } from 'mrjs/core/MRSystem'; import { MRTextEntity } from 'mrjs/core/entities/MRTextEntity'; import { MRTextInputEntity } from 'mrjs/core/entities/MRTextInputEntity'; import { MRTextFieldEntity } from 'mrjs/core/entities/MRTextFieldEntity'; @@ -155,17 +155,6 @@ export class TextSystem extends MRSystem { } }; - /** - * @function - * @description The per-frame system update call for all text items including updates for style and cleaning of content for special characters. - * @param {number} deltaTime - given timestep to be used for any feature changes - * @param {object} frame - given frame information to be used for any feature changes - */ - update(deltaTime, frame) { - // For this system, since we have the 'per entity' and 'per scene event' update calls, - // we dont need a main update call here. - } - /** * @function * @description Updates the style for the text's information based on compStyle and inputted css elements. @@ -186,7 +175,11 @@ export class TextSystem extends MRSystem { textObj.lineHeight = this.getLineHeight(entity.compStyle.lineHeight, entity); // Color and opacity - mrjsUtils.color.setTEXTObject3DColor(textObj, entity.compStyle.color, entity.compStyle.opacity ?? 1); + // TODO - swap this to use the mrjsUtils.color.setObject3DColor function in future. + // For now since that creates a weird affect for styling (white edges), leaving as the + // current implementation. This probably just means there's a default style css thing + // we need to change before we swap. + this.setTEXTObject3DColor(textObj, entity.compStyle.color); // Whitespace and Wrapping textObj.whiteSpace = entity.compStyle.whiteSpace ?? textObj.whiteSpace; @@ -324,4 +317,19 @@ export class TextSystem extends MRSystem { return obj; } + + setTextObject3DColor = function (object3D, color, default_color = '#000') { + if (color.includes('rgba')) { + const rgba = color + .substring(5, color.length - 1) + .split(',') + .map((part) => parseFloat(part.trim())); + object3D.material.color.setStyle(`rgb(${rgba[0]}, ${rgba[1]}, ${rgba[2]})`); + + object3D.material.opacity = rgba[3]; + } else { + object3D.material.color.setStyle(color ?? '#000'); + } + object3D.material.needsUpdate = true; + }; } diff --git a/src/utils/Color.js b/src/utils/Color.js index c92dbc51..4a2d370d 100644 --- a/src/utils/Color.js +++ b/src/utils/Color.js @@ -120,19 +120,4 @@ color.setEntityColor = function (entity, color, compStyle_opacity = '1', default }); }; -color.setTEXTObject3DColor = function (object3D, color, default_color = '#000') { - if (color.includes('rgba')) { - const rgba = color - .substring(5, color.length - 1) - .split(',') - .map((part) => parseFloat(part.trim())); - object3D.material.color.setStyle(`rgb(${rgba[0]}, ${rgba[1]}, ${rgba[2]})`); - - object3D.material.opacity = rgba[3]; - } else { - object3D.material.color.setStyle(color ?? '#000'); - } - object3D.material.needsUpdate = true; -}; - export { color }; From c9e57e10034cf22af5393bbf08b11194d18529bc Mon Sep 17 00:00:00 2001 From: hanbollar Date: Wed, 10 Apr 2024 11:03:44 -0700 Subject: [PATCH 03/27] fix Signed-off-by: hanbollar --- src/core/componentSystems/TextSystem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/componentSystems/TextSystem.js b/src/core/componentSystems/TextSystem.js index fcfc6acb..c5ccdd64 100644 --- a/src/core/componentSystems/TextSystem.js +++ b/src/core/componentSystems/TextSystem.js @@ -179,7 +179,7 @@ export class TextSystem extends MRSystem { // For now since that creates a weird affect for styling (white edges), leaving as the // current implementation. This probably just means there's a default style css thing // we need to change before we swap. - this.setTEXTObject3DColor(textObj, entity.compStyle.color); + this.setTextObject3DColor(textObj, entity.compStyle.color); // Whitespace and Wrapping textObj.whiteSpace = entity.compStyle.whiteSpace ?? textObj.whiteSpace; From a6d417a6b33c4b911c366155b1c573016c33c7c0 Mon Sep 17 00:00:00 2001 From: hanbollar Date: Wed, 10 Apr 2024 12:12:43 -0700 Subject: [PATCH 04/27] temp save Signed-off-by: hanbollar --- dist/mr.js | 6 +++--- src/core/entities/MRTextAreaEntity.js | 2 +- src/core/entities/MRTextInputEntity.js | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/dist/mr.js b/dist/mr.js index 332e0cd8..5872f6a1 100644 --- a/dist/mr.js +++ b/dist/mr.js @@ -652,7 +652,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ TextSystem: () => (/* binding */ TextSystem)\n/* harmony export */ });\n/* harmony import */ var troika_three_text__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! troika-three-text */ \"./node_modules/troika-three-text/dist/troika-three-text.esm.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n/* harmony import */ var mrjs_core_entities_MRButtonEntity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/core/entities/MRButtonEntity */ \"./src/core/entities/MRButtonEntity.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs_core_entities_MRTextEntity__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! mrjs/core/entities/MRTextEntity */ \"./src/core/entities/MRTextEntity.js\");\n/* harmony import */ var mrjs_core_entities_MRTextInputEntity__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! mrjs/core/entities/MRTextInputEntity */ \"./src/core/entities/MRTextInputEntity.js\");\n/* harmony import */ var mrjs_core_entities_MRTextFieldEntity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! mrjs/core/entities/MRTextFieldEntity */ \"./src/core/entities/MRTextFieldEntity.js\");\n/* harmony import */ var mrjs_core_entities_MRTextAreaEntity__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! mrjs/core/entities/MRTextAreaEntity */ \"./src/core/entities/MRTextAreaEntity.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n\n\n\n\n\n\n\n\n\n\n\n\n/**\n * @class TextSystem\n * @classdesc Handles text creation and font rendering for `mr-text`, `mr-textfield`, and `mr-textarea` with a starting framerate of 1/30.\n * @augments MRSystem\n */\nclass TextSystem extends mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__.MRSystem {\n /**\n * @class\n * @description TextSystem's default constructor\n */\n constructor() {\n super(false);\n\n // Setup all the preloaded fonts\n this.preloadedFonts = {};\n const styleSheets = Array.from(document.styleSheets);\n styleSheets.forEach((styleSheet) => {\n const cssRules = Array.from(styleSheet.cssRules);\n // all the font-faces rules\n const rulesFontFace = cssRules.filter((rule) => rule.cssText.startsWith('@font-face'));\n\n rulesFontFace.forEach((fontFace) => {\n const fontData = this.parseFontFace(fontFace.cssText);\n\n (0,troika_three_text__WEBPACK_IMPORTED_MODULE_8__.preloadFont)(\n {\n font: fontData.src,\n },\n () => {\n this.preloadedFonts[fontFace.style.getPropertyValue('font-family')] = fontData.src;\n document.dispatchEvent(new CustomEvent('font-loaded'));\n }\n );\n });\n });\n\n // Handle text style needs update\n this.app.addEventListener('trigger-text-style-update', (e) => {\n // The event has the entity stored as its detail.\n if (e.detail !== undefined) {\n this._updateSpecificEntity(e.detail);\n }\n });\n }\n\n /**\n * @function\n * @description When a new entity is created, adds it to the physics registry and initializes the physics aspects of the entity.\n * @param {MREntity} entity - the entity being set up\n */\n onNewEntity(entity) {\n if (!(entity instanceof mrjs_core_entities_MRTextEntity__WEBPACK_IMPORTED_MODULE_3__.MRTextEntity)) {\n return;\n }\n this.registry.add(entity);\n this._updateSpecificEntity(entity);\n }\n\n /**\n * @function\n * @param {object} entity - the entity that needs to be updated.\n * @description The per entity triggered update call. Handles updating all text items including updates for style and cleaning of content for special characters.\n */\n _updateSpecificEntity(entity) {\n this.checkIfTextContentChanged(entity);\n this.handleTextContentUpdate(entity);\n }\n\n /**\n *\n * @param {object} entity - checks if the content changed and if so, updates it to match.\n * @returns {boolean} true if the content needed to be updated, false otherwise.\n */\n checkIfTextContentChanged(entity) {\n // Add a check in case a user manually updates the text value\n let text =\n entity instanceof mrjs_core_entities_MRTextInputEntity__WEBPACK_IMPORTED_MODULE_4__.MRTextInputEntity\n ? entity.hiddenInput?.value ?? false\n : // troika honors newlines/white space\n // we want to mimic h1, p, etc which do not honor these values\n // so we have to clean these from the text\n // ref: https://github.com/protectwise/troika/issues/289#issuecomment-1841916850\n entity.textContent\n .replace(/(\\n)\\s+/g, '$1')\n .replace(/(\\r\\n|\\n|\\r)/gm, ' ')\n .trim();\n\n if (entity.textObj.text != text) {\n entity.textObj.text = text;\n return true;\n }\n return false;\n }\n\n /**\n *\n * @param {object} entity - the entity whose content updated.\n */\n handleTextContentUpdate(entity) {\n this.updateStyle(entity);\n\n // The sync step ensures troika's text render info and geometry is up to date\n // with any text content changes.\n entity.textObj.sync(() => {\n if (entity instanceof mrjs_core_entities_MRButtonEntity__WEBPACK_IMPORTED_MODULE_1__.MRButtonEntity) {\n // MRButtonEntity\n\n entity.textObj.anchorX = 'center';\n } else if (entity instanceof mrjs_core_entities_MRTextInputEntity__WEBPACK_IMPORTED_MODULE_4__.MRTextInputEntity) {\n // MRTextAreaEntity, MRTextFieldEntity, etc\n\n // textObj positioning and dimensions\n entity.textObj.maxWidth = entity.width;\n entity.textObj.maxHeight = entity.height;\n entity.textObj.position.setX(-entity.width / 2);\n entity.textObj.position.setY(entity.height / 2);\n // cursor positioning and dimensions\n entity.cursorStartingPosition.x = entity.textObj.position.x;\n entity.cursorStartingPosition.y = entity.textObj.position.y - entity.cursorHeight / 2;\n // handle activity\n if (entity == document.activeElement) {\n entity.updateCursorPosition();\n } else {\n entity.blur();\n }\n } else {\n // MRTextEntity\n\n entity.textObj.position.setX(-entity.width / 2);\n entity.textObj.position.setY(entity.height / 2);\n }\n });\n }\n\n /**\n * @function\n * @description The per global scene event update call. Handles updating all text items including updates for style and cleaning of content for special characters.\n */\n eventUpdate = () => {\n for (const entity of this.registry) {\n this.checkIfTextContentChanged(entity);\n this.handleTextContentUpdate(entity);\n }\n };\n\n /**\n * @function\n * @description The per-frame system update call for all text items including updates for style and cleaning of content for special characters.\n * @param {number} deltaTime - given timestep to be used for any feature changes\n * @param {object} frame - given frame information to be used for any feature changes\n */\n update(deltaTime, frame) {\n // For this system, since we have the 'per entity' and 'per scene event' update calls,\n // we dont need a main update call here.\n }\n\n /**\n * @function\n * @description Updates the style for the text's information based on compStyle and inputted css elements.\n * @param {MRTextEntity} entity - the text entity whose style is being updated\n */\n updateStyle = (entity) => {\n const { textObj } = entity;\n\n // Font\n textObj.font = textObj.text.trim().length != 0 ? this.preloadedFonts[entity.compStyle.fontFamily] : null;\n textObj.fontSize = this.parseFontSize(entity.compStyle.fontSize, entity);\n textObj.fontWeight = this.parseFontWeight(entity.compStyle.fontWeight);\n textObj.fontStyle = entity.compStyle.fontStyle;\n\n // Alignment\n textObj.anchorY = this.getVerticalAlign(entity.compStyle.verticalAlign, entity);\n textObj.textAlign = this.getTextAlign(entity.compStyle.textAlign);\n textObj.lineHeight = this.getLineHeight(entity.compStyle.lineHeight, entity);\n\n // Color and opacity\n mrjs__WEBPACK_IMPORTED_MODULE_7__.mrjsUtils.color.setTEXTObject3DColor(textObj, entity.compStyle.color, entity.compStyle.opacity ?? 1);\n\n // Whitespace and Wrapping\n textObj.whiteSpace = entity.compStyle.whiteSpace ?? textObj.whiteSpace;\n textObj.maxWidth = entity.width * 1.001;\n\n // Offset position for visibility on top of background plane\n textObj.position.z = 0.0001;\n };\n\n /**\n * @function\n * @description Handles when text is added as an entity updating content and style for the internal textObj appropriately.\n * @param {MRTextEntity} entity - the text entity being updated\n */\n addText = (entity) => {\n const text = entity.textContent.trim();\n entity.textObj.text = text.length > 0 ? text : ' ';\n\n this.updateStyle(entity);\n };\n\n /**\n * @function\n * @description parses the font weight as 'bold', 'normal', etc based on the given weight value\n * @param {number} weight - the numerical representation of the font-weight\n * @returns {string} - the enum of 'bold', 'normal', etc\n */\n parseFontWeight(weight) {\n if (weight >= 500) {\n return 'bold';\n }\n return 'normal';\n }\n\n /**\n * @function\n * @description parses the font size based on its `XXpx` value and converts it to a usable result based on the virtual display resolution\n * @param {number} val - the value being adjusted\n * @param {object} el - the css element handler\n * @returns {number} - the font size adjusted for the display as expected\n */\n parseFontSize(val, el) {\n const result = parseFloat(val.split('px')[0]) / mrjs__WEBPACK_IMPORTED_MODULE_7__.mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;\n return result;\n }\n\n /**\n * @function\n * @description Gets the vertical align\n * @param {number} verticalAlign - the numerical representation in pixel space of the vertical Align\n * @param {MREntity} entity - the entity whose comp style (css) is relevant\n * @returns {string} - the string representation of the the verticalAlign\n */\n getVerticalAlign(verticalAlign, entity) {\n let result = verticalAlign;\n\n if (typeof result === 'number') {\n result /= mrjs__WEBPACK_IMPORTED_MODULE_7__.mrjsUtils.css.pxToThree(entity.compStyle.fontSize);\n }\n\n switch (result) {\n case 'baseline':\n case 'sub':\n case 'super':\n return 0;\n case 'text-top':\n return 'top-ex';\n case 'text-bottom':\n return 'bottom';\n case 'middle':\n default:\n return result;\n }\n }\n\n /**\n * @function\n * @description Gets the line height\n * @param {number} lineHeight - the numerical representation in pixel space of the line height\n * @param {MREntity} entity - the entity whose comp style (css) is relevant\n * @returns {number} - the numerical representation of the the lineHeight\n */\n getLineHeight(lineHeight, entity) {\n let result = mrjs__WEBPACK_IMPORTED_MODULE_7__.mrjsUtils.css.pxToThree(lineHeight);\n\n if (typeof result === 'number') {\n result /= mrjs__WEBPACK_IMPORTED_MODULE_7__.mrjsUtils.css.pxToThree(entity.compStyle.fontSize);\n }\n\n return result;\n }\n\n /**\n * @function\n * @description Gets the text alignment string\n * @param {string} textAlign - handles values for `start`, `end`, `left`, and `right`; otherwise, defaults to the same input as `textAlign`.\n * @returns {string} - the resolved `textAlign`.\n */\n getTextAlign(textAlign) {\n if (textAlign == 'start') {\n return 'left';\n } else if (textAlign == 'end') {\n return 'right';\n }\n return textAlign;\n }\n\n /**\n * @function\n * @description Based on the given font-face value in the passed cssString, tries to either use by default or download the requested font-face\n * for use by the text object.\n * @param {string} cssString - the css string to be parsed for the font-face css value.\n * @returns {object} - json object respresenting the preloaded font-face\n */\n parseFontFace(cssString) {\n const obj = {};\n const match = cssString.match(/@font-face\\s*{\\s*([^}]*)\\s*}/);\n\n if (match) {\n const fontFaceAttributes = match[1];\n const attributes = fontFaceAttributes.split(';');\n\n attributes.forEach((attribute) => {\n const [key, value] = attribute.split(':').map((item) => item.trim());\n if (key === 'src') {\n const urlMatch = value.match(/url\\(\"([^\"]+)\"\\)/);\n if (urlMatch) {\n obj[key] = urlMatch[1];\n }\n } else {\n obj[key] = value;\n }\n });\n }\n\n return obj;\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/componentSystems/TextSystem.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ TextSystem: () => (/* binding */ TextSystem)\n/* harmony export */ });\n/* harmony import */ var troika_three_text__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! troika-three-text */ \"./node_modules/troika-three-text/dist/troika-three-text.esm.js\");\n/* harmony import */ var mrjs_core_entities_MRButtonEntity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/entities/MRButtonEntity */ \"./src/core/entities/MRButtonEntity.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n/* harmony import */ var mrjs_core_entities_MRTextEntity__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! mrjs/core/entities/MRTextEntity */ \"./src/core/entities/MRTextEntity.js\");\n/* harmony import */ var mrjs_core_entities_MRTextInputEntity__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! mrjs/core/entities/MRTextInputEntity */ \"./src/core/entities/MRTextInputEntity.js\");\n/* harmony import */ var mrjs_core_entities_MRTextFieldEntity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! mrjs/core/entities/MRTextFieldEntity */ \"./src/core/entities/MRTextFieldEntity.js\");\n/* harmony import */ var mrjs_core_entities_MRTextAreaEntity__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! mrjs/core/entities/MRTextAreaEntity */ \"./src/core/entities/MRTextAreaEntity.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n\n\n\n\n\n\n\n\n\n\n\n\n/**\n * @class TextSystem\n * @classdesc Handles text creation and font rendering for `mr-text`, `mr-textfield`, and `mr-textarea` with a starting framerate of 1/30.\n * @augments MRSystem\n */\nclass TextSystem extends mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_2__.MRSystem {\n /**\n * @class\n * @description TextSystem's default constructor\n */\n constructor() {\n super(false);\n\n // Setup all the preloaded fonts\n this.preloadedFonts = {};\n const styleSheets = Array.from(document.styleSheets);\n styleSheets.forEach((styleSheet) => {\n const cssRules = Array.from(styleSheet.cssRules);\n // all the font-faces rules\n const rulesFontFace = cssRules.filter((rule) => rule.cssText.startsWith('@font-face'));\n\n rulesFontFace.forEach((fontFace) => {\n const fontData = this.parseFontFace(fontFace.cssText);\n\n (0,troika_three_text__WEBPACK_IMPORTED_MODULE_8__.preloadFont)(\n {\n font: fontData.src,\n },\n () => {\n this.preloadedFonts[fontFace.style.getPropertyValue('font-family')] = fontData.src;\n document.dispatchEvent(new CustomEvent('font-loaded'));\n }\n );\n });\n });\n\n // Handle text style needs update\n this.app.addEventListener('trigger-text-style-update', (e) => {\n // The event has the entity stored as its detail.\n if (e.detail !== undefined) {\n this._updateSpecificEntity(e.detail);\n }\n });\n }\n\n /**\n * @function\n * @description When a new entity is created, adds it to the physics registry and initializes the physics aspects of the entity.\n * @param {MREntity} entity - the entity being set up\n */\n onNewEntity(entity) {\n if (!(entity instanceof mrjs_core_entities_MRTextEntity__WEBPACK_IMPORTED_MODULE_3__.MRTextEntity)) {\n return;\n }\n this.registry.add(entity);\n this._updateSpecificEntity(entity);\n }\n\n /**\n * @function\n * @param {object} entity - the entity that needs to be updated.\n * @description The per entity triggered update call. Handles updating all text items including updates for style and cleaning of content for special characters.\n */\n _updateSpecificEntity(entity) {\n this.checkIfTextContentChanged(entity);\n this.handleTextContentUpdate(entity);\n }\n\n /**\n *\n * @param {object} entity - checks if the content changed and if so, updates it to match.\n * @returns {boolean} true if the content needed to be updated, false otherwise.\n */\n checkIfTextContentChanged(entity) {\n // Add a check in case a user manually updates the text value\n let text =\n entity instanceof mrjs_core_entities_MRTextInputEntity__WEBPACK_IMPORTED_MODULE_4__.MRTextInputEntity\n ? entity.hiddenInput?.value ?? false\n : // troika honors newlines/white space\n // we want to mimic h1, p, etc which do not honor these values\n // so we have to clean these from the text\n // ref: https://github.com/protectwise/troika/issues/289#issuecomment-1841916850\n entity.textContent\n .replace(/(\\n)\\s+/g, '$1')\n .replace(/(\\r\\n|\\n|\\r)/gm, ' ')\n .trim();\n\n if (entity.textObj.text != text) {\n entity.textObj.text = text;\n return true;\n }\n return false;\n }\n\n /**\n *\n * @param {object} entity - the entity whose content updated.\n */\n handleTextContentUpdate(entity) {\n this.updateStyle(entity);\n\n // The sync step ensures troika's text render info and geometry is up to date\n // with any text content changes.\n entity.textObj.sync(() => {\n if (entity instanceof mrjs_core_entities_MRButtonEntity__WEBPACK_IMPORTED_MODULE_0__.MRButtonEntity) {\n // MRButtonEntity\n\n entity.textObj.anchorX = 'center';\n } else if (entity instanceof mrjs_core_entities_MRTextInputEntity__WEBPACK_IMPORTED_MODULE_4__.MRTextInputEntity) {\n // MRTextAreaEntity, MRTextFieldEntity, etc\n\n // textObj positioning and dimensions\n entity.textObj.maxWidth = entity.width;\n entity.textObj.maxHeight = entity.height;\n entity.textObj.position.setX(-entity.width / 2);\n entity.textObj.position.setY(entity.height / 2);\n // cursor positioning and dimensions\n entity.cursorStartingPosition.x = entity.textObj.position.x;\n entity.cursorStartingPosition.y = entity.textObj.position.y - entity.cursorHeight / 2;\n // handle activity\n if (entity == document.activeElement) {\n entity.updateCursorPosition();\n } else {\n entity.blur();\n }\n } else {\n // MRTextEntity\n\n entity.textObj.position.setX(-entity.width / 2);\n entity.textObj.position.setY(entity.height / 2);\n }\n });\n }\n\n /**\n * @function\n * @description The per global scene event update call. Handles updating all text items including updates for style and cleaning of content for special characters.\n */\n eventUpdate = () => {\n for (const entity of this.registry) {\n this.checkIfTextContentChanged(entity);\n this.handleTextContentUpdate(entity);\n }\n };\n\n /**\n * @function\n * @description Updates the style for the text's information based on compStyle and inputted css elements.\n * @param {MRTextEntity} entity - the text entity whose style is being updated\n */\n updateStyle = (entity) => {\n const { textObj } = entity;\n\n // Font\n textObj.font = textObj.text.trim().length != 0 ? this.preloadedFonts[entity.compStyle.fontFamily] : null;\n textObj.fontSize = this.parseFontSize(entity.compStyle.fontSize, entity);\n textObj.fontWeight = this.parseFontWeight(entity.compStyle.fontWeight);\n textObj.fontStyle = entity.compStyle.fontStyle;\n\n // Alignment\n textObj.anchorY = this.getVerticalAlign(entity.compStyle.verticalAlign, entity);\n textObj.textAlign = this.getTextAlign(entity.compStyle.textAlign);\n textObj.lineHeight = this.getLineHeight(entity.compStyle.lineHeight, entity);\n\n // Color and opacity\n // TODO - swap this to use the mrjsUtils.color.setObject3DColor function in future.\n // For now since that creates a weird affect for styling (white edges), leaving as the\n // current implementation. This probably just means there's a default style css thing\n // we need to change before we swap.\n this.setTextObject3DColor(textObj, entity.compStyle.color);\n\n // Whitespace and Wrapping\n textObj.whiteSpace = entity.compStyle.whiteSpace ?? textObj.whiteSpace;\n textObj.maxWidth = entity.width * 1.001;\n\n // Offset position for visibility on top of background plane\n textObj.position.z = 0.0001;\n };\n\n /**\n * @function\n * @description Handles when text is added as an entity updating content and style for the internal textObj appropriately.\n * @param {MRTextEntity} entity - the text entity being updated\n */\n addText = (entity) => {\n const text = entity.textContent.trim();\n entity.textObj.text = text.length > 0 ? text : ' ';\n\n this.updateStyle(entity);\n };\n\n /**\n * @function\n * @description parses the font weight as 'bold', 'normal', etc based on the given weight value\n * @param {number} weight - the numerical representation of the font-weight\n * @returns {string} - the enum of 'bold', 'normal', etc\n */\n parseFontWeight(weight) {\n if (weight >= 500) {\n return 'bold';\n }\n return 'normal';\n }\n\n /**\n * @function\n * @description parses the font size based on its `XXpx` value and converts it to a usable result based on the virtual display resolution\n * @param {number} val - the value being adjusted\n * @param {object} el - the css element handler\n * @returns {number} - the font size adjusted for the display as expected\n */\n parseFontSize(val, el) {\n const result = parseFloat(val.split('px')[0]) / mrjs__WEBPACK_IMPORTED_MODULE_7__.mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;\n return result;\n }\n\n /**\n * @function\n * @description Gets the vertical align\n * @param {number} verticalAlign - the numerical representation in pixel space of the vertical Align\n * @param {MREntity} entity - the entity whose comp style (css) is relevant\n * @returns {string} - the string representation of the the verticalAlign\n */\n getVerticalAlign(verticalAlign, entity) {\n let result = verticalAlign;\n\n if (typeof result === 'number') {\n result /= mrjs__WEBPACK_IMPORTED_MODULE_7__.mrjsUtils.css.pxToThree(entity.compStyle.fontSize);\n }\n\n switch (result) {\n case 'baseline':\n case 'sub':\n case 'super':\n return 0;\n case 'text-top':\n return 'top-ex';\n case 'text-bottom':\n return 'bottom';\n case 'middle':\n default:\n return result;\n }\n }\n\n /**\n * @function\n * @description Gets the line height\n * @param {number} lineHeight - the numerical representation in pixel space of the line height\n * @param {MREntity} entity - the entity whose comp style (css) is relevant\n * @returns {number} - the numerical representation of the the lineHeight\n */\n getLineHeight(lineHeight, entity) {\n let result = mrjs__WEBPACK_IMPORTED_MODULE_7__.mrjsUtils.css.pxToThree(lineHeight);\n\n if (typeof result === 'number') {\n result /= mrjs__WEBPACK_IMPORTED_MODULE_7__.mrjsUtils.css.pxToThree(entity.compStyle.fontSize);\n }\n\n return result;\n }\n\n /**\n * @function\n * @description Gets the text alignment string\n * @param {string} textAlign - handles values for `start`, `end`, `left`, and `right`; otherwise, defaults to the same input as `textAlign`.\n * @returns {string} - the resolved `textAlign`.\n */\n getTextAlign(textAlign) {\n if (textAlign == 'start') {\n return 'left';\n } else if (textAlign == 'end') {\n return 'right';\n }\n return textAlign;\n }\n\n /**\n * @function\n * @description Based on the given font-face value in the passed cssString, tries to either use by default or download the requested font-face\n * for use by the text object.\n * @param {string} cssString - the css string to be parsed for the font-face css value.\n * @returns {object} - json object respresenting the preloaded font-face\n */\n parseFontFace(cssString) {\n const obj = {};\n const match = cssString.match(/@font-face\\s*{\\s*([^}]*)\\s*}/);\n\n if (match) {\n const fontFaceAttributes = match[1];\n const attributes = fontFaceAttributes.split(';');\n\n attributes.forEach((attribute) => {\n const [key, value] = attribute.split(':').map((item) => item.trim());\n if (key === 'src') {\n const urlMatch = value.match(/url\\(\"([^\"]+)\"\\)/);\n if (urlMatch) {\n obj[key] = urlMatch[1];\n }\n } else {\n obj[key] = value;\n }\n });\n }\n\n return obj;\n }\n\n setTextObject3DColor = function (object3D, color, default_color = '#000') {\n if (color.includes('rgba')) {\n const rgba = color\n .substring(5, color.length - 1)\n .split(',')\n .map((part) => parseFloat(part.trim()));\n object3D.material.color.setStyle(`rgb(${rgba[0]}, ${rgba[1]}, ${rgba[2]})`);\n\n object3D.material.opacity = rgba[3];\n } else {\n object3D.material.color.setStyle(color ?? '#000');\n }\n object3D.material.needsUpdate = true;\n };\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/componentSystems/TextSystem.js?"); /***/ }), @@ -773,7 +773,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRTextAreaEntity: () => (/* binding */ MRTextAreaEntity)\n/* harmony export */ });\n/* harmony import */ var mrjs_core_entities_MRTextInputEntity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/entities/MRTextInputEntity */ \"./src/core/entities/MRTextInputEntity.js\");\n\n\n\n\n/**\n * @class MRTextAreaEntity\n * @classdesc The text area element that simulates the behavior of an HTML