Skip to content

Commit

Permalink
Fix missing element dimensions (#561)
Browse files Browse the repository at this point in the history
  • Loading branch information
vadimdemedes committed Mar 17, 2023
1 parent c97a2ee commit 5f09368
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 15 deletions.
1 change: 1 addition & 0 deletions src/dom.ts
Expand Up @@ -32,6 +32,7 @@ export type DOMElement = {
// Internal properties
isStaticDirty?: boolean;
staticNode?: DOMElement;
onComputeLayout?: () => void;
onRender?: () => void;
onImmediateRender?: () => void;
} & InkNode;
Expand Down
34 changes: 26 additions & 8 deletions src/ink.tsx
Expand Up @@ -8,6 +8,8 @@ import autoBind from 'auto-bind';
import signalExit from 'signal-exit';
import patchConsole from 'patch-console';
import {type FiberRoot} from 'react-reconciler';
// eslint-disable-next-line n/file-extension-in-import
import Yoga from 'yoga-wasm-web/auto';
import reconciler from './reconciler.js';
import render from './renderer.js';
import * as dom from './dom.js';
Expand Down Expand Up @@ -49,6 +51,7 @@ export default class Ink {

this.options = options;
this.rootNode = dom.createNode('ink-root');
this.rootNode.onComputeLayout = this.calculateLayout;

this.rootNode.onRender = options.debug
? this.onRender
Expand Down Expand Up @@ -107,29 +110,43 @@ export default class Ink {
}

if (!isCi) {
options.stdout.on('resize', this.onRender);
options.stdout.on('resize', this.resized);

this.unsubscribeResize = () => {
options.stdout.off('resize', this.onRender);
options.stdout.off('resize', this.resized);
};
}
}

resized = () => {
this.calculateLayout();
this.onRender();
};

resolveExitPromise: () => void = () => {};
rejectExitPromise: (reason?: Error) => void = () => {};
unsubscribeExit: () => void = () => {};

calculateLayout = () => {
// The 'columns' property can be undefined or 0 when not using a TTY.
// In that case we fall back to 80.
const terminalWidth = this.options.stdout.columns || 80;

this.rootNode.yogaNode!.setWidth(terminalWidth);

this.rootNode.yogaNode!.calculateLayout(
undefined,
undefined,
Yoga.DIRECTION_LTR
);
};

onRender: () => void = () => {
if (this.isUnmounted) {
return;
}

const {output, outputHeight, staticOutput} = render(
this.rootNode,
// The 'columns' property can be undefined or 0 when not using a TTY.
// In that case we fall back to 80.
this.options.stdout.columns || 80
);
const {output, outputHeight, staticOutput} = render(this.rootNode);

// If <Static> output isn't empty, it means new children have been added to it
const hasStaticOutput = staticOutput && staticOutput !== '\n';
Expand Down Expand Up @@ -243,6 +260,7 @@ export default class Ink {
return;
}

this.calculateLayout();
this.onRender();
this.unsubscribeExit();

Expand Down
4 changes: 4 additions & 0 deletions src/reconciler.ts
Expand Up @@ -76,6 +76,10 @@ export default createReconciler<
preparePortalMount: () => null,
clearContainer: () => false,
resetAfterCommit(rootNode) {
if (typeof rootNode.onComputeLayout === 'function') {
rootNode.onComputeLayout();
}

// Since renders are throttled at the instance level and <Static> component children
// are rendered only once and then get deleted, we need an escape hatch to
// trigger an immediate render to ensure <Static> children are written to output before they get erased
Expand Down
8 changes: 1 addition & 7 deletions src/renderer.ts
@@ -1,5 +1,3 @@
// eslint-disable-next-line n/file-extension-in-import
import Yoga from 'yoga-wasm-web/auto';
import renderNodeToOutput from './render-node-to-output.js';
import Output from './output.js';
import {type DOMElement} from './dom.js';
Expand All @@ -10,12 +8,8 @@ type Result = {
staticOutput: string;
};

const renderer = (node: DOMElement, terminalWidth: number): Result => {
node.yogaNode!.setWidth(terminalWidth);

const renderer = (node: DOMElement): Result => {
if (node.yogaNode) {
node.yogaNode.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);

const output = new Output({
width: node.yogaNode.getComputedWidth(),
height: node.yogaNode.getComputedHeight()
Expand Down
26 changes: 26 additions & 0 deletions test/measure-element.tsx
@@ -1,6 +1,7 @@
import React, {useState, useRef, useEffect} from 'react';
import test from 'ava';
import delay from 'delay';
import stripAnsi from 'strip-ansi';
import {Box, Text, render, measureElement} from '../src/index.js';
import createStdout from './helpers/create-stdout.js';

Expand All @@ -27,3 +28,28 @@ test('measure element', async t => {
await delay(100);
t.is((stdout.write as any).lastCall.args[0], 'Width: 100');
});

test.serial('calculate layout while rendering is throttled', async t => {
const stdout = createStdout();

function Test() {
const [width, setWidth] = useState(0);
const ref = useRef(null);

useEffect(() => {
setWidth(measureElement(ref.current as any).width);
}, []);

return (
<Box ref={ref}>
<Text>Width: {width}</Text>
</Box>
);
}

const {rerender} = render(null, {stdout, patchConsole: false});
rerender(<Test />);
await delay(50);

t.is(stripAnsi((stdout.write as any).lastCall.firstArg).trim(), 'Width: 100');
});

0 comments on commit 5f09368

Please sign in to comment.