setShowToolbar(true)} contentEditable={false}>
+
setShowToolbar(true)} contentEditable={false}>
{showToolbar &&
@@ -322,7 +399,7 @@ export const MathNodeView = (props) => {
position: 'absolute',
top: `${position.top}px`,
left: `${position.left}px`,
- zIndex: 20,
+ zIndex: 1000,
background: 'var(--editable-html-toolbar-bg, #efefef)',
boxShadow:
'0px 1px 5px 0px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.12)',
diff --git a/packages/mask-markup/src/components/blank.jsx b/packages/mask-markup/src/components/blank.jsx
index 469c9971b..5413ded7c 100644
--- a/packages/mask-markup/src/components/blank.jsx
+++ b/packages/mask-markup/src/components/blank.jsx
@@ -106,6 +106,7 @@ function BlankContent({
const rootRef = useRef(null);
const spanRef = useRef(null);
const frozenRef = useRef(null); // to use during dragging to prevent flickering
+ const measuringRef = useRef(false); // guard against ResizeObserver feedback loops
const [dimensions, setDimensions] = useState({ height: 0, width: 0 });
const handleImageLoad = () => {
@@ -133,7 +134,8 @@ function BlankContent({
};
const updateDimensions = () => {
- if (spanRef.current && rootRef.current) {
+ if (spanRef.current && rootRef.current && !measuringRef.current) {
+ measuringRef.current = true;
// Temporarily set rootRef width to 'auto' for natural measurement
rootRef.current.style.width = 'auto';
rootRef.current.style.height = 'auto';
@@ -164,8 +166,15 @@ function BlankContent({
height: adjustedHeight > responseAreaHeight ? adjustedHeight : prevState.height,
}));
- rootRef.current.style.width = `${adjustedWidth}px`;
- rootRef.current.style.height = `${adjustedHeight}px`;
+ const nextWidth = `${adjustedWidth}px`;
+ const nextHeight = `${adjustedHeight}px`;
+ if (rootRef.current.style.width !== nextWidth) {
+ rootRef.current.style.width = nextWidth;
+ }
+ if (rootRef.current.style.height !== nextHeight) {
+ rootRef.current.style.height = nextHeight;
+ }
+ measuringRef.current = false;
}
};
@@ -191,6 +200,20 @@ function BlankContent({
handleElements();
}, []);
+ // Re-measure when the element first becomes visible — covers the tabbed-view case
+ // where the initial measurement happened while the tab was hidden (size 0).
+ useEffect(() => {
+ if (typeof IntersectionObserver === 'undefined' || !rootRef.current) return undefined;
+ const io = new IntersectionObserver(
+ (entries) => {
+ if (entries.some((e) => e.isIntersecting)) updateDimensions();
+ },
+ { threshold: 0 },
+ );
+ io.observe(rootRef.current);
+ return () => io.disconnect();
+ }, []);
+
// Render math for the placeholder/preview when dragging over
useEffect(() => {
if (rootRef.current) {
@@ -204,7 +227,7 @@ function BlankContent({
return;
}
handleElements();
- }, [choice]);
+ }, [choice?.value]);
useEffect(() => {
if (!isOver && !isDragging) {
diff --git a/packages/math-toolbar/src/__tests__/editor-and-pad.test.js b/packages/math-toolbar/src/__tests__/editor-and-pad.test.js
index 9777bf0f0..9ce5f1105 100644
--- a/packages/math-toolbar/src/__tests__/editor-and-pad.test.js
+++ b/packages/math-toolbar/src/__tests__/editor-and-pad.test.js
@@ -9,8 +9,66 @@ describe('EditorAndPad', () => {
onBlur: jest.fn(),
};
+ beforeEach(() => {
+ jest.useFakeTimers();
+ });
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
it('renders with default props', () => {
const { container } = render(
);
expect(container.firstChild).toBeInTheDocument();
});
+
+ describe('autoFocus', () => {
+ it('focuses input immediately via setTimeout when autoFocus is true', () => {
+ const mockFocus = jest.fn();
+ const component = new EditorAndPad({ ...defaultProps, autoFocus: true });
+ component.input = { focus: mockFocus };
+
+ component.componentDidMount();
+
+ expect(mockFocus).not.toHaveBeenCalled();
+
+ jest.runAllTimers();
+
+ expect(mockFocus).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not focus input when autoFocus is false', () => {
+ const mockFocus = jest.fn();
+ const component = new EditorAndPad({ ...defaultProps, autoFocus: false });
+ component.input = { focus: mockFocus };
+
+ component.componentDidMount();
+ jest.runAllTimers();
+
+ expect(mockFocus).not.toHaveBeenCalled();
+ });
+
+ it('does not focus when input ref is not set', () => {
+ const component = new EditorAndPad({ ...defaultProps, autoFocus: true });
+ component.input = null;
+
+ expect(() => {
+ component.componentDidMount();
+ jest.runAllTimers();
+ }).not.toThrow();
+ });
+
+ it('uses setTimeout with 0ms delay to defer focus', () => {
+ const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
+ const mockFocus = jest.fn();
+ const component = new EditorAndPad({ ...defaultProps, autoFocus: true });
+ component.input = { focus: mockFocus };
+
+ component.componentDidMount();
+
+ expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 0);
+
+ setTimeoutSpy.mockRestore();
+ });
+ });
});
diff --git a/packages/math-toolbar/src/editor-and-pad.jsx b/packages/math-toolbar/src/editor-and-pad.jsx
index 56ce922f6..1dd4adc22 100644
--- a/packages/math-toolbar/src/editor-and-pad.jsx
+++ b/packages/math-toolbar/src/editor-and-pad.jsx
@@ -263,7 +263,8 @@ export class EditorAndPad extends React.Component {
componentDidMount() {
if (this.input && this.props.autoFocus) {
- this.input.focus();
+ // adding a timeout to wait for other stuff related to focus to be finished
+ setTimeout(() => this.input.focus(), 0);
}
}