Hello! I've been playing with Ink yesterday, and I really like where you're going. I've worked on something similar before, but your approach seems much simpler and less labyrinthine. Kudos!
One particular thing I'm finding myself missing, however, are scrollable containers. I anticipate that printing a lot of data will eventually cause my display to go beyond the height of a regular terminal (particularly when working with lists of interactive elements), so scroll will be quite important.
I'm trying to think how would one implement this feature through userland blocks, but I don't see how it could be done - I remember that implementing this in my library has required to first support overflows, meaning computing intersections between the various nodes printed on screen.
Do you have some thoughts about this topic?
Hi, thanks! I want to merge https://github.com/vadimdemedes/ink/pull/210 to solve this problem, I just want to test it more extensively. With this PR seems you won't need scrollable containers anymore.
Hm but that doesn't solve the issue for interactive elements, right? For example, if we imagine a list of radio buttons, the ones beyond the top of the screen would never be visible without first-party scrolling (terminals support some basic scroll, but typing anything would put back the scroll to the bottom).
Yep, in this case components have to implement their own scrolling, similar to https://github.com/vadimdemedes/ink-select-input.
Maybe what @arcanis means here is something of a general purpose scrolling component that can wrap any number of components as children and allow scrolling through it. I tried to write something like that and bumped into a few issues:
react-native that also uses yoga, using measure functions)overflow like in react-native where all sub-components of a Box would have been clipped. neither there is zIndex. as a result if the scroll view is just next to a header, scrolling through elements will render on top of the headerinterface IScrollViewProps {
totalHeight: number;
viewportHeight: number;
}
export const VerticalScrollView: React.FC<IScrollViewProps> = props => {
const thumbHeight = 3;
const [offset, setOffsetCore] = React.useState(0);
const thumbPosition =
(offset / (props.totalHeight - (props.viewportHeight - thumbHeight))) *
props.viewportHeight;
const setOffset = (changer: (value: number) => number) => {
return setOffsetCore(value => {
const next = changer(value);
return Math.min(
Math.max(next, 0),
props.totalHeight - props.viewportHeight - 1
);
});
};
useInput((text, key) => {
if (key.upArrow || text === 'OA') {
setOffset(value => value - 1);
}
if (key.downArrow || text === 'OB') {
setOffset(value => value + 1);
}
});
return (
<Box flexDirection="row" flexGrow={1} justifyContent="space-between">
<Box marginY={-offset}>{props.children}</Box>
<Box width={1} marginTop={thumbPosition} textWrap="wrap">
|||
</Box>
</Box>
);
};
In my example below the header gets erased by the list as we scroll down:
export const App: React.FC<{}> = () => {
const items = range(100);
const screen = screenSize();
const viewportHeight = screen.height - 5;
const totalHeight = items.length;
return (
<Box {...screen} flexDirection="column">
<Box flexShrink={0}>
<Color red>Header should stay visible</Color>
</Box>
<Box flexShrink={0}>
<Color red>Header should stay visible</Color>
</Box>
<Box flexGrow={1} flexDirection="column">
<VerticalScrollView
totalHeight={totalHeight}
viewportHeight={viewportHeight}
>
<Box flexDirection="column" flexGrow={1}>
{items.map((item, i) => (
<Box key={i} flexDirection="row" flexShrink={0}>
<Text>{item.toString(10)}</Text>
</Box>
))}
</Box>
</VerticalScrollView>
</Box>
<Box flexShrink={0}>
<Color red>Footer should stay visible</Color>
</Box>
<Box flexShrink={0}>
Footer should stay visible; With viewport height{' '}
<Color green>{viewportHeight}</Color> and Screen height{' '}
<Color green>{screen.height}</Color>
</Box>
</Box>
);
};
@vadimdemedes do you think Ink could be modified to support these features?
Off the topic, it also seemed like I was missing alignSelf property and stretch option for alignItems property.
Maybe what @arcanis means here is something of a general purpose scrolling component that can wrap any number of components as children and allow scrolling through it. I tried to write something like that and bumped into a few issues:
Yes exactly. Fixed-size single-row elements are easy enough to scroll, but as soon as you get more complex setups it becomes kind of a pain - even fixed-size multi-row elements aren't trivial to implement this way since you then need to find a way to make them partially visible (or you take a shortcut and make the scroll size equal to the element size).
In my case for example, I have a bunch of elements like this. When using Ink on large projects, the list goes well past the size of the screen and most elements are hidden:

Lack of scrolling is maybe the single biggest issue we have we Ink.
I don't have a solution for it short-term and browser-like focus management is also required to make scrolling work, which certainly complicates things. Until Ink provides some sort of feature for this, custom scrolling implementation is the way to go for this sort of UI.
Hey @vadimdemedes ... I agree that focus management might be complicated. However, at the moment, it can be implemented by the users of the library, using useInput hook and a couple of custom baked hooks that will have to be used by every input element.
The layout issues that I found, however, can only be fixed through exposing yoga API from Box instance (similar to unstable_getComputedWidth) methods or through introduction of additional helper Context. So, they can only be fixed by changing ink.
Even if ink doesn’t provide a ScrollBox component I think if we fix those issues the community would be able to create their own version of ScrollBox.
I was able to change the experimental renderer to clip sub-elements of a Box that I had overflow=“hidden” set in the tree. I’m still at early stages and need to debug and measure performance impact and test, but I think I should be able to create a PR.
The issues that I would like to address are:
Box elementsBox by setting overflow=“hidden”I'm running into this as well, I need to scroll at the size of the screen minus the area taken by boxes above/below the component. I can implement scrolling based on some fixed height, but have no good way of knowing the sizes of boxes.
Most helpful comment
Hey @vadimdemedes ... I agree that focus management might be complicated. However, at the moment, it can be implemented by the users of the library, using
useInputhook and a couple of custom baked hooks that will have to be used by every input element.The layout issues that I found, however, can only be fixed through exposing
yogaAPI fromBoxinstance (similar tounstable_getComputedWidth) methods or through introduction of additional helperContext. So, they can only be fixed by changingink.Even if
inkdoesn’t provide aScrollBoxcomponent I think if we fix those issues the community would be able to create their own version ofScrollBox.I was able to change the experimental renderer to clip sub-elements of a
Boxthat I hadoverflow=“hidden”set in the tree. I’m still at early stages and need to debug and measure performance impact and test, but I think I should be able to create a PR.The issues that I would like to address are:
BoxelementsBoxby settingoverflow=“hidden”