diff --git a/examples/orientation.ts b/examples/orientation.ts deleted file mode 100644 index 45b0840..0000000 --- a/examples/orientation.ts +++ /dev/null @@ -1,68 +0,0 @@ -import {printTable} from '../src/index.js' - -const description = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' - -const data = [ - { - description, - item: 'Item 1', - url: 'https://www.example.com/item/1', - }, - { - description, - item: 'Item 2', - url: 'https://www.example.com/item/2', - }, - { - description, - item: 'Item 3', - url: 'https://www.example.com/item/3', - }, -] - -printTable({ - columns: ['item', 'description', 'url'], - data, - headerOptions: { - formatter: 'capitalCase', - }, - horizontalAlignment: 'center', - overflow: 'wrap', - title: 'Horizontal Orientation', - titleOptions: {bold: true}, -}) - -// When the vertical orientation table wraps, the "value" does not stay in its column -// Example (note the "description" column): -// -// ────────────────────────────────────────────────────────────────────────────────────── -// Item Item 1 -// Description -// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor -// incididunt ut labore et dolore magna aliqua. -// Url https://www.example.com/item/1 -// -// I would expect it to look like this: -// -// Example (note the "description" column): -// ------------------------------------------------ -// Item Item 1 -// Description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do -// eiusmod tempor incididunt ut labore et dolore magna aliqua. -// Url https://www.example.com/item/1 - - - -printTable({ - columns: ['item', 'description', 'url'], - data, - headerOptions: { - formatter: 'capitalCase', - }, - horizontalAlignment: 'center', - orientation: 'vertical', - overflow: 'wrap', - title: 'Vertical Orientation', - titleOptions: {bold: true}, -}) diff --git a/examples/overflow.ts b/examples/overflow.ts index cb6a4f5..7aa33b2 100644 --- a/examples/overflow.ts +++ b/examples/overflow.ts @@ -60,22 +60,6 @@ printTable({ titleOptions: {bold: true}, }) -// I would expect this for wrapping on align-center: -// -// Wrap (aligned center) -// ┌───────┬─────────┬─────┬───────────────────────────────────────────────────────────────────────────┐ -// │ Id │ Name │ Age │ Description │ -// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ -// │ 36329 │ Alice │ 20 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ -// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ -// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ -// │ 49032 │ Bob │ 21 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ -// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ -// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ -// │ 51786 │ Charlie │ 22 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ -// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ -// └───────┴─────────┴─────┴───────────────────────────────────────────────────────────────────────────┘ - printTable({ columns: ['id', 'name', 'age', 'description'], data, @@ -98,24 +82,9 @@ printTable({ overflow: 'wrap', title: 'Wrap (aligned left)', titleOptions: {bold: true}, + verticalAlignment: 'center', }) -// Similar for align-right: -// -// Wrap (aligned right) -// ┌───────┬─────────┬─────┬───────────────────────────────────────────────────────────────────────────┐ -// │ Id │ Name │ Age │ Description │ -// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ -// │ 36329 │ Alice │ 20 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ -// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ -// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ -// │ 49032 │ Bob │ 21 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ -// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ -// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ -// │ 51786 │ Charlie │ 22 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ -// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ -// └───────┴─────────┴─────┴───────────────────────────────────────────────────────────────────────────┘ - printTable({ columns: ['id', 'name', 'age', 'description'], data, @@ -126,4 +95,5 @@ printTable({ overflow: 'wrap', title: 'Wrap (aligned right)', titleOptions: {bold: true}, + verticalAlignment: 'bottom', }) diff --git a/src/table.tsx b/src/table.tsx index 3d1884c..2988c93 100644 --- a/src/table.tsx +++ b/src/table.tsx @@ -128,7 +128,7 @@ function formatTextWithMargins({ const valueWithNoZeroWidthChars = String(value).replaceAll('​', ' ') const spaceForText = width - padding * 2 - if (stripAnsi(valueWithNoZeroWidthChars).length < spaceForText) { + if (stripAnsi(valueWithNoZeroWidthChars).length <= spaceForText) { const spaces = width - stripAnsi(valueWithNoZeroWidthChars).length return { text: valueWithNoZeroWidthChars, @@ -139,12 +139,42 @@ function formatTextWithMargins({ if (overflow === 'wrap') { const wrappedText = wrapAnsi(valueWithNoZeroWidthChars, spaceForText, {hard: true, trim: true, wordWrap: true}) const {marginLeft, marginRight} = calculateMargins(width - determineWidthOfWrappedText(stripAnsi(wrappedText))) - const text = wrappedText.replaceAll('\n', `${' '.repeat(marginRight)}\n${' '.repeat(marginLeft)}`) + + const lines = wrappedText.split('\n').map((line, idx) => { + const {marginLeft: lineSpecificLeftMargin} = calculateMargins(width - stripAnsi(line).length) + + if (horizontalAlignment === 'left') { + if (idx === 0) { + // if it's the first line, only add margin to the right side (The left margin will be applied later) + return `${line}${' '.repeat(marginRight)}` + } + + // if left alignment, add the overall margin to the left side and right sides + return `${' '.repeat(marginLeft)}${line}${' '.repeat(marginRight)}` + } + + if (horizontalAlignment === 'center') { + if (idx === 0) { + // if it's the first line, only add margin to the right side (The left margin will be applied later) + return `${line}${' '.repeat(marginRight)}` + } + + // if center alignment, add line specific margin to the left side and the overall margin to the right side + return `${' '.repeat(lineSpecificLeftMargin)}${line}${' '.repeat(marginRight)}` + } + + // right alignment + if (idx === 0) { + return `${' '.repeat(Math.max(0, lineSpecificLeftMargin - marginLeft))}${line}${' '.repeat(marginRight)}` + } + + return `${' '.repeat(lineSpecificLeftMargin)}${line}${' '.repeat(marginRight)}` + }) return { marginLeft, marginRight, - text, + text: lines.join('\n'), } } @@ -163,7 +193,6 @@ export function Table>(props: TableOptions) horizontalAlignment = 'left', maxWidth, noStyle = false, - orientation = 'horizontal', overflow = 'truncate', padding = 1, sort, @@ -237,48 +266,6 @@ export function Table>(props: TableOptions) skeleton: BORDER_SKELETONS[config.borderStyle].separator, }) - if (orientation === 'vertical') { - return ( - - {title && {title}} - {processedData.map((row, index) => { - // Calculate the hash of the row based on its value and position - const key = `row-${sha1(row)}-${index}` - const maxKeyLength = Math.max(...Object.values(headings).map((c) => c.length)) - // Construct a row. - return ( - - {/* print all data in key:value pairs */} - {columns.map((column) => { - const value = (row[column.column] ?? '').toString() - const keyName = (headings[column.key] ?? column.key).toString() - const keyPadding = ' '.repeat(maxKeyLength - keyName.length + padding) - return ( - - - {keyName} - {keyPadding} - - {value} - - ) - })} - - ) - })} - - ) - } - return ( {title && {title}} @@ -407,7 +394,6 @@ export function Skeleton(props: React.PropsWithChildren & {readonly height?: num export function printTable>(options: TableOptions): void { const instance = render() instance.unmount() - // It might be nice to have a "paddingBefore" and "paddingAfter" option for the number of newlines to enter. process.stdout.write('\n') } @@ -429,8 +415,8 @@ export function printTables[]>( const processed = tables.map((table) => ({ ...table, - // adjust maxWidth to account for margin - maxWidth: determineConfiguredWidth(table.maxWidth, columns), + // adjust maxWidth to account for margin and columnGap + maxWidth: determineConfiguredWidth(table.maxWidth, columns) - (options?.columnGap ?? 0) * tables.length, })) const instance = render( diff --git a/src/types.ts b/src/types.ts index 0d89dfb..c5624de 100644 --- a/src/types.ts +++ b/src/types.ts @@ -113,7 +113,7 @@ export type TableOptions> = { * * If you provide a number or percentage that is larger than the terminal width, it will default to the terminal width. * - * If you provide a number or percentage that is too small to fit the table, it will default to the width of the table. + * If you provide a number or percentage that is too small to fit the table, it will default to the minimum width of the table. */ maxWidth?: Percentage | number /** @@ -125,7 +125,7 @@ export type TableOptions> = { */ headerOptions?: HeaderOptions /** - * Border style for the table. Defaults to 'all'. Only applies to horizontal orientation. + * Border style for the table. Defaults to 'all'. */ borderStyle?: BorderStyle /** @@ -133,7 +133,7 @@ export type TableOptions> = { */ borderColor?: SupportedColor /** - * Align data in columns. Defaults to 'left'. Only applies to horizontal orientation. + * Align data in columns. Defaults to 'left'. */ horizontalAlignment?: HorizontalAlignment /** @@ -170,26 +170,7 @@ export type TableOptions> = { */ sort?: Sort /** - * The orientation of the table. Defaults to 'horizontal'. - * - * If 'vertical', individual records will be displayed vertically in key:value pairs. - * - * @example - * ``` - * ───────────── - * Name Alice - * Id 36329 - * Age 20 - * ───────────── - * Name Bob - * Id 49032 - * Age 21 - * ───────────── - * ``` - */ - orientation?: 'horizontal' | 'vertical' - /** - * Vertical alignment of cell content. Defaults to 'top'. Only applies to horizontal orientation. + * Vertical alignment of cell content. Defaults to 'top'. */ verticalAlignment?: VerticalAlignment /**