tinky 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,82 @@
1
+ import { type TextStyles } from "../utils/apply-text-styles.js";
2
+ /**
3
+ * Props for the Separator component.
4
+ */
5
+ export interface SeparatorProps extends TextStyles {
6
+ /**
7
+ * The character to repeat for the separator line.
8
+ *
9
+ * @defaultValue "─"
10
+ * @example
11
+ * ```tsx
12
+ * <Separator char="=" />
13
+ * ```
14
+ */
15
+ readonly char?: string;
16
+ /**
17
+ * The direction of the separator.
18
+ *
19
+ * - `"horizontal"`: The separator will expand horizontally to fill the container's width. Height will be 1 row.
20
+ * - `"vertical"`: The separator will expand vertically to fill the container's height. Width will be 1 column.
21
+ *
22
+ * @defaultValue "horizontal"
23
+ */
24
+ readonly direction?: "horizontal" | "vertical";
25
+ }
26
+ /**
27
+ * A component that renders a line of repeated characters to fill the available space.
28
+ *
29
+ * The `<Separator>` component is designed to be efficient and layout-friendly.
30
+ * Unlike manually repeating a character in a generic text string, this component
31
+ * uses the underlying layout engine (Taffy) to determine the exact number of
32
+ * characters needed to fill the container. This prevents overflow issues and
33
+ * avoids allocating unnecessarily large strings in memory.
34
+ *
35
+ * It supports both horizontal and vertical orientations and accepts all standard
36
+ * text styling properties (color, background, bold, etc.).
37
+ *
38
+ * @example
39
+ * **Basic Usage**
40
+ *
41
+ * Renders a horizontal line using the default character "─".
42
+ *
43
+ * ```tsx
44
+ * import { render, Box, Separator } from 'tinky';
45
+ *
46
+ * render(
47
+ * <Box flexDirection="column" width={20}>
48
+ * <Text>Title</Text>
49
+ * <Separator />
50
+ * <Text>Content</Text>
51
+ * </Box>
52
+ * );
53
+ * ```
54
+ *
55
+ * @example
56
+ * **Vertical Separator**
57
+ *
58
+ * Renders a vertical line in a row layout.
59
+ *
60
+ * ```tsx
61
+ * <Box height={5}>
62
+ * <Text>Left</Text>
63
+ * <Separator direction="vertical" char="│" />
64
+ * <Text>Right</Text>
65
+ * </Box>
66
+ * ```
67
+ *
68
+ * @example
69
+ * **Styling**
70
+ *
71
+ * You can apply colors and text modifiers just like with the `<Text>` component.
72
+ *
73
+ * ```tsx
74
+ * <Separator
75
+ * char="="
76
+ * color="blue"
77
+ * bold
78
+ * backgroundColor="white"
79
+ * />
80
+ * ```
81
+ */
82
+ export declare function Separator({ char, direction, color, dimColor, backgroundColor, bold, italic, underline, strikethrough, inverse, }: SeparatorProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,75 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { applyTextStyles, } from "../utils/apply-text-styles.js";
3
+ /**
4
+ * A component that renders a line of repeated characters to fill the available space.
5
+ *
6
+ * The `<Separator>` component is designed to be efficient and layout-friendly.
7
+ * Unlike manually repeating a character in a generic text string, this component
8
+ * uses the underlying layout engine (Taffy) to determine the exact number of
9
+ * characters needed to fill the container. This prevents overflow issues and
10
+ * avoids allocating unnecessarily large strings in memory.
11
+ *
12
+ * It supports both horizontal and vertical orientations and accepts all standard
13
+ * text styling properties (color, background, bold, etc.).
14
+ *
15
+ * @example
16
+ * **Basic Usage**
17
+ *
18
+ * Renders a horizontal line using the default character "─".
19
+ *
20
+ * ```tsx
21
+ * import { render, Box, Separator } from 'tinky';
22
+ *
23
+ * render(
24
+ * <Box flexDirection="column" width={20}>
25
+ * <Text>Title</Text>
26
+ * <Separator />
27
+ * <Text>Content</Text>
28
+ * </Box>
29
+ * );
30
+ * ```
31
+ *
32
+ * @example
33
+ * **Vertical Separator**
34
+ *
35
+ * Renders a vertical line in a row layout.
36
+ *
37
+ * ```tsx
38
+ * <Box height={5}>
39
+ * <Text>Left</Text>
40
+ * <Separator direction="vertical" char="│" />
41
+ * <Text>Right</Text>
42
+ * </Box>
43
+ * ```
44
+ *
45
+ * @example
46
+ * **Styling**
47
+ *
48
+ * You can apply colors and text modifiers just like with the `<Text>` component.
49
+ *
50
+ * ```tsx
51
+ * <Separator
52
+ * char="="
53
+ * color="blue"
54
+ * bold
55
+ * backgroundColor="white"
56
+ * />
57
+ * ```
58
+ */
59
+ export function Separator({ char = "─", direction = "horizontal", color, dimColor = false, backgroundColor, bold = false, italic = false, underline = false, strikethrough = false, inverse = false, }) {
60
+ const style = direction === "horizontal"
61
+ ? { flexGrow: 1, flexShrink: 0, height: 1 }
62
+ : { flexGrow: 1, flexShrink: 0, width: 1 };
63
+ return (_jsx("tinky-separator", { style: style, internal_separatorChar: char, internal_separatorDirection: direction, internal_transform: (text) => {
64
+ return applyTextStyles(text, {
65
+ dimColor,
66
+ color,
67
+ backgroundColor,
68
+ bold,
69
+ italic,
70
+ underline,
71
+ strikethrough,
72
+ inverse,
73
+ });
74
+ } }));
75
+ }
@@ -1,11 +1,10 @@
1
1
  import { type ReactNode } from "react";
2
- import { type ForegroundColorName } from "chalk";
3
- import { type LiteralUnion } from "type-fest";
2
+ import { type TextStyles } from "../utils/apply-text-styles.js";
4
3
  import { type Styles } from "../core/styles.js";
5
4
  /**
6
5
  * Props for the Text component.
7
6
  */
8
- export interface TextProps {
7
+ export interface TextProps extends TextStyles {
9
8
  /**
10
9
  * A label for the element for screen readers.
11
10
  */
@@ -14,39 +13,6 @@ export interface TextProps {
14
13
  * Hide the element from screen readers.
15
14
  */
16
15
  readonly "aria-hidden"?: boolean;
17
- /**
18
- * Change text color. Tinky uses Chalk under the hood, so all its functionality
19
- * is supported.
20
- */
21
- readonly color?: LiteralUnion<ForegroundColorName, string>;
22
- /**
23
- * Same as `color`, but for the background.
24
- */
25
- readonly backgroundColor?: LiteralUnion<ForegroundColorName, string>;
26
- /**
27
- * Dim the color (make it less bright).
28
- */
29
- readonly dimColor?: boolean;
30
- /**
31
- * Make the text bold.
32
- */
33
- readonly bold?: boolean;
34
- /**
35
- * Make the text italic.
36
- */
37
- readonly italic?: boolean;
38
- /**
39
- * Make the text underlined.
40
- */
41
- readonly underline?: boolean;
42
- /**
43
- * Make the text crossed out with a line.
44
- */
45
- readonly strikethrough?: boolean;
46
- /**
47
- * Inverse background and foreground colors.
48
- */
49
- readonly inverse?: boolean;
50
16
  /**
51
17
  * This property tells Tinky to wrap or truncate text if its width is larger
52
18
  * than the container. If `wrap` is passed (the default), Tinky will wrap text
@@ -1,7 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useContext } from "react";
3
- import chalk from "chalk";
4
- import { colorize } from "../utils/colorize.js";
3
+ import { applyTextStyles, } from "../utils/apply-text-styles.js";
5
4
  import { AccessibilityContext } from "../contexts/AccessibilityContext.js";
6
5
  import { backgroundContext } from "../contexts/BackgroundContext.js";
7
6
  /**
@@ -34,33 +33,18 @@ export function Text({ color, backgroundColor, dimColor = false, bold = false, i
34
33
  return null;
35
34
  }
36
35
  const transform = (children) => {
37
- if (dimColor) {
38
- children = chalk.dim(children);
39
- }
40
- if (color) {
41
- children = colorize(children, color, "foreground");
42
- }
43
36
  // Use explicit backgroundColor if provided, otherwise use inherited from parent Box
44
37
  const effectiveBackgroundColor = backgroundColor ?? inheritedBackgroundColor;
45
- if (effectiveBackgroundColor) {
46
- children = colorize(children, effectiveBackgroundColor, "background");
47
- }
48
- if (bold) {
49
- children = chalk.bold(children);
50
- }
51
- if (italic) {
52
- children = chalk.italic(children);
53
- }
54
- if (underline) {
55
- children = chalk.underline(children);
56
- }
57
- if (strikethrough) {
58
- children = chalk.strikethrough(children);
59
- }
60
- if (inverse) {
61
- children = chalk.inverse(children);
62
- }
63
- return children;
38
+ return applyTextStyles(children, {
39
+ color,
40
+ backgroundColor: effectiveBackgroundColor,
41
+ dimColor,
42
+ bold,
43
+ italic,
44
+ underline,
45
+ strikethrough,
46
+ inverse,
47
+ });
64
48
  };
65
49
  if (isScreenReaderEnabled && ariaHidden) {
66
50
  return null;
package/lib/core/dom.d.ts CHANGED
@@ -21,7 +21,7 @@ export type TextName = "#text";
21
21
  /**
22
22
  * Type representing element names in the Tinky DOM.
23
23
  */
24
- export type ElementNames = "tinky-root" | "tinky-box" | "tinky-text" | "tinky-virtual-text";
24
+ export type ElementNames = "tinky-root" | "tinky-box" | "tinky-text" | "tinky-virtual-text" | "tinky-separator";
25
25
  /**
26
26
  * Union type of all possible node names.
27
27
  */
@@ -143,6 +143,24 @@ export const renderNodeToOutput = (node, output, options) => {
143
143
  }
144
144
  return;
145
145
  }
146
+ if (node.nodeName === "tinky-separator") {
147
+ const separatorChar = node.attributes["internal_separatorChar"] || "─";
148
+ const direction = node.attributes["internal_separatorDirection"] ||
149
+ "horizontal";
150
+ let separatorText;
151
+ if (direction === "horizontal") {
152
+ // Fill width with the separator character
153
+ const width = Math.max(0, Math.floor(layout.width));
154
+ separatorText = separatorChar.repeat(width);
155
+ }
156
+ else {
157
+ // Fill height with the separator character (one per line)
158
+ const height = Math.max(0, Math.floor(layout.height));
159
+ separatorText = (separatorChar + "\n").repeat(height).trimEnd();
160
+ }
161
+ output.write(x, y, separatorText, { transformers: newTransformers });
162
+ return;
163
+ }
146
164
  let clipped = false;
147
165
  if (node.nodeName === "tinky-box") {
148
166
  renderBackground(x, y, node, output);
package/lib/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export { StderrContext, type StderrProps } from "./contexts/StderrContext.js";
9
9
  export { Static, type StaticProps } from "./components/Static.js";
10
10
  export { Transform, type TransformProps } from "./components/Transform.js";
11
11
  export { Newline, type NewlineProps } from "./components/Newline.js";
12
+ export { Separator, type SeparatorProps } from "./components/Separator.js";
12
13
  export { Spacer } from "./components/Spacer.js";
13
14
  export { useInput, type InputHandler, type InputOptions, type Key, } from "./hooks/use-input.js";
14
15
  export { useApp } from "./hooks/use-app.js";
@@ -20,6 +21,7 @@ export { useFocusManager, type FocusManager, } from "./hooks/use-focus-manager.j
20
21
  export { useIsScreenReaderEnabled } from "./hooks/use-is-screen-reader-enabled.js";
21
22
  export { measureElement } from "./utils/measure-element.js";
22
23
  export { type Dimension } from "./utils/dimension.js";
24
+ export { applyTextStyles, type TextStyles } from "./utils/apply-text-styles.js";
23
25
  export { type DOMElement, type DOMNode, type DOMNodeAttribute, type ElementNames, type NodeNames, type TextName, type TextNode, type TinkyNode, } from "./core/dom.js";
24
26
  export { type Styles } from "./core/styles.js";
25
27
  export { type OutputTransformer } from "./core/render-node-to-output.js";
package/lib/index.js CHANGED
@@ -8,6 +8,7 @@ export { StderrContext } from "./contexts/StderrContext.js";
8
8
  export { Static } from "./components/Static.js";
9
9
  export { Transform } from "./components/Transform.js";
10
10
  export { Newline } from "./components/Newline.js";
11
+ export { Separator } from "./components/Separator.js";
11
12
  export { Spacer } from "./components/Spacer.js";
12
13
  export { useInput, } from "./hooks/use-input.js";
13
14
  export { useApp } from "./hooks/use-app.js";
@@ -18,3 +19,4 @@ export { useFocus, } from "./hooks/use-focus.js";
18
19
  export { useFocusManager, } from "./hooks/use-focus-manager.js";
19
20
  export { useIsScreenReaderEnabled } from "./hooks/use-is-screen-reader-enabled.js";
20
21
  export { measureElement } from "./utils/measure-element.js";
22
+ export { applyTextStyles } from "./utils/apply-text-styles.js";
@@ -0,0 +1,48 @@
1
+ import { type ForegroundColorName } from "chalk";
2
+ import { type LiteralUnion } from "type-fest";
3
+ /**
4
+ * Common text styling props used by components like Text and Separator.
5
+ */
6
+ export interface TextStyles {
7
+ /**
8
+ * Change text color. Tinky uses Chalk under the hood, so all its functionality
9
+ * is supported.
10
+ */
11
+ readonly color?: LiteralUnion<ForegroundColorName, string>;
12
+ /**
13
+ * Same as `color`, but for the background.
14
+ */
15
+ readonly backgroundColor?: LiteralUnion<ForegroundColorName, string>;
16
+ /**
17
+ * Dim the color (make it less bright).
18
+ */
19
+ readonly dimColor?: boolean;
20
+ /**
21
+ * Make the text bold.
22
+ */
23
+ readonly bold?: boolean;
24
+ /**
25
+ * Make the text italic.
26
+ */
27
+ readonly italic?: boolean;
28
+ /**
29
+ * Make the text underlined.
30
+ */
31
+ readonly underline?: boolean;
32
+ /**
33
+ * Make the text crossed out with a line.
34
+ */
35
+ readonly strikethrough?: boolean;
36
+ /**
37
+ * Inverse background and foreground colors.
38
+ */
39
+ readonly inverse?: boolean;
40
+ }
41
+ /**
42
+ * Applies text styles (color, bold, etc.) to a string using Chalk.
43
+ *
44
+ * @param text - The text to apply styles to.
45
+ * @param styles - The styles to apply.
46
+ * @returns The styled text.
47
+ */
48
+ export declare const applyTextStyles: (text: string, styles: TextStyles) => string;
@@ -0,0 +1,36 @@
1
+ import chalk from "chalk";
2
+ import { colorize } from "./colorize.js";
3
+ /**
4
+ * Applies text styles (color, bold, etc.) to a string using Chalk.
5
+ *
6
+ * @param text - The text to apply styles to.
7
+ * @param styles - The styles to apply.
8
+ * @returns The styled text.
9
+ */
10
+ export const applyTextStyles = (text, styles) => {
11
+ if (styles.dimColor) {
12
+ text = chalk.dim(text);
13
+ }
14
+ if (styles.color) {
15
+ text = colorize(text, styles.color, "foreground");
16
+ }
17
+ if (styles.backgroundColor) {
18
+ text = colorize(text, styles.backgroundColor, "background");
19
+ }
20
+ if (styles.bold) {
21
+ text = chalk.bold(text);
22
+ }
23
+ if (styles.italic) {
24
+ text = chalk.italic(text);
25
+ }
26
+ if (styles.underline) {
27
+ text = chalk.underline(text);
28
+ }
29
+ if (styles.strikethrough) {
30
+ text = chalk.strikethrough(text);
31
+ }
32
+ if (styles.inverse) {
33
+ text = chalk.inverse(text);
34
+ }
35
+ return text;
36
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinky",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "React for CLIs, re-imagined with the Taffy engine",
5
5
  "keywords": [
6
6
  "react",
@@ -51,7 +51,9 @@
51
51
  "indent-string": "^5.0.0",
52
52
  "mitt": "^3.0.1",
53
53
  "react-reconciler": "^0.33.0",
54
+ "slice-ansi": "^7.1.2",
54
55
  "stack-utils": "^2.0.6",
56
+ "string-width": "^8.1.0",
55
57
  "taffy-layout": "^0.1.0",
56
58
  "tslint": "^5.20.1",
57
59
  "type-fest": "^5.4.1",