Ink: Get width of <Box>

Created on 20 Mar 2019  Â·  23Comments  Â·  Source: vadimdemedes/ink

Is it possible to obtain the width of the Box element in Ink 2.0?

enhancement

Most helpful comment

I'm thinking about measureElement method that Ink could expose that would allow you to measure any element after component is mounted. For example:

import {measureElement} from 'ink';

class Hello extends React.Component {
  constructor() {
    super()

    this.elementRef = React.createRef()
  }

  render() {
    return (
      <SomeElement ref={this.elementRef}/>
    )
  }

  componentDidMount() {
    const dimensions = measureElement(this.elementRef)
    //=> { width: 40, height: 20 }
  }
}

What do you think?

All 23 comments

There's a way, but I'm still thinking how to properly expose it in a way that can live on long term. Hopefully I'm going to finish it soon and get back to you in this thread.

Related: https://github.com/vadimdemedes/ink/issues/5.

I forgot to ask, what's the use case for this? When would you need to know a specific width of <Box>?

I want to create a general table component with <tr> and <td> - similar to how tables work in plain HTML where everything is aligned by default.

Hm, would you be able to use flexbox properties like flexGrow to achieve the same layout?

A possible use case is to "hack" something to have border as if I'm right, there is no way to have a bordered box for now.

Anyways, if anyone is creating layouts inside Box with fancy component that depends on the parent's dimensions, I guess both width and height should be exposed...
i.e.

<Box height={10} width={20}>
  <FancyComponent
    // that wants to use 100% of available space (parent)
    // needs to know width/height
  />
</Box>

I think the flexbox comportement of Box should work in that case

Le lun. 1 avr. 2019 à 07:32, danikaze notifications@github.com a écrit :

Anyways, if anyone is creating layouts inside Box with fancy component
that depends on the parent's dimensions, I guess both width and height
should be exposed...
i.e.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/vadimdemedes/ink/issues/168#issuecomment-478441416,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABVWxXlxHWaSZzE-ApJ5qjxeNe2mSjHYks5vcZoLgaJpZM4b-q97
.

I think the flexbox comportement of Box should work in that case

@jdeniau I'm sorry I'm a bit new with this library but... how would it be done if the <FancyComponent> actually wants to render like a <Card>? Like, in this case, 100% = 20x10 _tiles_ with background color?
How card knows it's actual available size?
Using other <Box> components inside so they rely on the outter one? but again, if only Box doesn't support what you want to do (in this case, background color) how to do it?

I don't think flexGrow is an option. I haven't experimented with flex-box limits, however, to my understanding that wouldn't solve the problem because flexGrow is only row-agnostic which leads to well-sized rows, stretched to fill the horizontal space, but doesn't account for the size of other cells in the column.

My current workaround involves having context with all elements and finding the maximum width of the entries.

https://github.com/maticzav/emma-cli/blob/66c1bed0b1d6d0b47ccaf0922d2e988f4f6cd7e1/src/components/Package.tsx#L166

I don't think flexGrow is an option. I haven't experimented with flex-box limits, however, to my understanding that wouldn't solve the problem because flexGrow is only row-agnostic which leads to well-sized rows, stretched to fill the horizontal space, but doesn't account for the size of other cells in the column.

exactly my point

@danikaze @maticzav If I understand, and test it right, there are some issues when using height on a Box or when not using textWrap.
But if I understand ink and flexbox correctly, the Box should take the all width and height of its parent container. If it is not, then I think it's a bug, and passing height and width to it's children is a hack to fix a bug.

If you have this :

import React from 'react';
import { render, Box } from 'ink';

const text =
  'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris accumsan pellentesque enim, et scelerisque lacus varius et. Vivamus finibus nisi vel lorem vestibulum mollis in sed purus. Ut vitae quam felis. Integer cursus, quam at lacinia iaculis, mauris lacus tempus augue, at lobortis nibh libero in lectus. Suspendisse congue facilisis velit, at cursus ante tincidunt a. Duis sagittis faucibus ligula, ut euismod nulla maximus sit amet. Suspendisse pellentesque facilisis erat vel pretium. Donec tempor, elit tempus tempor mattis, nunc urna ultrices felis, id viverra leo justo ac arcu. Ut ac erat id lectus consequat faucibus eu elementum lorem. Aliquam ut justo ac justo tempor convallis at vitae neque. Nam a purus maximus, rhoncus dolor vel, bibendum nunc. Donec et malesuada turpis. Vestibulum lacinia lectus eu sem cursus laoreet.';                                                      

function SomeText() {
  return <Box>{text}</Box>;
}

function App() {
  return (
    <Box>
      <Box width={10}>10</Box>
      <SomeText />
    </Box>
  );
}

render(<App />);

It renders:

10 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris accumsan pellentesque enim, et scelerisque lacus varius et. Vivamus finibus nisi vel lorem vestibulum mollis in sed purus. Ut vitae quam felis. Integer cursus
, quam at lacinia iaculis, mauris lacus tempus augue, at lobortis nibh libero in lectus. Suspendisse congue facilisis velit, at cursus ante tincidunt a. Duis sagittis faucibus ligula, ut euismod nulla maximus sit amet. Suspen
disse pellentesque facilisis erat vel pretium. Donec tempor, elit tempus tempor mattis, nunc urna ultrices felis, id viverra leo justo ac arcu. Ut ac erat id lectus consequat faucibus eu elementum lorem. Aliquam ut justo ac j
usto tempor convallis at vitae neque. Nam a purus maximus, rhoncus dolor vel, bibendum nunc. Donec et malesuada turpis. Vestibulum lacinia lectus eu sem cursus laoreet.

Wich seems weird, but if you add a textWrap on SomeText, it work "as expected":

import React from 'react';
import { render, Box, Text } from 'ink';

const text =
  'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris accumsan pellentesque enim, et scelerisque lacus varius et. Vivamus finibus nisi vel lorem vestibulum mollis in sed purus. Ut vitae quam felis. Integer cursus, quam at lacinia iaculis, mauris lacus tempus augue, at lobortis nibh libero in lectus. Suspendisse congue facilisis velit, at cursus ante tincidunt a. Duis sagittis faucibus ligula, ut euismod nulla maximus sit amet. Suspendisse pellentesque facilisis erat vel pretium. Donec tempor, elit tempus tempor mattis, nunc urna ultrices felis, id viverra leo justo ac arcu. Ut ac erat id lectus consequat faucibus eu elementum lorem. Aliquam ut justo ac justo tempor convallis at vitae neque. Nam a purus maximus, rhoncus dolor vel, bibendum nunc. Donec et malesuada turpis. Vestibulum lacinia lectus eu sem cursus laoreet.';                                                      

function SomeText() {
  return <Box textWrap="wrap">{text}</Box>;
}

function App() {
  return (
    <Box>
      <Box width={10}>10</Box>
      <SomeText />
    </Box>
  );
}

render(<App />);

renders:

10        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris accumsan pellentesque enim, et scelerisque lacus varius et. Vivamus finibus nisi vel lorem vestibulum mollis in sed purus. Ut vitae quam felis. Integer
           cursus, quam at lacinia iaculis, mauris lacus tempus augue, at lobortis nibh libero in lectus. Suspendisse congue facilisis velit, at cursus ante tincidunt a. Duis sagittis faucibus ligula, ut euismod nulla maximus
           sit amet. Suspendisse pellentesque facilisis erat vel pretium. Donec tempor, elit tempus tempor mattis, nunc urna ultrices felis, id viverra leo justo ac arcu. Ut ac erat id lectus consequat faucibus eu elementum
          lorem. Aliquam ut justo ac justo tempor convallis at vitae neque. Nam a purus maximus, rhoncus dolor vel, bibendum nunc. Donec et malesuada turpis. Vestibulum lacinia lectus eu sem cursus laoreet.

which seems fine.

I found another issue too when using height : #179

I think we are talking about two different things, though it is possible that I am missing something.

My primary intention with this issue was to see whether it is possible to construct HTML-like tables in Ink. What I mean with the table is not a bordered list but rather an aligned form where columns self align based on the width of the cells independent of the width of the container.

OK, I was linking to ink-table, but you did that :rofl:

If ink is flex only, then you are right, it's hard to do table-like without having a parent context.

As a matter of fact, I needed that today too, and I "hacked" by computing length of all my children (only string, so easy to do) : https://github.com/jdeniau/changelog-view/blob/da3dc76/src/component/ChangelogViewResult.js#L37-L40

I'm thinking about measureElement method that Ink could expose that would allow you to measure any element after component is mounted. For example:

import {measureElement} from 'ink';

class Hello extends React.Component {
  constructor() {
    super()

    this.elementRef = React.createRef()
  }

  render() {
    return (
      <SomeElement ref={this.elementRef}/>
    )
  }

  componentDidMount() {
    const dimensions = measureElement(this.elementRef)
    //=> { width: 40, height: 20 }
  }
}

What do you think?

It does seems to work, but it seems to be not that used in the React ecosystem, isn't it ?

Moreover, it may be difficult to react to a size change (if you resize your terminal for example).

Another solution might to expose a onDimensionChanged callback on Box, that is called on first rendering and when the box dimension did change, but I'm not really happy with this either ^^

measureElement seems like a very nice approach. I don't think resize of the terminal should be a problem since you could just rerun the measurement on a ref once the event is triggered.

@vadimdemedes could you give an example of how ”list” operations would be handled this way? I am curious what would happen if we wanted to get sizes of multiple children using refs. Is that a typical pattern?

It does seems to work, but it seems to be not that used in the React ecosystem, isn't it ?

@jdeniau Yeah, that would be a thing custom to Ink, since we have different limitations in the terminal environment.

@vadimdemedes could you give an example of how ”list” operations would be handled this way? I am curious what would happen if we wanted to get sizes of multiple children using refs. Is that a typical pattern?

@maticzav That's a good point, it seems this solution would only work for measuring single elements. I'm also thinking whether providing a custom function for text truncating would eliminate the need for measuring elements. That function would receive available space and of course it could measure the target text.

I can't see another good way to allow something like having a scrolling list that must stay within the bounds of the window vertically without being able to calculate the height allowed and any wrapping that is happening within the contents. @vadimdemedes

Hey folks, here's an example of a table with equal width columns based on the width of the table -> https://gist.github.com/vadimdemedes/11eea9f3b3a392670bcd6be1708593c2. Note that you need to install Ink from master branch (npm i vadimdemedes/ink).

Do you think you'd still need something like measureElement()?

I think that idea behind measureElement was the ability to change the width of columns based on the content. That would match the behaviour of HTML table component. I guess that if content overflows in your example, nothing happens. Is that right?

If there was a long text in one of the columns, it would get wrapped (you can customize how).

If you want to measure content first, couldn’t you just use String.prototype.length?

The core idea was that we would have a generalised table component in Ink. It's, of course, possible to simply measure the length of strings and implement the flexible table for a particular use case. It's quite tricky, however, to create a general component that accepts react element as a child.

Do you have an idea of how that would be possible?

It's quite tricky, however, to create a general component that accepts react element as a child.

Why not? See the table example I sent above, you can use any child in each row and it will still lay out columns correctly.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nmehta6 picture nmehta6  Â·  3Comments

danikaze picture danikaze  Â·  5Comments

goliney picture goliney  Â·  4Comments

zkat picture zkat  Â·  7Comments

colinking picture colinking  Â·  3Comments