@zakmandhro/bunti 0.1.4 → 0.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.
Files changed (203) hide show
  1. package/AGENTS.md +65 -0
  2. package/README.md +145 -34
  3. package/dist/cli.d.ts +44 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +147 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/colors.d.ts +144 -0
  8. package/dist/colors.d.ts.map +1 -0
  9. package/dist/colors.js +426 -0
  10. package/dist/colors.js.map +1 -0
  11. package/dist/components/Box.d.ts +11 -0
  12. package/dist/components/Box.d.ts.map +1 -0
  13. package/dist/components/Box.js +9 -0
  14. package/dist/components/Box.js.map +1 -0
  15. package/dist/components/Button.d.ts +24 -0
  16. package/dist/components/Button.d.ts.map +1 -0
  17. package/dist/components/Button.js +102 -0
  18. package/dist/components/Button.js.map +1 -0
  19. package/dist/components/Card.d.ts +33 -0
  20. package/dist/components/Card.d.ts.map +1 -0
  21. package/dist/components/Card.js +79 -0
  22. package/dist/components/Card.js.map +1 -0
  23. package/dist/components/Header.d.ts +23 -0
  24. package/dist/components/Header.d.ts.map +1 -0
  25. package/dist/components/Header.js +58 -0
  26. package/dist/components/Header.js.map +1 -0
  27. package/dist/components/Input.d.ts +25 -0
  28. package/dist/components/Input.d.ts.map +1 -0
  29. package/dist/components/Input.js +232 -0
  30. package/dist/components/Input.js.map +1 -0
  31. package/dist/components/Link.d.ts +21 -0
  32. package/dist/components/Link.d.ts.map +1 -0
  33. package/dist/components/Link.js +29 -0
  34. package/dist/components/Link.js.map +1 -0
  35. package/dist/components/Modal.d.ts +38 -0
  36. package/dist/components/Modal.d.ts.map +1 -0
  37. package/dist/components/Modal.js +87 -0
  38. package/dist/components/Modal.js.map +1 -0
  39. package/dist/components/Progress.d.ts +27 -0
  40. package/dist/components/Progress.d.ts.map +1 -0
  41. package/dist/components/Progress.js +39 -0
  42. package/dist/components/Progress.js.map +1 -0
  43. package/dist/components/Spinner.d.ts +19 -0
  44. package/dist/components/Spinner.d.ts.map +1 -0
  45. package/dist/components/Spinner.js +27 -0
  46. package/dist/components/Spinner.js.map +1 -0
  47. package/dist/components/index.d.ts +10 -0
  48. package/dist/components/index.d.ts.map +1 -0
  49. package/dist/components/index.js +10 -0
  50. package/dist/components/index.js.map +1 -0
  51. package/dist/data/nf-glyphs.d.ts +11 -0
  52. package/dist/data/nf-glyphs.d.ts.map +1 -0
  53. package/dist/data/nf-glyphs.js +535 -0
  54. package/dist/data/nf-glyphs.js.map +1 -0
  55. package/dist/data/nf-names.d.ts +8 -0
  56. package/dist/data/nf-names.d.ts.map +1 -0
  57. package/dist/data/nf-names.js +2 -0
  58. package/dist/data/nf-names.js.map +1 -0
  59. package/dist/demo-registry.d.ts +27 -0
  60. package/dist/demo-registry.d.ts.map +1 -0
  61. package/dist/demo-registry.js +57 -0
  62. package/dist/demo-registry.js.map +1 -0
  63. package/dist/demos/2048.ts +743 -0
  64. package/dist/demos/animation.ts +246 -0
  65. package/dist/demos/demo-layout.ts +124 -0
  66. package/dist/demos/engine.ts +374 -0
  67. package/dist/demos/interaction.ts +91 -0
  68. package/dist/demos/login.ts +203 -0
  69. package/dist/demos/mission-control.ts +558 -0
  70. package/dist/demos/showcase.ts +224 -0
  71. package/dist/detect.d.ts +104 -0
  72. package/dist/detect.d.ts.map +1 -0
  73. package/dist/detect.js +227 -0
  74. package/dist/detect.js.map +1 -0
  75. package/dist/diagnostics.d.ts +73 -0
  76. package/dist/diagnostics.d.ts.map +1 -0
  77. package/dist/diagnostics.js +225 -0
  78. package/dist/diagnostics.js.map +1 -0
  79. package/dist/dsl/context.d.ts +11 -0
  80. package/dist/dsl/context.d.ts.map +1 -0
  81. package/dist/dsl/context.js +563 -0
  82. package/dist/dsl/context.js.map +1 -0
  83. package/dist/dsl/hooks.d.ts +12 -0
  84. package/dist/dsl/hooks.d.ts.map +1 -0
  85. package/dist/dsl/hooks.js +148 -0
  86. package/dist/dsl/hooks.js.map +1 -0
  87. package/dist/dsl/interaction.d.ts +16 -0
  88. package/dist/dsl/interaction.d.ts.map +1 -0
  89. package/dist/dsl/interaction.js +101 -0
  90. package/dist/dsl/interaction.js.map +1 -0
  91. package/dist/dsl/layers.d.ts +14 -0
  92. package/dist/dsl/layers.d.ts.map +1 -0
  93. package/dist/dsl/layers.js +87 -0
  94. package/dist/dsl/layers.js.map +1 -0
  95. package/dist/dsl/motion.d.ts +10 -0
  96. package/dist/dsl/motion.d.ts.map +1 -0
  97. package/dist/dsl/motion.js +150 -0
  98. package/dist/dsl/motion.js.map +1 -0
  99. package/dist/dsl/render.d.ts +22 -0
  100. package/dist/dsl/render.d.ts.map +1 -0
  101. package/dist/dsl/render.js +69 -0
  102. package/dist/dsl/render.js.map +1 -0
  103. package/dist/dsl/types.d.ts +510 -0
  104. package/dist/dsl/types.d.ts.map +1 -0
  105. package/dist/dsl/types.js +33 -0
  106. package/dist/dsl/types.js.map +1 -0
  107. package/dist/dsl.d.ts +18 -0
  108. package/dist/dsl.d.ts.map +1 -0
  109. package/dist/dsl.js +17 -0
  110. package/dist/dsl.js.map +1 -0
  111. package/dist/easing.d.ts +45 -0
  112. package/dist/easing.d.ts.map +1 -0
  113. package/dist/easing.js +121 -0
  114. package/dist/easing.js.map +1 -0
  115. package/dist/geometry.d.ts +77 -0
  116. package/dist/geometry.d.ts.map +1 -0
  117. package/dist/geometry.js +130 -0
  118. package/dist/geometry.js.map +1 -0
  119. package/dist/icons-full.d.ts +5 -0
  120. package/dist/icons-full.d.ts.map +1 -0
  121. package/dist/icons-full.js +31 -0
  122. package/dist/icons-full.js.map +1 -0
  123. package/dist/icons.d.ts +442 -0
  124. package/dist/icons.d.ts.map +1 -0
  125. package/dist/icons.js +314 -0
  126. package/dist/icons.js.map +1 -0
  127. package/dist/index.d.ts +228 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +46 -0
  130. package/dist/index.js.map +1 -0
  131. package/dist/input.d.ts +177 -0
  132. package/dist/input.d.ts.map +1 -0
  133. package/dist/input.js +533 -0
  134. package/dist/input.js.map +1 -0
  135. package/dist/layout.d.ts +194 -0
  136. package/dist/layout.d.ts.map +1 -0
  137. package/dist/layout.js +673 -0
  138. package/dist/layout.js.map +1 -0
  139. package/dist/render.d.ts +49 -0
  140. package/dist/render.d.ts.map +1 -0
  141. package/dist/render.js +650 -0
  142. package/dist/render.js.map +1 -0
  143. package/dist/state.d.ts +226 -0
  144. package/dist/state.d.ts.map +1 -0
  145. package/dist/state.js +130 -0
  146. package/dist/state.js.map +1 -0
  147. package/dist/theme.d.ts +129 -0
  148. package/dist/theme.d.ts.map +1 -0
  149. package/dist/theme.js +167 -0
  150. package/dist/theme.js.map +1 -0
  151. package/dist/themes/catppuccin-mocha.d.ts +19 -0
  152. package/dist/themes/catppuccin-mocha.d.ts.map +1 -0
  153. package/dist/themes/catppuccin-mocha.js +36 -0
  154. package/dist/themes/catppuccin-mocha.js.map +1 -0
  155. package/dist/themes/dracula.d.ts +21 -0
  156. package/dist/themes/dracula.d.ts.map +1 -0
  157. package/dist/themes/dracula.js +37 -0
  158. package/dist/themes/dracula.js.map +1 -0
  159. package/dist/themes/github-light.d.ts +21 -0
  160. package/dist/themes/github-light.d.ts.map +1 -0
  161. package/dist/themes/github-light.js +36 -0
  162. package/dist/themes/github-light.js.map +1 -0
  163. package/dist/themes/index.d.ts +19 -0
  164. package/dist/themes/index.d.ts.map +1 -0
  165. package/dist/themes/index.js +17 -0
  166. package/dist/themes/index.js.map +1 -0
  167. package/dist/themes/nord.d.ts +23 -0
  168. package/dist/themes/nord.d.ts.map +1 -0
  169. package/dist/themes/nord.js +38 -0
  170. package/dist/themes/nord.js.map +1 -0
  171. package/dist/themes/one-dark-pro.d.ts +21 -0
  172. package/dist/themes/one-dark-pro.d.ts.map +1 -0
  173. package/dist/themes/one-dark-pro.js +36 -0
  174. package/dist/themes/one-dark-pro.js.map +1 -0
  175. package/dist/themes/tokyo-night.d.ts +20 -0
  176. package/dist/themes/tokyo-night.d.ts.map +1 -0
  177. package/dist/themes/tokyo-night.js +37 -0
  178. package/dist/themes/tokyo-night.js.map +1 -0
  179. package/dist/utils.d.ts +28 -0
  180. package/dist/utils.d.ts.map +1 -0
  181. package/dist/utils.js +158 -0
  182. package/dist/utils.js.map +1 -0
  183. package/dist/vendor/colors.d.ts +45 -0
  184. package/dist/vendor/colors.d.ts.map +1 -0
  185. package/dist/vendor/colors.js +72 -0
  186. package/dist/vendor/colors.js.map +1 -0
  187. package/llms.txt +79 -0
  188. package/package.json +52 -11
  189. package/src/colors.ts +0 -255
  190. package/src/components/Button.ts +0 -104
  191. package/src/components/Card.ts +0 -53
  192. package/src/components/Header.ts +0 -65
  193. package/src/components/Input.ts +0 -124
  194. package/src/components/index.ts +0 -4
  195. package/src/data/glyphs.ts +0 -30
  196. package/src/detect.ts +0 -60
  197. package/src/dsl.ts +0 -661
  198. package/src/icons.ts +0 -186
  199. package/src/index.ts +0 -72
  200. package/src/layout.ts +0 -640
  201. package/src/render.ts +0 -302
  202. package/src/state.ts +0 -148
  203. package/src/utils.ts +0 -165
@@ -0,0 +1,79 @@
1
+ import { adjustBrightness } from '../colors';
2
+ import { visibleWidth } from '../utils';
3
+ import { Box } from './Box';
4
+ /**
5
+ * Tactical Card Component
6
+ * A structural container with an integrated header. Colors derive from
7
+ * ctx.theme tokens:
8
+ * - default: border `theme.border`, title `theme.muted`
9
+ * - accent: border `theme.border`, title `theme.accent`
10
+ * - danger: border `theme.danger`, title `theme.danger`
11
+ *
12
+ * With an `id`, hovering lifts the border to `theme.focus` and shifts the
13
+ * background one step. Hover styling reads the hitbox registered on the
14
+ * previous frame (immediate-mode: geometry is only exact after a render),
15
+ * which is invisible at interactive frame rates.
16
+ */
17
+ export function Card(ctx, props, callback) {
18
+ const { color, theme } = ctx;
19
+ const variant = props.variant ?? props.theme ?? 'default';
20
+ let borderColor = theme.border;
21
+ let titleColor = theme.muted;
22
+ if (variant === 'accent') {
23
+ titleColor = theme.accent;
24
+ }
25
+ else if (variant === 'danger') {
26
+ borderColor = theme.danger;
27
+ titleColor = theme.danger;
28
+ }
29
+ // Hover affordance (previous-frame hover state; see docblock).
30
+ const hoverable = props.id !== undefined;
31
+ const hovered = hoverable
32
+ ? (ctx.state.hoverStates.get(props.id) ?? false)
33
+ : false;
34
+ let bgColor = props.bgColor;
35
+ let finalBorder = props.borderColor || borderColor;
36
+ if (hovered) {
37
+ finalBorder = theme.focus;
38
+ const shift = theme.mode === 'dark' ? 12 : -12;
39
+ bgColor =
40
+ bgColor !== undefined &&
41
+ !(typeof bgColor === 'object' && 'colors' in bgColor)
42
+ ? adjustBrightness(bgColor, shift)
43
+ : (bgColor ?? theme.surface);
44
+ }
45
+ const startY = ctx.cursorY;
46
+ const rendered = Box(ctx, {
47
+ x: props.x,
48
+ y: props.y,
49
+ anchor: props.anchor,
50
+ width: props.width || '100%',
51
+ height: props.height,
52
+ minHeight: props.minHeight,
53
+ border: props.border || 'frame',
54
+ borderColor: finalBorder,
55
+ padding: props.padding || [1, 2],
56
+ bgColor,
57
+ }, (sub) => {
58
+ if (props.title) {
59
+ sub.text(color.fg(titleColor, color.bold(`${props.title.toUpperCase()}\n\n`)));
60
+ }
61
+ callback(sub);
62
+ });
63
+ if (hoverable) {
64
+ const lines = rendered.split('\n');
65
+ const h = lines.length;
66
+ const w = Math.max(0, ...lines.map(visibleWidth));
67
+ // Mirror resolveBoxArea's placement defaults (centered when unplaced).
68
+ const area = ctx.resolveLocalRect({
69
+ x: props.x,
70
+ y: props.y ?? (startY || undefined),
71
+ width: w,
72
+ height: h,
73
+ anchor: props.anchor,
74
+ });
75
+ ctx.hitbox(props.id, area);
76
+ }
77
+ return rendered;
78
+ }
79
+ //# sourceMappingURL=Card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Card.js","sourceRoot":"","sources":["../../src/components/Card.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { BuntiContext } from '../dsl';
2
+ /** Props for Header. */
3
+ export interface HeaderProps {
4
+ /** Centered bar title. */
5
+ title: string;
6
+ /** Brand glyph on the left edge (use ctx.icon(name)). */
7
+ leftIcon?: string;
8
+ /** Muted telemetry/exit label on the right edge. */
9
+ rightLabel?: string;
10
+ /**
11
+ * @deprecated Colors now derive from `ctx.theme` tokens; wrap the Header
12
+ * in `ctx.themed(...)` to restyle it. This prop is ignored.
13
+ */
14
+ theme?: 'light' | 'dark';
15
+ }
16
+ /**
17
+ * Enterprise Tactical Header
18
+ * A full-width, high-signal branding bar. Colors derive from ctx.theme:
19
+ * bar `theme.surfaceRaised`, title `theme.foreground`, brand icon
20
+ * `theme.accent`, right label `theme.muted`.
21
+ */
22
+ export declare function Header(ctx: BuntiContext, props: HeaderProps): void;
23
+ //# sourceMappingURL=Header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../src/components/Header.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAI3C,wBAAwB;AACxB,MAAM,WAAW,WAAW;IAC1B,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,QA0D3D"}
@@ -0,0 +1,58 @@
1
+ import { box as engineBox } from '../layout';
2
+ import { Box } from './Box';
3
+ /**
4
+ * Enterprise Tactical Header
5
+ * A full-width, high-signal branding bar. Colors derive from ctx.theme:
6
+ * bar `theme.surfaceRaised`, title `theme.foreground`, brand icon
7
+ * `theme.accent`, right label `theme.muted`.
8
+ */
9
+ export function Header(ctx, props) {
10
+ const { color, joinHorizontal, theme } = ctx;
11
+ const bg = theme.surfaceRaised;
12
+ const fg = theme.foreground;
13
+ // Master Background Box
14
+ Box(ctx, {
15
+ anchor: 'top',
16
+ width: '100%',
17
+ height: 1,
18
+ bgColor: bg,
19
+ border: 'none',
20
+ padding: [0, 2],
21
+ }, ({ text, split }) => {
22
+ const [leftArea, midArea, rightArea] = split({
23
+ direction: 'horizontal',
24
+ constraints: [12, '1fr', 12],
25
+ });
26
+ // 1. Left: Branding
27
+ const leftStr = props.leftIcon
28
+ ? color.fg(theme.accent, props.leftIcon)
29
+ : '';
30
+ const leftNode = engineBox(leftStr, {
31
+ width: leftArea?.width ?? 0,
32
+ align: 'left',
33
+ border: 'none',
34
+ padding: [0, 0],
35
+ });
36
+ // 2. Center: Title
37
+ const midStr = color.fg(fg, color.bold(props.title));
38
+ const midNode = engineBox(midStr, {
39
+ width: midArea?.width ?? 0,
40
+ align: 'center',
41
+ border: 'none',
42
+ padding: [0, 0],
43
+ });
44
+ // 3. Right: Telemetry / Exit
45
+ const rightStr = props.rightLabel
46
+ ? color.fg(theme.muted, props.rightLabel)
47
+ : '';
48
+ const rightNode = engineBox(rightStr, {
49
+ width: rightArea?.width ?? 0,
50
+ align: 'right',
51
+ border: 'none',
52
+ padding: [0, 0],
53
+ });
54
+ // Join and push to render pipeline
55
+ text(joinHorizontal(leftNode, midNode, rightNode));
56
+ });
57
+ }
58
+ //# sourceMappingURL=Header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Header.js","sourceRoot":"","sources":["../../src/components/Header.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,IAAI,SAAS,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAiB5B;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,GAAiB,EAAE,KAAkB;IAC1D,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC;IAE7C,MAAM,EAAE,GAAG,KAAK,CAAC,aAAa,CAAC;IAC/B,MAAM,EAAE,GAAG,KAAK,CAAC,UAAU,CAAC;IAE5B,wBAAwB;IACxB,GAAG,CACD,GAAG,EACH;QACE,MAAM,EAAE,KAAK;QACb,KAAK,EAAE,MAAM;QACb,MAAM,EAAE,CAAC;QACT,OAAO,EAAE,EAAE;QACX,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;KAChB,EACD,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE;QAClB,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC;YAC3C,SAAS,EAAE,YAAY;YACvB,WAAW,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC;SAC7B,CAAC,CAAC;QAEH,oBAAoB;QACpB,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ;YAC5B,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;YACxC,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,EAAE;YAClC,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;YAC3B,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;SAChB,CAAC,CAAC;QAEH,mBAAmB;QACnB,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,EAAE;YAChC,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;YAC1B,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;SAChB,CAAC,CAAC;QAEH,6BAA6B;QAC7B,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU;YAC/B,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC;YACzC,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,EAAE;YACpC,KAAK,EAAE,SAAS,EAAE,KAAK,IAAI,CAAC;YAC5B,KAAK,EAAE,OAAO;YACd,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;SAChB,CAAC,CAAC;QAEH,mCAAmC;QACnC,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;IACrD,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { BuntiContext } from '../dsl';
2
+ import type { StyleOptions } from '../layout';
3
+ /** Props for Input. */
4
+ export interface InputProps extends StyleOptions {
5
+ /** Stable id: keys the value state, focus slot, and hitbox. */
6
+ id: string;
7
+ /** Label rendered above the field. */
8
+ label?: string;
9
+ /** Ghost text shown while the field is empty. */
10
+ placeholder?: string;
11
+ /** Initial value (state lives in useState(id) afterwards). */
12
+ value?: string;
13
+ /** 'password' masks every glyph as '*'. */
14
+ type?: 'text' | 'password';
15
+ /** Fires with the new value after every edit. */
16
+ onChange?: (value: string) => void;
17
+ }
18
+ /**
19
+ * Tactical Input Component
20
+ * Managed state HOC with a grapheme-aware editing cursor (insert/delete at
21
+ * cursor, arrows/home/end, ctrl+a/ctrl+e), an inverse-video cursor cell, and
22
+ * a width-aware horizontal scroll window.
23
+ */
24
+ export declare function Input(ctx: BuntiContext, props: InputProps): string;
25
+ //# sourceMappingURL=Input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Input.d.ts","sourceRoot":"","sources":["../../src/components/Input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAG9C,uBAAuB;AACvB,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC9C,+DAA+D;IAC/D,EAAE,EAAE,MAAM,CAAC;IACX,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAyJD;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,UAyHzD"}
@@ -0,0 +1,232 @@
1
+ import { charWidth, indentBlock } from '../utils';
2
+ /** Key names that must never insert as text. */
3
+ const NON_TEXT_KEYS = new Set([
4
+ 'up',
5
+ 'down',
6
+ 'left',
7
+ 'right',
8
+ 'enter',
9
+ 'escape',
10
+ 'tab',
11
+ 'backspace',
12
+ 'home',
13
+ 'end',
14
+ 'delete',
15
+ 'pageup',
16
+ 'pagedown',
17
+ 'insert',
18
+ 'click',
19
+ 'wheel_up',
20
+ 'wheel_down',
21
+ ]);
22
+ const FN_KEY_RE = /^f\d{1,2}$/;
23
+ function graphemes(text) {
24
+ if (text.length === 0)
25
+ return [];
26
+ const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
27
+ return Array.from(segmenter.segment(text), ({ segment }) => segment);
28
+ }
29
+ function isTextKey(key) {
30
+ return key.length > 0 && !NON_TEXT_KEYS.has(key) && !FN_KEY_RE.test(key);
31
+ }
32
+ /**
33
+ * Applies one frame of KeyEvents to the (value, cursor) pair. Grapheme-aware:
34
+ * cursor is a grapheme index, insert/delete operate on whole graphemes.
35
+ * Ctrl/alt combos never insert; ctrl+a/ctrl+e map to home/end (readline).
36
+ */
37
+ function applyKeys(keys, value, cursor) {
38
+ let glyphs = graphemes(value);
39
+ let pos = Math.max(0, Math.min(cursor, glyphs.length));
40
+ let changed = false;
41
+ for (const event of keys) {
42
+ if (event.kind === 'release')
43
+ continue;
44
+ if (event.ctrl || event.alt) {
45
+ // Readline muscle memory; every other modified combo is ignored so
46
+ // control bytes can never leak into the value.
47
+ if (event.ctrl && !event.alt && event.key === 'a')
48
+ pos = 0;
49
+ if (event.ctrl && !event.alt && event.key === 'e')
50
+ pos = glyphs.length;
51
+ continue;
52
+ }
53
+ switch (event.key) {
54
+ case 'left':
55
+ pos = Math.max(0, pos - 1);
56
+ break;
57
+ case 'right':
58
+ pos = Math.min(glyphs.length, pos + 1);
59
+ break;
60
+ case 'home':
61
+ pos = 0;
62
+ break;
63
+ case 'end':
64
+ pos = glyphs.length;
65
+ break;
66
+ case 'backspace':
67
+ if (pos > 0) {
68
+ glyphs = [...glyphs.slice(0, pos - 1), ...glyphs.slice(pos)];
69
+ pos--;
70
+ changed = true;
71
+ }
72
+ break;
73
+ case 'delete':
74
+ if (pos < glyphs.length) {
75
+ glyphs = [...glyphs.slice(0, pos), ...glyphs.slice(pos + 1)];
76
+ changed = true;
77
+ }
78
+ break;
79
+ default:
80
+ if (isTextKey(event.key)) {
81
+ glyphs = [...glyphs.slice(0, pos), event.key, ...glyphs.slice(pos)];
82
+ pos++;
83
+ changed = true;
84
+ }
85
+ }
86
+ }
87
+ return { value: glyphs.join(''), cursor: pos, changed };
88
+ }
89
+ /**
90
+ * Computes the horizontal scroll window that keeps the cursor visible in
91
+ * `innerW` columns. Width-aware: wide graphemes (emoji) count their real
92
+ * column width, so the window never overflows the field.
93
+ */
94
+ function cursorWindow(glyphs, cursor, scrollX, innerW) {
95
+ const widths = glyphs.map((g) => charWidth(g));
96
+ const cursorGlyph = cursor < glyphs.length ? glyphs[cursor] : ' ';
97
+ const cursorW = cursor < glyphs.length ? widths[cursor] : 1;
98
+ let scroll = Math.max(0, Math.min(scrollX, glyphs.length));
99
+ if (cursor < scroll)
100
+ scroll = cursor;
101
+ const spanWidth = (from) => {
102
+ let w = 0;
103
+ for (let i = from; i < cursor; i++)
104
+ w += widths[i];
105
+ return w + cursorW;
106
+ };
107
+ // Scroll right until the cursor cell fits (tail view when cursor at end).
108
+ while (scroll < cursor && spanWidth(scroll) > innerW)
109
+ scroll++;
110
+ const before = glyphs.slice(scroll, cursor).join('');
111
+ let used = spanWidth(scroll);
112
+ let after = '';
113
+ for (let i = cursor + 1; i < glyphs.length; i++) {
114
+ if (used + widths[i] > innerW)
115
+ break;
116
+ after += glyphs[i];
117
+ used += widths[i];
118
+ }
119
+ return { scroll, before, cursorGlyph, after };
120
+ }
121
+ /**
122
+ * Tactical Input Component
123
+ * Managed state HOC with a grapheme-aware editing cursor (insert/delete at
124
+ * cursor, arrows/home/end, ctrl+a/ctrl+e), an inverse-video cursor cell, and
125
+ * a width-aware horizontal scroll window.
126
+ */
127
+ export function Input(ctx, props) {
128
+ const { box, color, focusable, state, useState, hitbox, resolveLocalRect } = ctx;
129
+ // 1. Mouse Hit-Testing
130
+ const contentWidth = 40;
131
+ const contentHeight = 3;
132
+ const labelOffset = props.label ? 1 : 0;
133
+ const area = resolveLocalRect({
134
+ y: ctx.cursorY + labelOffset,
135
+ width: props.width ?? contentWidth,
136
+ height: props.height ?? contentHeight,
137
+ contentWidth,
138
+ contentHeight,
139
+ });
140
+ const interaction = hitbox(props.id, {
141
+ x: area.x,
142
+ y: area.y,
143
+ width: area.width,
144
+ height: area.height,
145
+ });
146
+ const isHovered = interaction.hovered;
147
+ // If clicked, force focus state
148
+ if (interaction.pressed) {
149
+ state.focusedId = props.id;
150
+ }
151
+ // 2. Register in the global focus loop
152
+ const isSelected = focusable(props.id);
153
+ // 3. Manage internal state: value + grapheme cursor + scroll window
154
+ const [value, setValue] = useState(props.id, props.value || '');
155
+ const [storedCursor, setCursor] = useState(`${props.id}_cursor`, graphemes(value).length);
156
+ const [scrollX, setScrollX] = useState(`${props.id}_scroll`, 0);
157
+ // The value can change externally (props/onChange round-trips): re-clamp.
158
+ let glyphs = graphemes(value);
159
+ let cursor = Math.max(0, Math.min(storedCursor, glyphs.length));
160
+ // 4. Keyboard interaction via the KeyEvent frame queue (only when focused)
161
+ if (isSelected && ctx.keys.length > 0) {
162
+ const edit = applyKeys(ctx.keys, value, cursor);
163
+ if (edit.changed) {
164
+ setValue(edit.value);
165
+ if (props.onChange)
166
+ props.onChange(edit.value);
167
+ glyphs = graphemes(edit.value);
168
+ }
169
+ if (edit.cursor !== storedCursor)
170
+ setCursor(edit.cursor);
171
+ cursor = edit.cursor;
172
+ }
173
+ // 5. Resolve Theme — all colors come from ctx.theme tokens so the field
174
+ // restyles on live theme swaps.
175
+ const theme = ctx.theme;
176
+ const borderCol = isSelected
177
+ ? theme.focus
178
+ : isHovered
179
+ ? theme.muted
180
+ : theme.border;
181
+ const labelColor = theme.foreground;
182
+ const placeholderColor = theme.muted;
183
+ const textColor = theme.foreground;
184
+ // Inverse-video cursor cell: swap the field's fg/bg at the cursor.
185
+ const inverse = (s) => color.bg(theme.foreground, color.fg(theme.surface, s));
186
+ const displayGlyphs = props.type === 'password' ? glyphs.map(() => '*') : glyphs;
187
+ // Inner columns between the borders and [0,1] padding.
188
+ const innerW = Math.max(1, area.width - 4);
189
+ let fieldValue = '';
190
+ if (glyphs.length === 0) {
191
+ const placeholder = props.placeholder
192
+ ? color.fg(placeholderColor, props.placeholder)
193
+ : '';
194
+ // Focused empty field: inverse-space cursor, ghost placeholder after it.
195
+ fieldValue = isSelected ? inverse(' ') + placeholder : placeholder;
196
+ }
197
+ else if (isSelected) {
198
+ const win = cursorWindow(displayGlyphs, cursor, scrollX, innerW);
199
+ if (win.scroll !== scrollX)
200
+ setScrollX(win.scroll);
201
+ fieldValue =
202
+ color.fg(textColor, win.before) +
203
+ inverse(win.cursorGlyph) +
204
+ color.fg(textColor, win.after);
205
+ }
206
+ else {
207
+ fieldValue = color.fg(textColor, displayGlyphs.join(''));
208
+ }
209
+ const field = box({
210
+ x: area.x,
211
+ y: area.y,
212
+ width: area.width,
213
+ height: area.height,
214
+ border: 'rounded',
215
+ borderColor: borderCol,
216
+ bgColor: theme.surface,
217
+ padding: [0, 1],
218
+ align: 'left',
219
+ valign: 'middle',
220
+ detach: true,
221
+ }, ({ text }) => text(fieldValue));
222
+ if (props.label) {
223
+ ctx.text(color.fg(labelColor, props.label));
224
+ ctx.text('\n');
225
+ }
226
+ const renderedField = ctx.isRoot ? field : indentBlock(field, area.x);
227
+ if (!ctx.isRoot) {
228
+ ctx.text(renderedField);
229
+ }
230
+ return renderedField;
231
+ }
232
+ //# sourceMappingURL=Input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Input.js","sourceRoot":"","sources":["../../src/components/Input.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type ColorValue } from '../colors';
2
+ import type { BuntiContext } from '../dsl';
3
+ export interface LinkProps {
4
+ id: string;
5
+ label: string;
6
+ /** Base color (default theme.accent); brightens on hover (mode-aware). */
7
+ color?: ColorValue;
8
+ onClick?: () => void;
9
+ /** Explicit column (default: current flow cursor). */
10
+ x?: number;
11
+ /** Explicit row (default: current flow cursor). */
12
+ y?: number;
13
+ }
14
+ /**
15
+ * Inline hyperlink: underlined themed text with a pointer-friendly hitbox.
16
+ * Hover brightens the color one step (mode-aware); clicks fire once per
17
+ * mouse release via the hitbox harness. Appends to the current flow and
18
+ * returns the styled string.
19
+ */
20
+ export declare function Link(ctx: BuntiContext, props: LinkProps): string;
21
+ //# sourceMappingURL=Link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Link.d.ts","sourceRoot":"","sources":["../../src/components/Link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,UAAU,EAAM,MAAM,WAAW,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAG3C,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,sDAAsD;IACtD,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,mDAAmD;IACnD,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAuBhE"}
@@ -0,0 +1,29 @@
1
+ import { adjustBrightness, fg } from '../colors';
2
+ import { visibleWidth } from '../utils';
3
+ /**
4
+ * Inline hyperlink: underlined themed text with a pointer-friendly hitbox.
5
+ * Hover brightens the color one step (mode-aware); clicks fire once per
6
+ * mouse release via the hitbox harness. Appends to the current flow and
7
+ * returns the styled string.
8
+ */
9
+ export function Link(ctx, props) {
10
+ const width = visibleWidth(props.label);
11
+ const x = props.x ?? ctx.cursorX;
12
+ const y = props.y ?? ctx.cursorY;
13
+ const interaction = ctx.hitbox(props.id, { x, y, width, height: 1 });
14
+ if (interaction.clicked)
15
+ props.onClick?.();
16
+ const theme = ctx.theme;
17
+ const base = props.color ?? theme.accent;
18
+ const shift = theme.mode === 'dark' ? 25 : -25;
19
+ const colorNow = interaction.hovered ? adjustBrightness(base, shift) : base;
20
+ // Raw SGR 4/24 (not ctx.color.underline) so underline survives regardless
21
+ // of the vendor formatter's TTY color gate.
22
+ const styled = `\x1b[4m${fg(colorNow, props.label)}\x1b[24m`;
23
+ // When explicitly placed right of the cursor, pad the gap so the flow
24
+ // lands the label on its hitbox.
25
+ const pad = props.x !== undefined ? ' '.repeat(Math.max(0, props.x - ctx.cursorX)) : '';
26
+ ctx.text(pad + styled);
27
+ return styled;
28
+ }
29
+ //# sourceMappingURL=Link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Link.js","sourceRoot":"","sources":["../../src/components/Link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAmB,EAAE,EAAE,MAAM,WAAW,CAAC;AAElE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAcxC;;;;;GAKG;AACH,MAAM,UAAU,IAAI,CAAC,GAAiB,EAAE,KAAgB;IACtD,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC;IACjC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC;IAEjC,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IACrE,IAAI,WAAW,CAAC,OAAO;QAAE,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;IAE3C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;IACxB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;IACzC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/C,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAE5E,0EAA0E;IAC1E,4CAA4C;IAC5C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC;IAE7D,sEAAsE;IACtE,iCAAiC;IACjC,MAAM,GAAG,GACP,KAAK,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9E,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC;IACvB,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { BuntiContext } from '../dsl';
2
+ /** Props for Modal. */
3
+ export interface ModalProps {
4
+ /** Dialog width in cells. */
5
+ width: number;
6
+ /** Dialog height in rows. */
7
+ height: number;
8
+ /** Border glyph set (default 'double'). */
9
+ border?: 'default' | 'rounded' | 'double' | 'dashed' | 'dotted' | 'frame' | 'thick-frame' | 'classic' | 'none';
10
+ /** Border color (default theme.border). */
11
+ borderColor?: any;
12
+ /** Surface color (default theme.surfaceRaised). */
13
+ bgColor?: any;
14
+ /** Horizontal content alignment (default 'center'). */
15
+ align?: 'left' | 'center' | 'right';
16
+ /** Vertical content alignment. */
17
+ valign?: 'top' | 'middle' | 'bottom';
18
+ /** [vertical, horizontal] padding inside the border, in cells. */
19
+ padding?: [number, number];
20
+ /** Keys the entrance animation when multiple modals coexist. */
21
+ id?: string;
22
+ /** Layer z-index the modal composites at (default 100). */
23
+ zIndex?: number;
24
+ /** Backdrop dim 0..1 under the modal; `false` or 0 disables (default 0.55). */
25
+ backdrop?: number | false;
26
+ /** Drop shadow under the modal surface (default true). */
27
+ shadow?: boolean;
28
+ /** 150ms fade + slide-in entrance (default true). */
29
+ entrance?: boolean;
30
+ }
31
+ /**
32
+ * Modal dialog: renders on its own layer with a dimmed backdrop and drop
33
+ * shadow, centered, with a 150ms fade/slide-in entrance. Surface and border
34
+ * derive from ctx.theme tokens; every visual default can be overridden or
35
+ * disabled via props.
36
+ */
37
+ export declare function Modal(ctx: BuntiContext, props: ModalProps, callback: (sub: BuntiContext) => void): string;
38
+ //# sourceMappingURL=Modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/components/Modal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAI3C,uBAAuB;AACvB,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,MAAM,CAAC,EACH,SAAS,GACT,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,aAAa,GACb,SAAS,GACT,MAAM,CAAC;IACX,2CAA2C;IAC3C,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,mDAAmD;IACnD,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,kCAAkC;IAClC,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACrC,kEAAkE;IAClE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,gEAAgE;IAChE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC1B,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAcD;;;;;GAKG;AACH,wBAAgB,KAAK,CACnB,GAAG,EAAE,YAAY,EACjB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,GACpC,MAAM,CAmFR"}
@@ -0,0 +1,87 @@
1
+ import { fade, isThemeColor } from '../colors';
2
+ import { clamp01, easeOutCubic } from '../easing';
3
+ /** Entrance animation length. */
4
+ const ENTRANCE_MS = 150;
5
+ /** A modal unseen for this long counts as closed; reopening replays the entrance. */
6
+ const REOPEN_MS = 250;
7
+ /** Colors the entrance can interpolate (not gradients/styler functions). */
8
+ function fadeable(color) {
9
+ if (isThemeColor(color))
10
+ return true;
11
+ if (typeof color === 'string' || typeof color === 'number')
12
+ return true;
13
+ return typeof color === 'object' && color !== null && 'r' in color;
14
+ }
15
+ /**
16
+ * Modal dialog: renders on its own layer with a dimmed backdrop and drop
17
+ * shadow, centered, with a 150ms fade/slide-in entrance. Surface and border
18
+ * derive from ctx.theme tokens; every visual default can be overridden or
19
+ * disabled via props.
20
+ */
21
+ export function Modal(ctx, props, callback) {
22
+ const theme = ctx.theme;
23
+ const backdrop = props.backdrop === false ? 0 : (props.backdrop ?? 0.55);
24
+ const shadow = props.shadow ?? true;
25
+ // Entrance progress, keyed per modal id in componentState. `lastSeen`
26
+ // tracks presence so a reopened modal (absent > REOPEN_MS) replays the
27
+ // entrance instead of reusing its stale open timestamp.
28
+ let progress = 1;
29
+ if (props.entrance !== false) {
30
+ const key = `__modal_entrance:${props.id ?? 'modal'}`;
31
+ const now = Date.now();
32
+ const record = ctx.state.componentState.get(key);
33
+ if (!record || now - record.lastSeen > REOPEN_MS) {
34
+ ctx.state.componentState.set(key, { openedAt: now, lastSeen: now });
35
+ progress = 0;
36
+ }
37
+ else {
38
+ record.lastSeen = now;
39
+ progress = clamp01((now - record.openedAt) / ENTRANCE_MS);
40
+ }
41
+ }
42
+ const eased = easeOutCubic(progress);
43
+ let rendered = '';
44
+ ctx.layer({
45
+ zIndex: props.zIndex ?? 100,
46
+ backdrop: backdrop > 0 ? backdrop : undefined,
47
+ shadow,
48
+ }, (overlay) => {
49
+ const modalW = props.width;
50
+ const modalH = props.height;
51
+ // Slide: enter from 1 row above the resting position.
52
+ const slide = Math.round(1 - eased);
53
+ const localX = Math.max(0, Math.floor((overlay.width - modalW) / 2));
54
+ const localY = Math.max(0, Math.floor((overlay.height - modalH) / 2) - slide);
55
+ const x = overlay.offsetX + localX;
56
+ const y = overlay.offsetY + localY;
57
+ // Fade: surface bg and border sweep from the screen background color
58
+ // to their final colors over the entrance.
59
+ const targetBg = props.bgColor ?? theme.surfaceRaised;
60
+ const surfaceBg = eased < 1 && fadeable(targetBg)
61
+ ? fade(theme.background.rgb, targetBg, eased)
62
+ : targetBg;
63
+ const targetBorder = props.borderColor ?? theme.border;
64
+ const borderColor = eased < 1 && fadeable(targetBorder)
65
+ ? fade(theme.background.rgb, targetBorder, eased)
66
+ : targetBorder;
67
+ // 1. Solid background rect: the raised-surface plate the dialog
68
+ // floats on (guarantees full coverage under sparse content).
69
+ overlay.rect(x, y, modalW, modalH, { char: ' ', bg: surfaceBg });
70
+ // 2. The box enclosing the modal content.
71
+ rendered = overlay.box({
72
+ x: localX,
73
+ y: localY,
74
+ width: modalW,
75
+ height: modalH,
76
+ border: props.border ?? 'double',
77
+ borderColor,
78
+ // Content fallback fg: explicit ANSI styling in content still wins.
79
+ color: theme.foreground,
80
+ align: props.align ?? 'center',
81
+ valign: props.valign,
82
+ padding: props.padding,
83
+ }, callback);
84
+ });
85
+ return rendered;
86
+ }
87
+ //# sourceMappingURL=Modal.js.map