@markii/ansi 0.0.1 → 0.16.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.
Files changed (60) hide show
  1. package/dist/ansi.d.ts +26 -0
  2. package/dist/ansi.js +51 -0
  3. package/dist/components/badge.d.ts +1 -1
  4. package/dist/components/badge.js +5 -4
  5. package/dist/components/callout.d.ts +24 -13
  6. package/dist/components/callout.js +45 -21
  7. package/dist/components/card.d.ts +24 -2
  8. package/dist/components/card.js +41 -12
  9. package/dist/components/cell.d.ts +5 -11
  10. package/dist/components/cell.js +11 -15
  11. package/dist/components/chart.d.ts +4 -10
  12. package/dist/components/chart.js +9 -16
  13. package/dist/components/details-string.d.ts +26 -0
  14. package/dist/components/details-string.js +39 -0
  15. package/dist/components/details.d.ts +30 -10
  16. package/dist/components/details.js +27 -25
  17. package/dist/components/divider.js +9 -11
  18. package/dist/components/figure.d.ts +5 -16
  19. package/dist/components/figure.js +9 -19
  20. package/dist/components/index.d.ts +14 -10
  21. package/dist/components/index.js +19 -16
  22. package/dist/components/kbd.d.ts +1 -1
  23. package/dist/components/kbd.js +3 -2
  24. package/dist/components/layout-wrapper.d.ts +7 -22
  25. package/dist/components/layout-wrapper.js +9 -23
  26. package/dist/components/progress.d.ts +3 -11
  27. package/dist/components/progress.js +6 -14
  28. package/dist/components/rating.d.ts +1 -2
  29. package/dist/components/rating.js +2 -3
  30. package/dist/components/row.d.ts +33 -20
  31. package/dist/components/row.js +33 -51
  32. package/dist/components/stat.d.ts +3 -8
  33. package/dist/components/stat.js +4 -9
  34. package/dist/components/tab.d.ts +9 -8
  35. package/dist/components/tab.js +12 -10
  36. package/dist/components/table-grid.js +1 -2
  37. package/dist/components/table.d.ts +5 -10
  38. package/dist/components/table.js +7 -12
  39. package/dist/components/tabs-string.d.ts +17 -0
  40. package/dist/components/tabs-string.js +30 -0
  41. package/dist/components/tabs.d.ts +29 -14
  42. package/dist/components/tabs.js +37 -30
  43. package/dist/index.d.ts +2 -4
  44. package/dist/index.js +6 -7
  45. package/dist/ink-string.d.ts +12 -0
  46. package/dist/ink-string.js +41 -0
  47. package/dist/interactive.d.ts +41 -0
  48. package/dist/interactive.js +64 -0
  49. package/dist/layout.js +1 -2
  50. package/dist/registry.d.ts +91 -100
  51. package/dist/registry.js +17 -9
  52. package/dist/render.d.ts +39 -24
  53. package/dist/render.js +676 -373
  54. package/dist/text-grid.d.ts +54 -0
  55. package/dist/text-grid.js +249 -0
  56. package/package.json +7 -4
  57. package/dist/box.d.ts +0 -47
  58. package/dist/box.js +0 -209
  59. package/dist/measure.d.ts +0 -25
  60. package/dist/measure.js +0 -108
@@ -1,12 +1,32 @@
1
- import type { AnsiComponent } from '../registry.js';
1
+ import type { ReactNode } from 'react';
2
+ import type { ColorLevel } from '../ansi.js';
2
3
  /**
3
- * `:::details{title="..." open} ... :::` — a collapsible disclosure. A
4
- * terminal has no collapse/expand affordance (see `render.ts`'s top comment
5
- * on interactive components), so this ALWAYS shows the body: the bare
6
- * `open` attribute instead only changes the marker glyph (`▾` for `open`,
7
- * `▸` for folded-by-default), a quiet hint at the note's own authored
8
- * default rather than a control that does anything here. The marker line
9
- * also names the section a "collapsible section" so a reader understands
10
- * why they are seeing an indented block with no directive name attached.
4
+ * `:::details{title="..." open} ... :::` — a collapsible disclosure, built
5
+ * as a real Ink element (`render.tsx`'s `renderDetailsElement` calls this
6
+ * directly, the same way it calls `InteractiveTabs` for `tabs` — see
7
+ * `registry.ts`'s and `render.tsx`'s doc comments).
8
+ *
9
+ * Non-interactive (the default, and the render-once path): ALWAYS shows the
10
+ * body, exactly like the pre-Ink engine — a static render has no
11
+ * expand/collapse affordance. The bare `open` attribute only changes the
12
+ * marker glyph (`▾` for `open`, `▸` for folded-by-default), a quiet hint at
13
+ * the note's own authored default.
14
+ *
15
+ * Interactive: starts CLOSED unless `open` was given; Enter toggles it when
16
+ * this `details` instance is focused (`useIsFocused`). When focused, the
17
+ * summary line also carries a dim hint (`enter to open`/`enter to close`,
18
+ * matching the current state) and the whole line is drawn in the accent
19
+ * color with a leading `› ` glyph (`useFocusMarker`), so focus is visible
20
+ * even on a theme with no perceptible accent color; unfocused, the line
21
+ * gets two leading spaces instead, so nothing shifts horizontally when
22
+ * focus moves onto or off of this block.
11
23
  */
12
- export declare const Details: AnsiComponent;
24
+ export interface InteractiveDetailsProps {
25
+ title: string;
26
+ body: ReactNode;
27
+ defaultOpen: boolean;
28
+ color: ColorLevel;
29
+ interactive: boolean;
30
+ focusId: number | undefined;
31
+ }
32
+ export declare function InteractiveDetails({ title, body, defaultOpen, color, interactive, focusId, }: InteractiveDetailsProps): ReactNode;
@@ -1,25 +1,27 @@
1
- import { indentBlock } from '../box.js';
2
- const DEFAULT_TITLE = 'Details';
3
- /** Columns the indent prefix (" ") consumes from the enclosing width. */
4
- const INDENT_WIDTH = 2;
5
- /**
6
- * `:::details{title="..." open} ... :::` — a collapsible disclosure. A
7
- * terminal has no collapse/expand affordance (see `render.ts`'s top comment
8
- * on interactive components), so this ALWAYS shows the body: the bare
9
- * `open` attribute instead only changes the marker glyph (`▾` for `open`,
10
- * `▸` for folded-by-default), a quiet hint at the note's own authored
11
- * default rather than a control that does anything here. The marker line
12
- * also names the section a "collapsible section" so a reader understands
13
- * why they are seeing an indented block with no directive name attached.
14
- */
15
- export const Details = (attributes, children, ctx) => {
16
- const title = attributes.title ?? DEFAULT_TITLE;
17
- const open = Object.hasOwn(attributes, 'open');
18
- const glyph = open ? '▾' : '▸';
19
- const marker = ctx.dim(`${glyph} ${ctx.bold(ctx.text(title))} (collapsible section, shown expanded)`);
20
- const innerWidth = Math.max(1, ctx.width - INDENT_WIDTH);
21
- const childrenText = children({ width: innerWidth });
22
- if (!childrenText)
23
- return marker;
24
- return `${marker}\n${indentBlock(childrenText, ' ')}`;
25
- };
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { bold, dim } from '../ansi.js';
5
+ import { useFocusMarker, useIsFocused } from '../interactive.js';
6
+ export function InteractiveDetails({ title, body, defaultOpen, color, interactive, focusId, }) {
7
+ const [open, setOpen] = useState(defaultOpen);
8
+ const focused = useIsFocused(focusId);
9
+ useInput((_input, key) => {
10
+ if (key.return)
11
+ setOpen((current) => !current);
12
+ }, { isActive: interactive && focused });
13
+ // Every hook this component calls runs BEFORE the non-interactive early
14
+ // return below, so the hook order is identical in both modes. The summary
15
+ // line is cheap to build and unused in the non-interactive branch; that
16
+ // waste is the price of never making a hook call conditional.
17
+ const openGlyph = open ? '▾' : '▸';
18
+ const hint = focused ? (open ? ' enter to close' : ' enter to open') : '';
19
+ const summaryLine = dim(`${openGlyph} ${bold(title, color)}${hint}`, color);
20
+ const focusGlyph = focused ? '› ' : ' ';
21
+ const headingLine = useFocusMarker(`${focusGlyph}${summaryLine}`, focusId);
22
+ if (!interactive) {
23
+ const glyph = defaultOpen ? '▾' : '▸';
24
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: dim(`${glyph} ${bold(title, color)} (collapsible section, shown expanded)`, color) }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: body })] }));
25
+ }
26
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: headingLine }), open && (_jsx(Box, { marginLeft: 2, flexDirection: "column", children: body }))] }));
27
+ }
@@ -1,4 +1,4 @@
1
- import { measure } from '../measure.js';
1
+ import { measureWidth, padText, ruleLine } from '../text-grid.js';
2
2
  import { selfLayoutAlign, selfLayoutWidth } from '../layout.js';
3
3
  const DIVIDER_VARIANTS = [
4
4
  'line',
@@ -20,7 +20,7 @@ const SHORT_SIDE_RULE = 2;
20
20
  /** Builds a full-`width` rule with `label` woven in at `align`, e.g. `── Part 2 ─────────`. */
21
21
  function labeledRule(ruleChar, label, width, align) {
22
22
  const labelText = ` ${label} `;
23
- const remaining = Math.max(0, width - measure(labelText));
23
+ const remaining = Math.max(0, width - measureWidth(labelText));
24
24
  let left;
25
25
  if (align === 'left')
26
26
  left = Math.min(SHORT_SIDE_RULE, remaining);
@@ -42,15 +42,13 @@ function labeledRule(ruleChar, label, width, align) {
42
42
  * within the width. Dimmed, matching the plain thematic-break rendering.
43
43
  *
44
44
  * Registered `selfLayout` (`registry.ts`'s `AnsiRegistryEntry.selfLayout`):
45
- * a full-width rule is ONE long run with no spaces in it, so a generic
45
+ * a full-width rule is ONE long run with no spaces in it, so the generic
46
46
  * post-render `applyLayout` narrowing would treat the whole rule as a
47
- * single over-long "word" and hard-break it into several shorter lines
48
- * (`../box.ts`'s `wrap`'s `breakLongWord`) instead of drawing one shorter
49
- * rule — this component instead reads `ctx.layout` and draws its own rule
50
- * at the resolved width from the start, exactly like `card`/`callout`.
47
+ * single over-long "word" and hard-break it — this component instead reads
48
+ * `ctx.layout` and draws its own rule at the resolved width from the start.
51
49
  */
52
50
  const FIT_DEFAULT_WIDTH = 10;
53
- export const Divider = (attributes, _children, ctx) => {
51
+ export const Divider = ({ attributes, ctx }) => {
54
52
  const rawVariant = attributes.variant ?? 'line';
55
53
  const variant = isDividerVariant(rawVariant)
56
54
  ? rawVariant
@@ -61,20 +59,20 @@ export const Divider = (attributes, _children, ctx) => {
61
59
  const labelAlign = rawLabelAlign && isDividerLabelAlign(rawLabelAlign)
62
60
  ? rawLabelAlign
63
61
  : 'center';
64
- const naturalWidth = label ? measure(label) + 4 : FIT_DEFAULT_WIDTH;
62
+ const naturalWidth = label ? measureWidth(label) + 4 : FIT_DEFAULT_WIDTH;
65
63
  const width = selfLayoutWidth(ctx.layout, ctx.width, naturalWidth);
66
64
  let line;
67
65
  if (variant === 'ornament') {
68
66
  const inner = label
69
67
  ? `${ORNAMENT_GLYPH} ${label} ${ORNAMENT_GLYPH}`
70
68
  : ORNAMENT_GLYPH;
71
- line = ctx.pad(inner, width, labelAlign);
69
+ line = padText(inner, width, labelAlign);
72
70
  }
73
71
  else {
74
72
  const ruleChar = RULE_CHAR[variant];
75
73
  line = label
76
74
  ? labeledRule(ruleChar, label, width, labelAlign)
77
- : ctx.rule(width, ruleChar);
75
+ : ruleLine(width, ruleChar);
78
76
  }
79
77
  return selfLayoutAlign(ctx.dim(line), ctx.layout, ctx.width);
80
78
  };
@@ -1,20 +1,9 @@
1
- import type { AnsiComponent } from '../registry.js';
1
+ import { type AnsiComponent } from '../registry.js';
2
2
  /**
3
3
  * `:::figure{src="..." alt="..."} caption markdown :::` — an image with a
4
- * rich (markdown) caption. `src` is required; a missing `src` shows only the
5
- * caption, matching the graceful-degradation spirit of the unknown-directive
6
- * fallback rather than throwing.
7
- *
8
- * Security: `src` bypasses `@markii/core`'s hast-level URL sanitizer (it is
9
- * a directive attribute, not a markdown image), so this component closes
10
- * that gap itself with `@markii/core`'s `isSafeUrl` — the same allowlist the
11
- * sanitizer uses — and drops the image line entirely on a refusal, replacing
12
- * it with a short labeled marker (a terminal has no tooltip to carry the
13
- * full sentence out of the text flow) plus the full sentence reported to
14
- * `onDiagnostic`; matches `@markii/html`'s `Figure` in behavior, not in the
15
- * inline wording.
16
- *
17
- * Terminal form: alt text, then the (possibly host-resolved) src, then the
18
- * caption, each on its own line, dimmed except the caption itself.
4
+ * rich (markdown) caption. `src` is required; a missing `src` shows only
5
+ * the caption. Security: `src` bypasses `@markii/core`'s hast-level URL
6
+ * sanitizer (it is a directive attribute, not a markdown image), so this
7
+ * component closes that gap itself with `isSafeUrl`.
19
8
  */
20
9
  export declare const Figure: AnsiComponent;
@@ -2,27 +2,17 @@ import { isSafeUrl } from '@markii/core';
2
2
  import { reportDiagnostic } from '@markii/stdlib';
3
3
  import { resolveImageAttribute } from '../image-resolve.js';
4
4
  import { unsafeImageSrcLabel, unsafeImageSrcTitle, } from '../failure-presentation.js';
5
+ import { childrenText } from '../registry.js';
5
6
  const DEFAULT_ALT = '';
6
7
  const DIRECTIVE_NAME = 'figure';
7
8
  /**
8
9
  * `:::figure{src="..." alt="..."} caption markdown :::` — an image with a
9
- * rich (markdown) caption. `src` is required; a missing `src` shows only the
10
- * caption, matching the graceful-degradation spirit of the unknown-directive
11
- * fallback rather than throwing.
12
- *
13
- * Security: `src` bypasses `@markii/core`'s hast-level URL sanitizer (it is
14
- * a directive attribute, not a markdown image), so this component closes
15
- * that gap itself with `@markii/core`'s `isSafeUrl` — the same allowlist the
16
- * sanitizer uses — and drops the image line entirely on a refusal, replacing
17
- * it with a short labeled marker (a terminal has no tooltip to carry the
18
- * full sentence out of the text flow) plus the full sentence reported to
19
- * `onDiagnostic`; matches `@markii/html`'s `Figure` in behavior, not in the
20
- * inline wording.
21
- *
22
- * Terminal form: alt text, then the (possibly host-resolved) src, then the
23
- * caption, each on its own line, dimmed except the caption itself.
10
+ * rich (markdown) caption. `src` is required; a missing `src` shows only
11
+ * the caption. Security: `src` bypasses `@markii/core`'s hast-level URL
12
+ * sanitizer (it is a directive attribute, not a markdown image), so this
13
+ * component closes that gap itself with `isSafeUrl`.
24
14
  */
25
- export const Figure = (attributes, children, ctx) => {
15
+ export const Figure = ({ attributes, children, ctx }) => {
26
16
  const rawSrc = attributes.src ?? null;
27
17
  const alt = attributes.alt ?? DEFAULT_ALT;
28
18
  const refused = Boolean(rawSrc) && !isSafeUrl(rawSrc);
@@ -46,8 +36,8 @@ export const Figure = (attributes, children, ctx) => {
46
36
  lines.push(ctx.dim(src));
47
37
  }
48
38
  }
49
- const childrenText = children();
50
- if (childrenText)
51
- lines.push(childrenText);
39
+ const body = childrenText(children);
40
+ if (body)
41
+ lines.push(body);
52
42
  return lines.join('\n');
53
43
  };
@@ -6,7 +6,8 @@ export type { CalloutType } from './callout.js';
6
6
  export { Card } from './card.js';
7
7
  export { Cell } from './cell.js';
8
8
  export { Chart } from './chart.js';
9
- export { Details } from './details.js';
9
+ export { Details } from './details-string.js';
10
+ export { InteractiveDetails } from './details.js';
10
11
  export { Divider } from './divider.js';
11
12
  export type { DividerVariant } from './divider.js';
12
13
  export { Figure } from './figure.js';
@@ -15,20 +16,23 @@ export { createLayoutWrapper, layoutWrapperPresetAxis, LAYOUT_WRAPPER_PRESETS, }
15
16
  export type { LayoutWrapperPreset } from './layout-wrapper.js';
16
17
  export { Progress } from './progress.js';
17
18
  export { Rating } from './rating.js';
18
- export { Row, ROW_COLUMN_THRESHOLD } from './row.js';
19
+ export { Row, ROW_COLUMN_THRESHOLD, resolveRowLayout } from './row.js';
19
20
  export { Stat } from './stat.js';
20
21
  export { Tab, DEFAULT_TAB_LABEL } from './tab.js';
21
- export { Tabs } from './tabs.js';
22
+ export { Tabs } from './tabs-string.js';
23
+ export { InteractiveTabs } from './tabs.js';
22
24
  export { Table } from './table.js';
23
25
  export { drawTableGrid, measureTableGridWidth, MIN_COLUMN_WIDTH, } from './table-grid.js';
24
26
  /**
25
- * The built-in standard components, pre-registered under their names —
26
- * matching `@markii/html`'s `defaultHtmlRegistry` and `@markii/react`'s
27
- * `defaultRegistry` in shape and coverage: the same 23 names. `card`,
28
- * `callout`, `divider`, and `table` are marked `selfLayout` (`registry.ts`'s
29
- * `AnsiRegistryEntry.selfLayout`) because they draw a single long
27
+ * The built-in standard components, pre-registered under their names: the
28
+ * same 23 names as `@markii/html`'s `defaultHtmlRegistry` and
29
+ * `@markii/react`'s `defaultRegistry`. `card`, `callout`, `divider`, and
30
+ * `table` are marked `selfLayout` because they draw a single long
30
31
  * frame/bar/rule/grid a generic post-render `applyLayout` narrow/pad would
31
- * corrupt or hard-break mid-glyph; see `card.ts`'s and `divider.ts`'s doc
32
- * comments.
32
+ * corrupt or hard-break mid-glyph. `row`, `tabs`, and `details` are the
33
+ * three directives `render.tsx` gives real Ink layout/state to directly
34
+ * (see `registry.ts`'s and `render.tsx`'s doc comments); their REGISTERED
35
+ * `component` here is the STRING-mode fallback, reached only when one of
36
+ * them is nested inside a self-drawing container's own body.
33
37
  */
34
38
  export declare const defaultAnsiRegistry: AnsiRegistry;
@@ -5,7 +5,7 @@ import { Callout } from './callout.js';
5
5
  import { Card } from './card.js';
6
6
  import { Cell } from './cell.js';
7
7
  import { Chart } from './chart.js';
8
- import { Details } from './details.js';
8
+ import { Details } from './details-string.js';
9
9
  import { Divider } from './divider.js';
10
10
  import { Figure } from './figure.js';
11
11
  import { Kbd } from './kbd.js';
@@ -15,31 +15,33 @@ import { Rating } from './rating.js';
15
15
  import { Row } from './row.js';
16
16
  import { Stat } from './stat.js';
17
17
  import { Tab } from './tab.js';
18
- import { Tabs } from './tabs.js';
18
+ import { Tabs } from './tabs-string.js';
19
19
  import { Table } from './table.js';
20
20
  export { Badge } from './badge.js';
21
21
  export { Callout } from './callout.js';
22
22
  export { Card } from './card.js';
23
23
  export { Cell } from './cell.js';
24
24
  export { Chart } from './chart.js';
25
- export { Details } from './details.js';
25
+ export { Details } from './details-string.js';
26
+ export { InteractiveDetails } from './details.js';
26
27
  export { Divider } from './divider.js';
27
28
  export { Figure } from './figure.js';
28
29
  export { Kbd } from './kbd.js';
29
30
  export { createLayoutWrapper, layoutWrapperPresetAxis, LAYOUT_WRAPPER_PRESETS, } from './layout-wrapper.js';
30
31
  export { Progress } from './progress.js';
31
32
  export { Rating } from './rating.js';
32
- export { Row, ROW_COLUMN_THRESHOLD } from './row.js';
33
+ export { Row, ROW_COLUMN_THRESHOLD, resolveRowLayout } from './row.js';
33
34
  export { Stat } from './stat.js';
34
35
  export { Tab, DEFAULT_TAB_LABEL } from './tab.js';
35
- export { Tabs } from './tabs.js';
36
+ export { Tabs } from './tabs-string.js';
37
+ export { InteractiveTabs } from './tabs.js';
36
38
  export { Table } from './table.js';
37
39
  export { drawTableGrid, measureTableGridWidth, MIN_COLUMN_WIDTH, } from './table-grid.js';
38
40
  /**
39
41
  * Derives a registry entry's `inline` flag from `@markii/stdlib`'s standard
40
- * component contract for `name`, matching `@markii/html`'s
41
- * `inlineFromContract`: `kind: 'inline'` -> `inline: true`, otherwise
42
- * `false`. Falls back to `false` if `name` has no standard contract.
42
+ * component contract for `name`: `kind: 'inline'` -> `inline: true`,
43
+ * otherwise `false`. Falls back to `false` if `name` has no standard
44
+ * contract.
43
45
  */
44
46
  function inlineFromContract(name) {
45
47
  return getContract(name)?.kind === 'inline';
@@ -47,7 +49,6 @@ function inlineFromContract(name) {
47
49
  /**
48
50
  * One layout-wrapper registration: the shared wrapper component bound to
49
51
  * `preset`, plus the `layout` axis that preset sets by its own name.
50
- * Matches `@markii/html`'s `layoutWrapperEntry`.
51
52
  */
52
53
  function layoutWrapperEntry(preset) {
53
54
  return {
@@ -57,14 +58,16 @@ function layoutWrapperEntry(preset) {
57
58
  };
58
59
  }
59
60
  /**
60
- * The built-in standard components, pre-registered under their names —
61
- * matching `@markii/html`'s `defaultHtmlRegistry` and `@markii/react`'s
62
- * `defaultRegistry` in shape and coverage: the same 23 names. `card`,
63
- * `callout`, `divider`, and `table` are marked `selfLayout` (`registry.ts`'s
64
- * `AnsiRegistryEntry.selfLayout`) because they draw a single long
61
+ * The built-in standard components, pre-registered under their names: the
62
+ * same 23 names as `@markii/html`'s `defaultHtmlRegistry` and
63
+ * `@markii/react`'s `defaultRegistry`. `card`, `callout`, `divider`, and
64
+ * `table` are marked `selfLayout` because they draw a single long
65
65
  * frame/bar/rule/grid a generic post-render `applyLayout` narrow/pad would
66
- * corrupt or hard-break mid-glyph; see `card.ts`'s and `divider.ts`'s doc
67
- * comments.
66
+ * corrupt or hard-break mid-glyph. `row`, `tabs`, and `details` are the
67
+ * three directives `render.tsx` gives real Ink layout/state to directly
68
+ * (see `registry.ts`'s and `render.tsx`'s doc comments); their REGISTERED
69
+ * `component` here is the STRING-mode fallback, reached only when one of
70
+ * them is nested inside a self-drawing container's own body.
68
71
  */
69
72
  export const defaultAnsiRegistry = createAnsiRegistry({
70
73
  callout: {
@@ -1,4 +1,4 @@
1
- import type { AnsiComponent } from '../registry.js';
1
+ import { type AnsiComponent } from '../registry.js';
2
2
  /**
3
3
  * `:kbd[Ctrl+S]` — a styled keycap for an inline text directive. Terminal
4
4
  * form: bracketed bold text, e.g. `[Ctrl+S]`. Takes no attributes; its inner
@@ -1,8 +1,9 @@
1
+ import { childrenText } from '../registry.js';
1
2
  /**
2
3
  * `:kbd[Ctrl+S]` — a styled keycap for an inline text directive. Terminal
3
4
  * form: bracketed bold text, e.g. `[Ctrl+S]`. Takes no attributes; its inner
4
5
  * content (already rendered plain text) is the key label.
5
6
  */
6
- export const Kbd = (_attributes, children, ctx) => {
7
- return `[${ctx.bold(children())}]`;
7
+ export const Kbd = ({ children, ctx }) => {
8
+ return `[${ctx.bold(childrenText(children))}]`;
8
9
  };
@@ -1,34 +1,19 @@
1
1
  import type { LayoutAxis } from '@markii/stdlib';
2
- import type { AnsiComponent } from '../registry.js';
2
+ import { type AnsiComponent } from '../registry.js';
3
3
  /**
4
4
  * The closed set of layout-wrapper container names (docs/format.md):
5
- * aliases of the one shared implementation below (`createLayoutWrapper`),
6
- * matching `@markii/html`'s `layout-wrapper.ts`. There is deliberately no
7
- * `normal` alias: the default needs no wrapper at all. Every name here is
8
- * ALSO one of `@markii/stdlib`'s own width/align preset values (`center`/
9
- * `left`/`right` are align presets, `wide`/`narrow`/`full`/`fit` are width
10
- * presets), which is what lets `createLayoutWrapper` use the preset name
11
- * itself as that axis's value with no separate lookup table.
5
+ * aliases of the one shared implementation below (`createLayoutWrapper`).
6
+ * There is deliberately no `normal` alias: the default needs no wrapper at
7
+ * all. Every name here is ALSO one of `@markii/stdlib`'s own width/align
8
+ * preset values.
12
9
  */
13
10
  export declare const LAYOUT_WRAPPER_PRESETS: readonly ["center", "left", "right", "wide", "narrow", "full", "fit"];
14
11
  export type LayoutWrapperPreset = (typeof LAYOUT_WRAPPER_PRESETS)[number];
15
12
  /**
16
13
  * Creates the registry component for one of docs/format.md's layout-wrapper
17
14
  * container names. One shared implementation, bound to `preset` at
18
- * registration time, matching `@markii/html`'s `createLayoutWrapper` in
19
- * spirit: it never reads `attributes` at all — `render.ts` already stripped
20
- * both reserved keys before this ever runs.
21
- *
22
- * A wrapper sets ONE axis by its own NAME (docs/spec.md §3): `preset` itself
23
- * IS that axis's value (`center` sets `align: 'center'`; `narrow` sets
24
- * `width: 'narrow'`), so it is applied unconditionally, whatever the author
25
- * wrote for that axis's own reserved attribute (already discarded — the
26
- * name always wins). The OTHER axis, when the author supplied it, arrives
27
- * as `ctx.layout` (`render.ts` resolved it on this wrapper's behalf, since
28
- * this wrapper is the directive's registered `layout` scope). Both are
29
- * merged into one `ResolvedLayoutPresets` and applied together via
30
- * `../layout.js`'s `applyLayout`, so `:::center{width=fit}` narrows AND
31
- * centers in one pass.
15
+ * registration time: it never reads `attributes` at all — `render.tsx`
16
+ * already stripped both reserved keys before this ever runs.
32
17
  */
33
18
  export declare function createLayoutWrapper(preset: LayoutWrapperPreset): AnsiComponent;
34
19
  /** The layout axis `preset` sets by its own name. Mirrors `@markii/html`'s `layoutWrapperPresetAxis`. */
@@ -1,14 +1,12 @@
1
1
  import { layoutWrapperAxis } from '@markii/stdlib';
2
2
  import { applyLayout } from '../layout.js';
3
+ import { childrenText } from '../registry.js';
3
4
  /**
4
5
  * The closed set of layout-wrapper container names (docs/format.md):
5
- * aliases of the one shared implementation below (`createLayoutWrapper`),
6
- * matching `@markii/html`'s `layout-wrapper.ts`. There is deliberately no
7
- * `normal` alias: the default needs no wrapper at all. Every name here is
8
- * ALSO one of `@markii/stdlib`'s own width/align preset values (`center`/
9
- * `left`/`right` are align presets, `wide`/`narrow`/`full`/`fit` are width
10
- * presets), which is what lets `createLayoutWrapper` use the preset name
11
- * itself as that axis's value with no separate lookup table.
6
+ * aliases of the one shared implementation below (`createLayoutWrapper`).
7
+ * There is deliberately no `normal` alias: the default needs no wrapper at
8
+ * all. Every name here is ALSO one of `@markii/stdlib`'s own width/align
9
+ * preset values.
12
10
  */
13
11
  export const LAYOUT_WRAPPER_PRESETS = [
14
12
  'center',
@@ -22,20 +20,8 @@ export const LAYOUT_WRAPPER_PRESETS = [
22
20
  /**
23
21
  * Creates the registry component for one of docs/format.md's layout-wrapper
24
22
  * container names. One shared implementation, bound to `preset` at
25
- * registration time, matching `@markii/html`'s `createLayoutWrapper` in
26
- * spirit: it never reads `attributes` at all — `render.ts` already stripped
27
- * both reserved keys before this ever runs.
28
- *
29
- * A wrapper sets ONE axis by its own NAME (docs/spec.md §3): `preset` itself
30
- * IS that axis's value (`center` sets `align: 'center'`; `narrow` sets
31
- * `width: 'narrow'`), so it is applied unconditionally, whatever the author
32
- * wrote for that axis's own reserved attribute (already discarded — the
33
- * name always wins). The OTHER axis, when the author supplied it, arrives
34
- * as `ctx.layout` (`render.ts` resolved it on this wrapper's behalf, since
35
- * this wrapper is the directive's registered `layout` scope). Both are
36
- * merged into one `ResolvedLayoutPresets` and applied together via
37
- * `../layout.js`'s `applyLayout`, so `:::center{width=fit}` narrows AND
38
- * centers in one pass.
23
+ * registration time: it never reads `attributes` at all — `render.tsx`
24
+ * already stripped both reserved keys before this ever runs.
39
25
  */
40
26
  export function createLayoutWrapper(preset) {
41
27
  const ownAxis = layoutWrapperAxis(preset);
@@ -46,9 +32,9 @@ export function createLayoutWrapper(preset) {
46
32
  const own = ownAxis === 'align'
47
33
  ? { align: preset }
48
34
  : { width: preset };
49
- return (_attributes, children, ctx) => {
35
+ return ({ children, ctx }) => {
50
36
  const merged = { ...own, ...ctx.layout };
51
- return applyLayout(children(), merged, ctx.width);
37
+ return applyLayout(childrenText(children), merged, ctx.width);
52
38
  };
53
39
  }
54
40
  /** The layout axis `preset` sets by its own name. Mirrors `@markii/html`'s `layoutWrapperPresetAxis`. */
@@ -1,15 +1,7 @@
1
1
  import type { AnsiComponent } from '../registry.js';
2
2
  /**
3
- * `::progress{value=3 max=5 label="tasks"}` — a meter bar. Data binding (§8)
4
- * mirrors `@markii/html`'s `Progress`: a bound number supplies `value`; a
5
- * bound object may supply `value`/`max` — explicit attributes always win.
6
- * Non-numeric/`NaN`/`Infinity` input falls back to `0` (value) or the
7
- * default `max` of `1`; the effective value is clamped to `[0, max]`.
8
- * Terminal form: `label bar percent`, the bar drawn with `█` (filled) and
9
- * `░` (empty) at up to `MAX_BAR_WIDTH` columns, narrower when the label and
10
- * percent text leave less room. `format`/`decimals`, when given, format the
11
- * `value/max` fraction for the percent readout in place of the default
12
- * rounded integer percent, matching `@markii/html` exactly. A failed/stale
13
- * binding appends the quiet failure suffix after the percent.
3
+ * `::progress{value=3 max=5 label="tasks"}` — a meter bar. Data binding
4
+ * (§8): a bound number supplies `value`; a bound object may supply
5
+ * `value`/`max` — explicit attributes always win.
14
6
  */
15
7
  export declare const Progress: AnsiComponent;
@@ -1,5 +1,5 @@
1
1
  import { formatValue } from '@markii/stdlib';
2
- import { measure } from '../measure.js';
2
+ import { measureWidth } from '../text-grid.js';
3
3
  import { safeRead } from '../resolve.js';
4
4
  import { dataStateSuffix, failureToken } from '../failure-presentation.js';
5
5
  const DEFAULT_MAX = 1;
@@ -38,19 +38,11 @@ const EMPTY_CHAR = '░';
38
38
  const MAX_BAR_WIDTH = 30;
39
39
  const MIN_BAR_WIDTH = 3;
40
40
  /**
41
- * `::progress{value=3 max=5 label="tasks"}` — a meter bar. Data binding (§8)
42
- * mirrors `@markii/html`'s `Progress`: a bound number supplies `value`; a
43
- * bound object may supply `value`/`max` — explicit attributes always win.
44
- * Non-numeric/`NaN`/`Infinity` input falls back to `0` (value) or the
45
- * default `max` of `1`; the effective value is clamped to `[0, max]`.
46
- * Terminal form: `label bar percent`, the bar drawn with `█` (filled) and
47
- * `░` (empty) at up to `MAX_BAR_WIDTH` columns, narrower when the label and
48
- * percent text leave less room. `format`/`decimals`, when given, format the
49
- * `value/max` fraction for the percent readout in place of the default
50
- * rounded integer percent, matching `@markii/html` exactly. A failed/stale
51
- * binding appends the quiet failure suffix after the percent.
41
+ * `::progress{value=3 max=5 label="tasks"}` — a meter bar. Data binding
42
+ * (§8): a bound number supplies `value`; a bound object may supply
43
+ * `value`/`max` — explicit attributes always win.
52
44
  */
53
- export const Progress = (attributes, _children, ctx) => {
45
+ export const Progress = ({ attributes, ctx }) => {
54
46
  const { data, dataStatus, dataFailureKind } = ctx;
55
47
  const bound = safeRead(() => dataStatus === 'missing' || dataStatus === 'error'
56
48
  ? {}
@@ -70,7 +62,7 @@ export const Progress = (attributes, _children, ctx) => {
70
62
  const suffix = dataStateSuffix(dataStatus, dataFailureKind);
71
63
  const token = failureToken(dataFailureKind);
72
64
  const percentPart = ` ${ctx.text(percentText)}${suffix ? (token ? ctx.style(suffix, token) : ctx.dim(suffix)) : ''}`;
73
- const overhead = measure(labelPart) + measure(percentText) + 1;
65
+ const overhead = measureWidth(labelPart) + measureWidth(percentText) + 1;
74
66
  const barWidth = Math.max(MIN_BAR_WIDTH, Math.min(MAX_BAR_WIDTH, ctx.width - overhead));
75
67
  const filledCount = Math.round((percent / 100) * barWidth);
76
68
  const bar = FILLED_CHAR.repeat(filledCount) + EMPTY_CHAR.repeat(barWidth - filledCount);
@@ -3,7 +3,6 @@ import type { AnsiComponent } from '../registry.js';
3
3
  * `::rating{value=3 max=5}` — a leaf directive rendering a row of stars.
4
4
  * Both attributes are optional and clamped to sane bounds; malformed input
5
5
  * degrades gracefully instead of throwing. Terminal form: filled (`★`) and
6
- * empty (`☆`) stars, one per position up to `max`. Matches
7
- * `@markii/html`'s `Rating` clamping rules exactly.
6
+ * empty (`☆`) stars, one per position up to `max`.
8
7
  */
9
8
  export declare const Rating: AnsiComponent;
@@ -14,10 +14,9 @@ function clamp(value, min, max) {
14
14
  * `::rating{value=3 max=5}` — a leaf directive rendering a row of stars.
15
15
  * Both attributes are optional and clamped to sane bounds; malformed input
16
16
  * degrades gracefully instead of throwing. Terminal form: filled (`★`) and
17
- * empty (`☆`) stars, one per position up to `max`. Matches
18
- * `@markii/html`'s `Rating` clamping rules exactly.
17
+ * empty (`☆`) stars, one per position up to `max`.
19
18
  */
20
- export const Rating = (attributes) => {
19
+ export const Rating = ({ attributes }) => {
21
20
  const max = clamp(parseCount(attributes.max, DEFAULT_MAX), MIN_MAX, MAX_MAX);
22
21
  const value = clamp(parseCount(attributes.value, 0), 0, max);
23
22
  let stars = '';