diff --git a/example.js b/example.js index 8aad4d4..c927f3e 100644 --- a/example.js +++ b/example.js @@ -40,8 +40,10 @@ console.log('\n\n' + boxen(chalk.black('unicorn'), { topRight: '+', bottomLeft: '+', bottomRight: '+', - horizontal: '-', - vertical: '|', + top: '-', + bottom: '-', + left: '|', + right: '|', }, }) + '\n'); diff --git a/index.d.ts b/index.d.ts index 31ebc50..0fabeb6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -6,21 +6,33 @@ Characters used for custom border. @example ``` -// affffb -// e e -// dffffc +// attttb +// l r +// dbbbbc const border: CustomBorderStyle = { topLeft: 'a', topRight: 'b', bottomRight: 'c', bottomLeft: 'd', - vertical: 'e', - horizontal: 'f' + left: 'l', + right: 'r', + top: 't', + bottom: 'b', }; ``` */ -export interface CustomBorderStyle extends BoxStyle {} +export interface CustomBorderStyle extends BoxStyle { + /** + @deprecated Use `top` and `bottom` instead. + */ + horizontal?: string; + + /** + @deprecated Use `left` and `right` instead. + */ + vertical?: string; +} /** Spacing used for `padding` and `margin`. diff --git a/index.js b/index.js index e23d282..4cca615 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,7 @@ import wrapAnsi from 'wrap-ansi'; const NEWLINE = '\n'; const PAD = ' '; +const BORDERS_WIDTH = 2; const terminalColumns = () => { const {env, stdout, stderr} = process; @@ -47,8 +48,10 @@ const getBorderChars = borderStyle => { 'topRight', 'bottomRight', 'bottomLeft', - 'vertical', - 'horizontal', + 'left', + 'right', + 'top', + 'bottom', ]; let characters; @@ -60,6 +63,18 @@ const getBorderChars = borderStyle => { throw new TypeError(`Invalid border style: ${borderStyle}`); } } else { + // Ensure retro-compatibility + if (borderStyle.vertical && typeof borderStyle.vertical === 'string') { + borderStyle.left = borderStyle.vertical; + borderStyle.right = borderStyle.vertical; + } + + // Ensure retro-compatibility + if (borderStyle.horizontal && typeof borderStyle.horizontal === 'string') { + borderStyle.top = borderStyle.horizontal; + borderStyle.bottom = borderStyle.horizontal; + } + for (const side of sides) { if (!borderStyle[side] || typeof borderStyle[side] !== 'string') { throw new TypeError(`Invalid border style: ${side}`); @@ -174,6 +189,38 @@ const makeContentText = (text, padding, columns, align) => { return lines.join(NEWLINE); }; +const boxContent = (content, contentWidth, options) => { + const colorizeBorder = border => { + const newBorder = options.borderColor ? getColorFn(options.borderColor)(border) : border; + return options.dimBorder ? chalk.dim(newBorder) : newBorder; + }; + + const colorizeContent = content => options.backgroundColor ? getBGColorFn(options.backgroundColor)(content) : content; + + const chars = getBorderChars(options.borderStyle); + const columns = terminalColumns(); + let marginLeft = PAD.repeat(options.margin.left); + + if (options.float === 'center') { + const marginWidth = Math.max((columns - contentWidth - BORDERS_WIDTH) / 2, 0); + marginLeft = PAD.repeat(marginWidth); + } else if (options.float === 'right') { + const marginWidth = Math.max(columns - contentWidth - options.margin.right - BORDERS_WIDTH, 0); + marginLeft = PAD.repeat(marginWidth); + } + + const top = colorizeBorder(NEWLINE.repeat(options.margin.top) + marginLeft + chars.topLeft + (options.title ? makeTitle(options.title, chars.top.repeat(contentWidth), options.titleAlignment) : chars.top.repeat(contentWidth)) + chars.topRight); + const bottom = colorizeBorder(marginLeft + chars.bottomLeft + chars.bottom.repeat(contentWidth) + chars.bottomRight + NEWLINE.repeat(options.margin.bottom)); + + const LINE_SEPARATOR = (contentWidth + BORDERS_WIDTH + options.margin.left >= columns) ? '' : NEWLINE; + + const lines = content.split(NEWLINE); + + const middle = lines.map(line => marginLeft + colorizeBorder(chars.left) + colorizeContent(line) + colorizeBorder(chars.right)).join(LINE_SEPARATOR); + + return top + LINE_SEPARATOR + middle + LINE_SEPARATOR + bottom; +}; + const isHex = color => color.match(/^#(?:[0-f]{3}){1,2}$/i); const isColorValid = color => typeof color === 'string' && ((chalk[color]) || isHex(color)); const getColorFn = color => isHex(color) ? chalk.hex(color) : chalk[color]; @@ -195,8 +242,6 @@ export default function boxen(text, options) { options.textAlignment = options.align; } - const BORDERS_WIDTH = 2; - if (options.borderColor && !isColorValid(options.borderColor)) { throw new Error(`${options.borderColor} is not a valid borderColor`); } @@ -205,71 +250,42 @@ export default function boxen(text, options) { throw new Error(`${options.backgroundColor} is not a valid backgroundColor`); } - const chars = getBorderChars(options.borderStyle); - const padding = getObject(options.padding); - const margin = getObject(options.margin); - - const colorizeBorder = border => { - const newBorder = options.borderColor ? getColorFn(options.borderColor)(border) : border; - return options.dimBorder ? chalk.dim(newBorder) : newBorder; - }; - - const colorizeContent = content => options.backgroundColor ? getBGColorFn(options.backgroundColor)(content) : content; + options.padding = getObject(options.padding); + options.margin = getObject(options.margin); const columns = terminalColumns(); - let contentWidth = widestLine(wrapAnsi(text, columns - BORDERS_WIDTH, {hard: true, trim: false})) + padding.left + padding.right; + let contentWidth = widestLine(wrapAnsi(text, columns - BORDERS_WIDTH, {hard: true, trim: false})) + options.padding.left + options.padding.right; // This prevents the title bar to exceed the console's width - let title = options.title && options.title.slice(0, columns - 4 - margin.left - margin.right); + options.title = options.title && options.title.slice(0, columns - 4 - options.margin.left - options.margin.right); - if (title) { - title = ` ${title} `; + if (options.title) { + options.title = ` ${options.title} `; // Make the box larger to fit a larger title - if (stringWidth(title) > contentWidth) { - contentWidth = stringWidth(title); + if (stringWidth(options.title) > contentWidth) { + contentWidth = stringWidth(options.title); } } - if ((margin.left && margin.right) && contentWidth + BORDERS_WIDTH + margin.left + margin.right > columns) { + if ((options.margin.left && options.margin.right) && contentWidth + BORDERS_WIDTH + options.margin.left + options.margin.right > columns) { // Let's assume we have margins: left = 3, right = 5, in total = 8 const spaceForMargins = columns - contentWidth - BORDERS_WIDTH; // Let's assume we have space = 4 - const multiplier = spaceForMargins / (margin.left + margin.right); + const multiplier = spaceForMargins / (options.margin.left + options.margin.right); // Here: multiplier = 4/8 = 0.5 - margin.left = Math.max(0, Math.floor(margin.left * multiplier)); - margin.right = Math.max(0, Math.floor(margin.right * multiplier)); + options.margin.left = Math.max(0, Math.floor(options.margin.left * multiplier)); + options.margin.right = Math.max(0, Math.floor(options.margin.right * multiplier)); // Left: 3 * 0.5 = 1.5 -> 1 // Right: 6 * 0.5 = 3 } // Prevent content from exceeding the console's width - contentWidth = Math.min(contentWidth, columns - BORDERS_WIDTH - margin.left - margin.right); - - text = makeContentText(text, padding, contentWidth, options.textAlignment); + contentWidth = Math.min(contentWidth, columns - BORDERS_WIDTH - options.margin.left - options.margin.right); - let marginLeft = PAD.repeat(margin.left); + text = makeContentText(text, options.padding, contentWidth, options.textAlignment); - if (options.float === 'center') { - const marginWidth = Math.max((columns - contentWidth - BORDERS_WIDTH) / 2, 0); - marginLeft = PAD.repeat(marginWidth); - } else if (options.float === 'right') { - const marginWidth = Math.max(columns - contentWidth - margin.right - BORDERS_WIDTH, 0); - marginLeft = PAD.repeat(marginWidth); - } - - const horizontal = chars.horizontal.repeat(contentWidth); - const top = colorizeBorder(NEWLINE.repeat(margin.top) + marginLeft + chars.topLeft + (title ? makeTitle(title, horizontal, options.titleAlignment) : horizontal) + chars.topRight); - const bottom = colorizeBorder(marginLeft + chars.bottomLeft + horizontal + chars.bottomRight + NEWLINE.repeat(margin.bottom)); - const side = colorizeBorder(chars.vertical); - - const LINE_SEPARATOR = (contentWidth + BORDERS_WIDTH + margin.left >= columns) ? '' : NEWLINE; - - const lines = text.split(NEWLINE); - - const middle = lines.map(line => marginLeft + side + colorizeContent(line) + side).join(LINE_SEPARATOR); - - return top + LINE_SEPARATOR + middle + LINE_SEPARATOR + bottom; + return boxContent(text, contentWidth, options); } export const _borderStyles = cliBoxes; diff --git a/index.test-d.ts b/index.test-d.ts index 3f821fe..43ccee4 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -6,8 +6,10 @@ const border: CustomBorderStyle = { topRight: ' ', bottomLeft: ' ', bottomRight: ' ', - horizontal: ' ', - vertical: ' ', + top: ' ', + bottom: ' ', + left: ' ', + right: ' ', }; const spacing: Spacing = { diff --git a/package.json b/package.json index d90e061..3afc6fe 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "ansi-align": "^3.0.1", "camelcase": "^6.2.0", "chalk": "^4.1.2", - "cli-boxes": "^2.2.1", + "cli-boxes": "^3.0.0", "string-width": "^5.0.1", "type-fest": "^2.3.4", "widest-line": "^4.0.0", diff --git a/readme.md b/readme.md index fbde7d7..da3d68b 100644 --- a/readme.md +++ b/readme.md @@ -111,6 +111,12 @@ Values: |foo| +---+ ``` +- `'arrow'` +``` +↘↓↓↓↙ +→foo← +↗↑↑↑↖ +``` Style of the box border. @@ -122,8 +128,10 @@ Can be any of the above predefined styles or an object with the following keys: topRight: '+', bottomLeft: '+', bottomRight: '+', - horizontal: '-', - vertical: '|' + top: '-', + bottom: '-', + left: '|', + right: '|' } ``` diff --git a/tests/border-option.js b/tests/border-option.js index e801604..4bd4b39 100644 --- a/tests/border-option.js +++ b/tests/border-option.js @@ -112,8 +112,10 @@ test('border style (custom ascii style)', t => { topRight: '2', bottomLeft: '3', bottomRight: '4', - horizontal: '-', - vertical: '|', + left: '|', + right: '!', + top: '-', + bottom: '_', }, }); diff --git a/tests/snapshots/tests/border-option.js.md b/tests/snapshots/tests/border-option.js.md index 1630d86..b2e84d7 100644 --- a/tests/snapshots/tests/border-option.js.md +++ b/tests/snapshots/tests/border-option.js.md @@ -105,5 +105,5 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `1---2␊ - |foo|␊ - 3---4` + |foo!␊ + 3___4` diff --git a/tests/snapshots/tests/border-option.js.snap b/tests/snapshots/tests/border-option.js.snap index 63387ba..067c65a 100644 Binary files a/tests/snapshots/tests/border-option.js.snap and b/tests/snapshots/tests/border-option.js.snap differ