`measureTemporaryTextBlock` in FireFox returns always textBlock with lineCount = 1

Hello!

Title says it all. When in Chrome

tool.measureTemporaryTextBlock(textArea.value).lineCount

returns >1, Firefox returns always 1.
No idea how to overcome this.
Assist required.

Thanks.
Vlad.

I just tried calling it in Firefox, and it returned 2 or 9. So what you are saying is not always true.

We’ll investigate though, to see if we can guess what your problem is. More information is needed before we can proceed. What is your TextBlock definition, what was the original string that it was showing, and what string is being passed to measureTemporaryTextBlock?

Hi Walter.

this textBlock is part of a panel type spot, which is in its order part of a panel type table

			go.GraphObject.make(
				go.TextBlock,
				{
					name: "textEditorName",
					margin: new go.Margin(isPalette ? 7 : 10, 0, 0, 0),
					minSize: new go.Size(110, 32),
					maxSize: new go.Size(136, 15),
					row: 0,
					column: 1,
					isMultiline: true,
					maxLines: 2,
					overflow: go.TextBlock.OverflowEllipsis,
					alignment: go.Spot.TopLeft,
					editable: true,
					cursor: isPalette ? 'default' : 'text',
					textEditor: customTextEditor,
					spacingBelow: 2,
					stroke: '#000000',
				},
				new go.Binding('text').makeTwoWay()
			),

customTextEditor which is set to textEditor propherty is a

const TextEditor: go.HTMLInfo = new go.HTMLInfo();

I took original textEditor extension and adjust/improve it for my needs.
Here some of the most interesting parts, if something else required, tell me.

textArea.addEventListener(
		'input',
		() => {
			const tool = (TextEditor as any).tool;
			if (tool.textBlock === null) {
				return;
			}
			const tempText = tool.measureTemporaryTextBlock(textArea.value); // return textBlock so it might be possible to update it's sizes.
			const scale = (textArea as any).textScale;
			if (config.onInput) {
				config.onInput(textArea, tempText, scale);
			} else {
				textArea.style.width = tempText.measuredBounds.width * scale + 'px';
			}
			textArea.rows =
				tempText.lineCount > tempText.maxLines ? tempText.maxLines : tempText.lineCount;
		},
		false
	);

	TextEditor.valueFunction = () => textArea.value;
	TextEditor.mainElement = textArea; // to reference it more easily

	(TextEditor as any).tool = null; // Initialize

	// used to be in doActivate
	TextEditor.show = (textBlock: go.GraphObject, diagram: go.Diagram, tool: go.Tool) => {
		if (!diagram || !diagram.div) {
			return;
		}
		if (!(textBlock instanceof go.TextBlock)) {
			return;
		}
		if ((TextEditor as any).tool !== null) {
			return;
		} // Only one at a time.

		(TextEditor as any).tool = tool; // remember the TextEditingTool for use by listeners

		// This is called during validation, if validation failed:
		if ((tool as any).state === (go.TextEditingTool as any).StateInvalid) {
			textArea.style.border = '3px solid red';
			textArea.focus();
			return;
		}

		diagram.commit(() => {
			textBlock.visible = false;
			if (config.onShow) {
				config.onShow(textBlock);
			}
		}, 'onCustomTextEditorShow');

		textArea['_parentKey'] = textBlock.part.data.key;

		if (config.handlePlaceholderText) {
			textArea.value = config.handlePlaceholderText === textBlock.text ? '' : textBlock.text;
		} else {
			textArea.value = textBlock.text;
		}

		textArea.className = config.className;

		const textBlockLocation = textBlock.getDocumentPoint(go.Spot.Center);
		const diagramPosition = diagram.position;
		const diagramScale = diagram.scale;
		let textBlockScale = textBlock.getDocumentScale() * diagramScale;
		if (textBlockScale < (tool as any).minimumEditorScale) {
			textBlockScale = (tool as any).minimumEditorScale;
		}

		let textWidth;
		if (config.widthCalculation) {
			textWidth = config.widthCalculation(textBlock);
		} else {
			textWidth = textBlock.naturalBounds.width;
		}
		textWidth = textWidth * textBlockScale;
		const textHeight = textBlock.naturalBounds.height * textBlockScale;
		const left = (textBlockLocation.x - diagramPosition.x) * diagramScale;
		const top = (textBlockLocation.y - diagramPosition.y) * diagramScale;
		const maxHeight = (textBlock.maxSize ?? textBlock.desiredSize).height + 'px';

		diagram.div.style['font'] = '13px Roboto, Arial, sans-serif';
		textArea.style['font'] = 'inherit';
		textArea.style['fontSize'] = textBlockScale * 100 + '%';
		textArea.style['width'] = textWidth + 'px';
		textArea.style['maxHeight'] = maxHeight;
		textArea.style['left'] = ((left - textWidth / 2) | 0) + 1 + 'px';
		textArea.style['top'] = ((top - textHeight / 2) | 0) - 1 + 'px';
		textArea.style['textAlign'] = textBlock.textAlign;
		textArea.maxLength = 255;
		textArea.rows =
			textBlock.lineCount > textBlock.maxLines ? textBlock.maxLines : textBlock.lineCount;
		(textArea as any).textScale = textBlockScale; // attach a value to the textarea, for convenience

		// Show:
		diagram.div.appendChild(textArea);

		// After adding, focus:
		textArea.focus();
		if ((tool as any).selectsTextOnActivate) {
			textArea.select();
			textArea.setSelectionRange(0, 9999);
		}
	};

	TextEditor.hide = (diagram: go.Diagram) => {
		(TextEditor as any).tool = null; // forget reference to TextEditingTool
		const key = textArea['_parentKey'];
		if (diagram.div) {
			diagram.div.removeChild(textArea);
		}
		diagram.commit(() => {
			const parent = diagram.findPartForKey(key);
			if (parent) {
				const textBlock = parent.findObject(config.textBlockSelector);
				if (config.handlePlaceholderText && textArea.value === '') {
					textBlock.setProperties({text: config.handlePlaceholderText});
				}
				textBlock.visible = true;
				if (config.onHide) {
					config.onHide(textBlock);
				}
			}
		}, 'onCustomTextEditorHide');
	};

In using your TextBlock in a Node and logging the value of:

tool.measureTemporaryTextBlock(textArea.value).lineCount

in your “input” listener, I found that it returned reasonable values as I was typing. This is in both Firefox and Chrome. The wrapping behavior was not precisely the same due to slight differences in the fonts and how text is measured.

I see.
Can you suggest a way to align those differences? Or improve precision of measure.

No, fonts are inherently going to be different on different platforms, even beyond browser differences.

Wait a second Walter, you said that you got the reasonable values of lineCount. But I dont. In Firefox i’m getting always lineCount = 1 no matter which length of inputed text, which breaks textBlock design in FF.

Firefox
ff

Chrome:
chrome

Input text are the same: “This is a placeholder with long name with ellipsis”

That happens because i’m getting lineCount = 1 for temporary textBlock in Firefox, when in Chrome lineCount = 2.

I need to find a way to make it behave alike.

I was able to use your TextBlock but was unable to use the rest of your code, so I only observed that TextEditingTool.measureTemporaryTextBlock does seem to produce a TextBlock with appropriate values for the given string on both Firefox and Chrome.

If you could provide a slightly more complete minimal code sample, then maybe I could try it. Perhaps the whole modified TextEditor.ts file, if the only use of it is as the value of TextBlock.textEditor.

It is also suspicious that the minimum height is larger than the maximum height. Does fixing that help in Firefox?

The issue with max/min height is a misatke which happened in process of pasting code here (sizes was described using variables, I rewrote them after paste it here. in original code heigh values are correct)
I will prepare a code pen example and share it here.

Here is an example: https://codepen.io/uniqalien/pen/dyRBJwL

The problem is that you have specified a maximum height that happens to be too short in Firefox. Change the 32 to 33, and it shows the second line just fine, at least on my machine.

Have you tried this on different devices, especially ones that have different fonts? I bet you’ll get different results, sometimes rather surprising results.

I’ll check it in a moment, but I already tried +2px height before specially for FF
(there is a comment about it in original TextEditor.js which adds +6 to width +2 to height for FF), but it didn’t help.

UPD. it actually work in CodePen. Checking in dev env.

Ok, it works indeed.
Thanks for an assistance Walter.
The thing is that I was adding extra height to textArea element inside textArea.show(). But actually it was the TextBlock itself which needed those extra height.
Bummer that I didnt tried that myself, I was focused on custom text editor.
Thanks once again Walter.

If I were you, I wouldn’t set a fixed maximum or minimum height at all. Setting TextBlock.maxLines should be sufficient for your needs.

A minimum and maximum width are fine, though – especially the maximum one which is needed to force wrapping, unless there is a width constraint coming from the Panel.

is there a way to set max/min width w/o hight?
I can see width/height properties on GraphObject but max/min goes as size which is go.Size(w,h).
Am I missing something?

new go.Size(123, NaN)

The default value for GraphObject.width and GraphObject.height is NaN.
And remember that those two properties are just shortcuts for GraphObject.desiredSize .width and .height.

1 Like