@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.
- package/AGENTS.md +65 -0
- package/README.md +145 -34
- package/dist/cli.d.ts +44 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +147 -0
- package/dist/cli.js.map +1 -0
- package/dist/colors.d.ts +144 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +426 -0
- package/dist/colors.js.map +1 -0
- package/dist/components/Box.d.ts +11 -0
- package/dist/components/Box.d.ts.map +1 -0
- package/dist/components/Box.js +9 -0
- package/dist/components/Box.js.map +1 -0
- package/dist/components/Button.d.ts +24 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Button.js +102 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/Card.d.ts +33 -0
- package/dist/components/Card.d.ts.map +1 -0
- package/dist/components/Card.js +79 -0
- package/dist/components/Card.js.map +1 -0
- package/dist/components/Header.d.ts +23 -0
- package/dist/components/Header.d.ts.map +1 -0
- package/dist/components/Header.js +58 -0
- package/dist/components/Header.js.map +1 -0
- package/dist/components/Input.d.ts +25 -0
- package/dist/components/Input.d.ts.map +1 -0
- package/dist/components/Input.js +232 -0
- package/dist/components/Input.js.map +1 -0
- package/dist/components/Link.d.ts +21 -0
- package/dist/components/Link.d.ts.map +1 -0
- package/dist/components/Link.js +29 -0
- package/dist/components/Link.js.map +1 -0
- package/dist/components/Modal.d.ts +38 -0
- package/dist/components/Modal.d.ts.map +1 -0
- package/dist/components/Modal.js +87 -0
- package/dist/components/Modal.js.map +1 -0
- package/dist/components/Progress.d.ts +27 -0
- package/dist/components/Progress.d.ts.map +1 -0
- package/dist/components/Progress.js +39 -0
- package/dist/components/Progress.js.map +1 -0
- package/dist/components/Spinner.d.ts +19 -0
- package/dist/components/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner.js +27 -0
- package/dist/components/Spinner.js.map +1 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +10 -0
- package/dist/components/index.js.map +1 -0
- package/dist/data/nf-glyphs.d.ts +11 -0
- package/dist/data/nf-glyphs.d.ts.map +1 -0
- package/dist/data/nf-glyphs.js +535 -0
- package/dist/data/nf-glyphs.js.map +1 -0
- package/dist/data/nf-names.d.ts +8 -0
- package/dist/data/nf-names.d.ts.map +1 -0
- package/dist/data/nf-names.js +2 -0
- package/dist/data/nf-names.js.map +1 -0
- package/dist/demo-registry.d.ts +27 -0
- package/dist/demo-registry.d.ts.map +1 -0
- package/dist/demo-registry.js +57 -0
- package/dist/demo-registry.js.map +1 -0
- package/dist/demos/2048.ts +743 -0
- package/dist/demos/animation.ts +246 -0
- package/dist/demos/demo-layout.ts +124 -0
- package/dist/demos/engine.ts +374 -0
- package/dist/demos/interaction.ts +91 -0
- package/dist/demos/login.ts +203 -0
- package/dist/demos/mission-control.ts +558 -0
- package/dist/demos/showcase.ts +224 -0
- package/dist/detect.d.ts +104 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +227 -0
- package/dist/detect.js.map +1 -0
- package/dist/diagnostics.d.ts +73 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +225 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/dsl/context.d.ts +11 -0
- package/dist/dsl/context.d.ts.map +1 -0
- package/dist/dsl/context.js +563 -0
- package/dist/dsl/context.js.map +1 -0
- package/dist/dsl/hooks.d.ts +12 -0
- package/dist/dsl/hooks.d.ts.map +1 -0
- package/dist/dsl/hooks.js +148 -0
- package/dist/dsl/hooks.js.map +1 -0
- package/dist/dsl/interaction.d.ts +16 -0
- package/dist/dsl/interaction.d.ts.map +1 -0
- package/dist/dsl/interaction.js +101 -0
- package/dist/dsl/interaction.js.map +1 -0
- package/dist/dsl/layers.d.ts +14 -0
- package/dist/dsl/layers.d.ts.map +1 -0
- package/dist/dsl/layers.js +87 -0
- package/dist/dsl/layers.js.map +1 -0
- package/dist/dsl/motion.d.ts +10 -0
- package/dist/dsl/motion.d.ts.map +1 -0
- package/dist/dsl/motion.js +150 -0
- package/dist/dsl/motion.js.map +1 -0
- package/dist/dsl/render.d.ts +22 -0
- package/dist/dsl/render.d.ts.map +1 -0
- package/dist/dsl/render.js +69 -0
- package/dist/dsl/render.js.map +1 -0
- package/dist/dsl/types.d.ts +510 -0
- package/dist/dsl/types.d.ts.map +1 -0
- package/dist/dsl/types.js +33 -0
- package/dist/dsl/types.js.map +1 -0
- package/dist/dsl.d.ts +18 -0
- package/dist/dsl.d.ts.map +1 -0
- package/dist/dsl.js +17 -0
- package/dist/dsl.js.map +1 -0
- package/dist/easing.d.ts +45 -0
- package/dist/easing.d.ts.map +1 -0
- package/dist/easing.js +121 -0
- package/dist/easing.js.map +1 -0
- package/dist/geometry.d.ts +77 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +130 -0
- package/dist/geometry.js.map +1 -0
- package/dist/icons-full.d.ts +5 -0
- package/dist/icons-full.d.ts.map +1 -0
- package/dist/icons-full.js +31 -0
- package/dist/icons-full.js.map +1 -0
- package/dist/icons.d.ts +442 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +314 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +228 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/input.d.ts +177 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +533 -0
- package/dist/input.js.map +1 -0
- package/dist/layout.d.ts +194 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +673 -0
- package/dist/layout.js.map +1 -0
- package/dist/render.d.ts +49 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +650 -0
- package/dist/render.js.map +1 -0
- package/dist/state.d.ts +226 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +130 -0
- package/dist/state.js.map +1 -0
- package/dist/theme.d.ts +129 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +167 -0
- package/dist/theme.js.map +1 -0
- package/dist/themes/catppuccin-mocha.d.ts +19 -0
- package/dist/themes/catppuccin-mocha.d.ts.map +1 -0
- package/dist/themes/catppuccin-mocha.js +36 -0
- package/dist/themes/catppuccin-mocha.js.map +1 -0
- package/dist/themes/dracula.d.ts +21 -0
- package/dist/themes/dracula.d.ts.map +1 -0
- package/dist/themes/dracula.js +37 -0
- package/dist/themes/dracula.js.map +1 -0
- package/dist/themes/github-light.d.ts +21 -0
- package/dist/themes/github-light.d.ts.map +1 -0
- package/dist/themes/github-light.js +36 -0
- package/dist/themes/github-light.js.map +1 -0
- package/dist/themes/index.d.ts +19 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +17 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/themes/nord.d.ts +23 -0
- package/dist/themes/nord.d.ts.map +1 -0
- package/dist/themes/nord.js +38 -0
- package/dist/themes/nord.js.map +1 -0
- package/dist/themes/one-dark-pro.d.ts +21 -0
- package/dist/themes/one-dark-pro.d.ts.map +1 -0
- package/dist/themes/one-dark-pro.js +36 -0
- package/dist/themes/one-dark-pro.js.map +1 -0
- package/dist/themes/tokyo-night.d.ts +20 -0
- package/dist/themes/tokyo-night.d.ts.map +1 -0
- package/dist/themes/tokyo-night.js +37 -0
- package/dist/themes/tokyo-night.js.map +1 -0
- package/dist/utils.d.ts +28 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +158 -0
- package/dist/utils.js.map +1 -0
- package/dist/vendor/colors.d.ts +45 -0
- package/dist/vendor/colors.d.ts.map +1 -0
- package/dist/vendor/colors.js +72 -0
- package/dist/vendor/colors.js.map +1 -0
- package/llms.txt +79 -0
- package/package.json +52 -11
- package/src/colors.ts +0 -255
- package/src/components/Button.ts +0 -104
- package/src/components/Card.ts +0 -53
- package/src/components/Header.ts +0 -65
- package/src/components/Input.ts +0 -124
- package/src/components/index.ts +0 -4
- package/src/data/glyphs.ts +0 -30
- package/src/detect.ts +0 -60
- package/src/dsl.ts +0 -661
- package/src/icons.ts +0 -186
- package/src/index.ts +0 -72
- package/src/layout.ts +0 -640
- package/src/render.ts +0 -302
- package/src/state.ts +0 -148
- 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
|