@effect-tui/core 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/anim.d.ts +4 -0
- package/dist/anim.d.ts.map +1 -0
- package/dist/anim.js +5 -0
- package/dist/anim.js.map +1 -0
- package/dist/ansi.d.ts +69 -0
- package/dist/ansi.d.ts.map +1 -0
- package/dist/ansi.js +72 -0
- package/dist/ansi.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +18 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +247 -0
- package/dist/keys.js.map +1 -0
- package/dist/layout/linearStack.d.ts +17 -0
- package/dist/layout/linearStack.d.ts.map +1 -0
- package/dist/layout/linearStack.js +86 -0
- package/dist/layout/linearStack.js.map +1 -0
- package/dist/motion-value.d.ts +58 -0
- package/dist/motion-value.d.ts.map +1 -0
- package/dist/motion-value.js +250 -0
- package/dist/motion-value.js.map +1 -0
- package/dist/present/display.d.ts +58 -0
- package/dist/present/display.d.ts.map +1 -0
- package/dist/present/display.js +168 -0
- package/dist/present/display.js.map +1 -0
- package/dist/present/writers/fullscreen.d.ts +19 -0
- package/dist/present/writers/fullscreen.d.ts.map +1 -0
- package/dist/present/writers/fullscreen.js +55 -0
- package/dist/present/writers/fullscreen.js.map +1 -0
- package/dist/present/writers/inline.d.ts +20 -0
- package/dist/present/writers/inline.d.ts.map +1 -0
- package/dist/present/writers/inline.js +92 -0
- package/dist/present/writers/inline.js.map +1 -0
- package/dist/render/buffer.d.ts +31 -0
- package/dist/render/buffer.d.ts.map +1 -0
- package/dist/render/buffer.js +183 -0
- package/dist/render/buffer.js.map +1 -0
- package/dist/render/color-utils.d.ts +18 -0
- package/dist/render/color-utils.d.ts.map +1 -0
- package/dist/render/color-utils.js +58 -0
- package/dist/render/color-utils.js.map +1 -0
- package/dist/render/diff.d.ts +30 -0
- package/dist/render/diff.d.ts.map +1 -0
- package/dist/render/diff.js +83 -0
- package/dist/render/diff.js.map +1 -0
- package/dist/render/measure.d.ts +15 -0
- package/dist/render/measure.d.ts.map +1 -0
- package/dist/render/measure.js +65 -0
- package/dist/render/measure.js.map +1 -0
- package/dist/render/palette.d.ts +46 -0
- package/dist/render/palette.d.ts.map +1 -0
- package/dist/render/palette.js +108 -0
- package/dist/render/palette.js.map +1 -0
- package/dist/render/surface.d.ts +77 -0
- package/dist/render/surface.d.ts.map +1 -0
- package/dist/render/surface.js +198 -0
- package/dist/render/surface.js.map +1 -0
- package/dist/runtime/backend_node.d.ts +36 -0
- package/dist/runtime/backend_node.d.ts.map +1 -0
- package/dist/runtime/backend_node.js +66 -0
- package/dist/runtime/backend_node.js.map +1 -0
- package/dist/spring-physics.d.ts +36 -0
- package/dist/spring-physics.d.ts.map +1 -0
- package/dist/spring-physics.js +113 -0
- package/dist/spring-physics.js.map +1 -0
- package/dist/spring.d.ts +73 -0
- package/dist/spring.d.ts.map +1 -0
- package/dist/spring.js +136 -0
- package/dist/spring.js.map +1 -0
- package/dist/ui/containers/canvas.d.ts +13 -0
- package/dist/ui/containers/canvas.d.ts.map +1 -0
- package/dist/ui/containers/canvas.js +16 -0
- package/dist/ui/containers/canvas.js.map +1 -0
- package/dist/ui/containers/geometry-reader.d.ts +17 -0
- package/dist/ui/containers/geometry-reader.d.ts.map +1 -0
- package/dist/ui/containers/geometry-reader.js +24 -0
- package/dist/ui/containers/geometry-reader.js.map +1 -0
- package/dist/ui/containers/hstack.d.ts +12 -0
- package/dist/ui/containers/hstack.d.ts.map +1 -0
- package/dist/ui/containers/hstack.js +28 -0
- package/dist/ui/containers/hstack.js.map +1 -0
- package/dist/ui/containers/scroll.d.ts +28 -0
- package/dist/ui/containers/scroll.d.ts.map +1 -0
- package/dist/ui/containers/scroll.js +97 -0
- package/dist/ui/containers/scroll.js.map +1 -0
- package/dist/ui/containers/shared.d.ts +12 -0
- package/dist/ui/containers/shared.d.ts.map +1 -0
- package/dist/ui/containers/shared.js +19 -0
- package/dist/ui/containers/shared.js.map +1 -0
- package/dist/ui/containers/vstack.d.ts +12 -0
- package/dist/ui/containers/vstack.d.ts.map +1 -0
- package/dist/ui/containers/vstack.js +28 -0
- package/dist/ui/containers/vstack.js.map +1 -0
- package/dist/ui/containers/zstack.d.ts +14 -0
- package/dist/ui/containers/zstack.d.ts.map +1 -0
- package/dist/ui/containers/zstack.js +36 -0
- package/dist/ui/containers/zstack.js.map +1 -0
- package/dist/ui/core/geometry-store.d.ts +22 -0
- package/dist/ui/core/geometry-store.d.ts.map +1 -0
- package/dist/ui/core/geometry-store.js +29 -0
- package/dist/ui/core/geometry-store.js.map +1 -0
- package/dist/ui/core/geometry.d.ts +34 -0
- package/dist/ui/core/geometry.d.ts.map +1 -0
- package/dist/ui/core/geometry.js +14 -0
- package/dist/ui/core/geometry.js.map +1 -0
- package/dist/ui/core/view.d.ts +25 -0
- package/dist/ui/core/view.d.ts.map +1 -0
- package/dist/ui/core/view.js +34 -0
- package/dist/ui/core/view.js.map +1 -0
- package/dist/ui/index.d.ts +44 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +39 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/inlinetext.d.ts +24 -0
- package/dist/ui/inlinetext.d.ts.map +1 -0
- package/dist/ui/inlinetext.js +131 -0
- package/dist/ui/inlinetext.js.map +1 -0
- package/dist/ui/install.d.ts +22 -0
- package/dist/ui/install.d.ts.map +1 -0
- package/dist/ui/install.js +66 -0
- package/dist/ui/install.js.map +1 -0
- package/dist/ui/markdown.d.ts +40 -0
- package/dist/ui/markdown.d.ts.map +1 -0
- package/dist/ui/markdown.js +351 -0
- package/dist/ui/markdown.js.map +1 -0
- package/dist/ui/modifiers/border.d.ts +33 -0
- package/dist/ui/modifiers/border.d.ts.map +1 -0
- package/dist/ui/modifiers/border.js +82 -0
- package/dist/ui/modifiers/border.js.map +1 -0
- package/dist/ui/modifiers/fill.d.ts +14 -0
- package/dist/ui/modifiers/fill.d.ts.map +1 -0
- package/dist/ui/modifiers/fill.js +25 -0
- package/dist/ui/modifiers/fill.js.map +1 -0
- package/dist/ui/modifiers/frame.d.ts +23 -0
- package/dist/ui/modifiers/frame.d.ts.map +1 -0
- package/dist/ui/modifiers/frame.js +54 -0
- package/dist/ui/modifiers/frame.js.map +1 -0
- package/dist/ui/modifiers/offset.d.ts +15 -0
- package/dist/ui/modifiers/offset.d.ts.map +1 -0
- package/dist/ui/modifiers/offset.js +21 -0
- package/dist/ui/modifiers/offset.js.map +1 -0
- package/dist/ui/modifiers/opacity.d.ts +15 -0
- package/dist/ui/modifiers/opacity.d.ts.map +1 -0
- package/dist/ui/modifiers/opacity.js +95 -0
- package/dist/ui/modifiers/opacity.js.map +1 -0
- package/dist/ui/modifiers/padding.d.ts +20 -0
- package/dist/ui/modifiers/padding.d.ts.map +1 -0
- package/dist/ui/modifiers/padding.js +36 -0
- package/dist/ui/modifiers/padding.js.map +1 -0
- package/dist/ui/modifiers/styled.d.ts +14 -0
- package/dist/ui/modifiers/styled.d.ts.map +1 -0
- package/dist/ui/modifiers/styled.js +26 -0
- package/dist/ui/modifiers/styled.js.map +1 -0
- package/dist/ui/primitives/rectangle.d.ts +15 -0
- package/dist/ui/primitives/rectangle.d.ts.map +1 -0
- package/dist/ui/primitives/rectangle.js +23 -0
- package/dist/ui/primitives/rectangle.js.map +1 -0
- package/dist/ui/primitives/spacer.d.ts +13 -0
- package/dist/ui/primitives/spacer.d.ts.map +1 -0
- package/dist/ui/primitives/spacer.js +16 -0
- package/dist/ui/primitives/spacer.js.map +1 -0
- package/dist/ui/primitives/text.d.ts +15 -0
- package/dist/ui/primitives/text.d.ts.map +1 -0
- package/dist/ui/primitives/text.js +79 -0
- package/dist/ui/primitives/text.js.map +1 -0
- package/dist/ui/primitives/wrapped-text.d.ts +30 -0
- package/dist/ui/primitives/wrapped-text.d.ts.map +1 -0
- package/dist/ui/primitives/wrapped-text.js +117 -0
- package/dist/ui/primitives/wrapped-text.js.map +1 -0
- package/dist/ui/shinytext.d.ts +66 -0
- package/dist/ui/shinytext.d.ts.map +1 -0
- package/dist/ui/shinytext.js +99 -0
- package/dist/ui/shinytext.js.map +1 -0
- package/dist/ui/text/layout.d.ts +35 -0
- package/dist/ui/text/layout.d.ts.map +1 -0
- package/dist/ui/text/layout.js +102 -0
- package/dist/ui/text/layout.js.map +1 -0
- package/dist/ui/textinput.d.ts +140 -0
- package/dist/ui/textinput.d.ts.map +1 -0
- package/dist/ui/textinput.js +402 -0
- package/dist/ui/textinput.js.map +1 -0
- package/dist/ui/view-constructors.d.ts +72 -0
- package/dist/ui/view-constructors.d.ts.map +1 -0
- package/dist/ui/view-constructors.js +74 -0
- package/dist/ui/view-constructors.js.map +1 -0
- package/package.json +57 -0
- package/src/anim.ts +5 -0
- package/src/ansi.ts +83 -0
- package/src/index.ts +21 -0
- package/src/keys.ts +302 -0
- package/src/layout/linearStack.ts +115 -0
- package/src/motion-value.ts +335 -0
- package/src/present/display.ts +206 -0
- package/src/present/writers/fullscreen.ts +58 -0
- package/src/present/writers/inline.ts +101 -0
- package/src/render/buffer.ts +200 -0
- package/src/render/color-utils.ts +60 -0
- package/src/render/diff.ts +95 -0
- package/src/render/measure.ts +74 -0
- package/src/render/palette.ts +113 -0
- package/src/render/surface.ts +238 -0
- package/src/runtime/backend_node.ts +80 -0
- package/src/spring-physics.ts +151 -0
- package/src/spring.ts +234 -0
- package/src/ui/__snapshots__/wrappedtext.test.ts.snap +57 -0
- package/src/ui/containers/canvas.ts +18 -0
- package/src/ui/containers/geometry-reader.ts +32 -0
- package/src/ui/containers/hstack.ts +33 -0
- package/src/ui/containers/scroll.ts +106 -0
- package/src/ui/containers/shared.ts +27 -0
- package/src/ui/containers/vstack.ts +34 -0
- package/src/ui/containers/zstack.ts +37 -0
- package/src/ui/core/geometry-store.ts +42 -0
- package/src/ui/core/geometry.ts +30 -0
- package/src/ui/core/view.ts +49 -0
- package/src/ui/index.ts +84 -0
- package/src/ui/inlinetext.ts +135 -0
- package/src/ui/install.ts +110 -0
- package/src/ui/markdown.test.ts +74 -0
- package/src/ui/markdown.ts +388 -0
- package/src/ui/modifiers/border.ts +100 -0
- package/src/ui/modifiers/fill.ts +28 -0
- package/src/ui/modifiers/frame.ts +74 -0
- package/src/ui/modifiers/offset.ts +23 -0
- package/src/ui/modifiers/opacity.ts +93 -0
- package/src/ui/modifiers/padding.ts +53 -0
- package/src/ui/modifiers/styled.ts +31 -0
- package/src/ui/primitives/rectangle.ts +25 -0
- package/src/ui/primitives/spacer.ts +18 -0
- package/src/ui/primitives/text.ts +85 -0
- package/src/ui/primitives/wrapped-text.ts +131 -0
- package/src/ui/shinytext.ts +159 -0
- package/src/ui/text/layout.ts +119 -0
- package/src/ui/textinput.ts +496 -0
- package/src/ui/view-constructors.ts +96 -0
- package/src/ui/wrappedtext.test.ts +138 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* view-constructors.ts — Static constructor methods for View
|
|
2
|
+
*
|
|
3
|
+
* This module contains all the static constructor methods that will be merged
|
|
4
|
+
* with the View class via namespace merging to avoid circular dependencies.
|
|
5
|
+
*/
|
|
6
|
+
import { Colors } from "../render/surface.js";
|
|
7
|
+
// Import all component classes for builders
|
|
8
|
+
import { Text } from "./primitives/text.js";
|
|
9
|
+
import { Rectangle } from "./primitives/rectangle.js";
|
|
10
|
+
import { Spacer } from "./primitives/spacer.js";
|
|
11
|
+
import { WrappedText } from "./primitives/wrapped-text.js";
|
|
12
|
+
import { HStack } from "./containers/hstack.js";
|
|
13
|
+
import { VStack } from "./containers/vstack.js";
|
|
14
|
+
import { ZStack } from "./containers/zstack.js";
|
|
15
|
+
import { Scroll } from "./containers/scroll.js";
|
|
16
|
+
import { Canvas } from "./containers/canvas.js";
|
|
17
|
+
import { GeometryReader } from "./containers/geometry-reader.js";
|
|
18
|
+
import { TextInput } from "./textinput.js";
|
|
19
|
+
import { Markdown } from "./markdown.js";
|
|
20
|
+
// Constructor functions that will become static methods on View
|
|
21
|
+
export const ViewConstructors = {
|
|
22
|
+
// Primitives
|
|
23
|
+
text: (s, wrap) => {
|
|
24
|
+
return new Text(s, wrap);
|
|
25
|
+
},
|
|
26
|
+
wrappedText: (s, options) => {
|
|
27
|
+
return new WrappedText(s, options);
|
|
28
|
+
},
|
|
29
|
+
rect: (w, h, fill) => {
|
|
30
|
+
return new Rectangle(w, h, typeof fill === "number" ? fill : (fill?.codePointAt?.(0) ?? 32));
|
|
31
|
+
},
|
|
32
|
+
spacer: (minLength = 0) => {
|
|
33
|
+
return new Spacer(minLength);
|
|
34
|
+
},
|
|
35
|
+
// Containers (accept opts or legacy (gap, { alignment }))
|
|
36
|
+
hstack: (children, optsOrGap = {}, maybe = {}) => {
|
|
37
|
+
const isNum = typeof optsOrGap === "number";
|
|
38
|
+
const gap = isNum ? optsOrGap : (optsOrGap.gap ?? optsOrGap.spacing ?? 1);
|
|
39
|
+
const alignment = (isNum ? maybe.alignment : optsOrGap.alignment) ?? "center";
|
|
40
|
+
return new HStack(children, gap, alignment);
|
|
41
|
+
},
|
|
42
|
+
vstack: (children, optsOrGap = {}, maybe = {}) => {
|
|
43
|
+
const isNum = typeof optsOrGap === "number";
|
|
44
|
+
const gap = isNum ? optsOrGap : (optsOrGap.gap ?? optsOrGap.spacing ?? 0);
|
|
45
|
+
const alignment = (isNum ? maybe.alignment : optsOrGap.alignment) ?? "leading";
|
|
46
|
+
return new VStack(children, gap, alignment);
|
|
47
|
+
},
|
|
48
|
+
zstack: (children, alignment) => {
|
|
49
|
+
return new ZStack(children, alignment ?? { h: "center", v: "center" });
|
|
50
|
+
},
|
|
51
|
+
scroll: (child, opts) => {
|
|
52
|
+
return new Scroll(child, opts ?? {});
|
|
53
|
+
},
|
|
54
|
+
canvas: (paint) => {
|
|
55
|
+
return new Canvas(paint);
|
|
56
|
+
},
|
|
57
|
+
geometryReader: (reader) => {
|
|
58
|
+
return new GeometryReader(reader);
|
|
59
|
+
},
|
|
60
|
+
// Overlay method (moved from chainer to avoid circular dependency)
|
|
61
|
+
overlay: (base, overlay, alignment) => {
|
|
62
|
+
return new ZStack([base, overlay], alignment ?? { h: "center", v: "center" });
|
|
63
|
+
},
|
|
64
|
+
// Components
|
|
65
|
+
textInput: (state, opts) => {
|
|
66
|
+
return new TextInput(state, opts);
|
|
67
|
+
},
|
|
68
|
+
markdown: (text, opts) => {
|
|
69
|
+
return new Markdown(text, opts);
|
|
70
|
+
},
|
|
71
|
+
// Utilities
|
|
72
|
+
Colors,
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=view-constructors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view-constructors.js","sourceRoot":"","sources":["../../src/ui/view-constructors.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@effect-tui/core",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Terminal UI library built with Effect",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"src",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/kitlangton/effect-tui.git"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/kitlangton/effect-tui",
|
|
17
|
+
"bugs": "https://github.com/kitlangton/effect-tui/issues",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"keywords": [
|
|
26
|
+
"terminal",
|
|
27
|
+
"tui",
|
|
28
|
+
"effect",
|
|
29
|
+
"cli",
|
|
30
|
+
"console"
|
|
31
|
+
],
|
|
32
|
+
"author": "Kit Langton",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public",
|
|
36
|
+
"tag": "alpha"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p .",
|
|
40
|
+
"typecheck": "tsc -p . --noEmit",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"format": "biome format --write .",
|
|
44
|
+
"format:check": "biome format .",
|
|
45
|
+
"prepublishOnly": "bun run typecheck && bun run build"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"effect": "^3.0.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@effect/vitest": "^0.27.0",
|
|
52
|
+
"@types/node": "^24.10.1",
|
|
53
|
+
"effect": "^3.19.8",
|
|
54
|
+
"typescript": "^5.9.3",
|
|
55
|
+
"vitest": "^4.0.14"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/anim.ts
ADDED
package/src/ansi.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// ANSI escape sequences and terminal control utilities
|
|
2
|
+
|
|
3
|
+
/** CSI prefix (ESC [) */
|
|
4
|
+
const CSI = "\x1b["
|
|
5
|
+
|
|
6
|
+
/** Basic ANSI codes and helpers */
|
|
7
|
+
export const ANSI = {
|
|
8
|
+
/** Reset all attributes */
|
|
9
|
+
reset: `${CSI}0m`,
|
|
10
|
+
|
|
11
|
+
/** Cursor control */
|
|
12
|
+
cursor: {
|
|
13
|
+
/** Hide cursor */
|
|
14
|
+
hide: `${CSI}?25l`,
|
|
15
|
+
/** Show cursor */
|
|
16
|
+
show: `${CSI}?25h`,
|
|
17
|
+
/** Move cursor to home position (1,1) */
|
|
18
|
+
home: `${CSI}H`,
|
|
19
|
+
/** Move cursor to 1-based row/column */
|
|
20
|
+
to: (row1: number, col1: number) => `${CSI}${row1};${col1}H`,
|
|
21
|
+
/** Move cursor to 1-based column on current row */
|
|
22
|
+
toCol: (col1: number) => `${CSI}${col1}G`,
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
/** Screen control */
|
|
26
|
+
screen: {
|
|
27
|
+
/** Enter alternate screen buffer */
|
|
28
|
+
altEnter: `${CSI}?1049h`,
|
|
29
|
+
/** Exit alternate screen buffer */
|
|
30
|
+
altExit: `${CSI}?1049l`,
|
|
31
|
+
/** Clear entire display */
|
|
32
|
+
clear: `${CSI}2J`,
|
|
33
|
+
/** Clear scrollback buffer */
|
|
34
|
+
clearScrollback: `${CSI}3J`,
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
/** Scrolling */
|
|
38
|
+
scroll: {
|
|
39
|
+
/** Scroll up by n lines */
|
|
40
|
+
up: (n: number) => `${CSI}${n}S`,
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/** Device Status Reports / queries */
|
|
44
|
+
report: {
|
|
45
|
+
/** Report Cursor Position (response: ESC [ row ; col R) */
|
|
46
|
+
cursorPosition: `${CSI}6n`,
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/** Bracketed paste mode */
|
|
50
|
+
paste: {
|
|
51
|
+
bracketed: {
|
|
52
|
+
on: `${CSI}?2004h`,
|
|
53
|
+
off: `${CSI}?2004l`,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
/** Mouse reporting toggles */
|
|
58
|
+
mouse: {
|
|
59
|
+
/** Enable button tracking and SGR coordinates */
|
|
60
|
+
on: `${CSI}?1002h${CSI}?1006h`,
|
|
61
|
+
/** Disable SGR coords and button tracking */
|
|
62
|
+
off: `${CSI}?1006l${CSI}?1002l`,
|
|
63
|
+
},
|
|
64
|
+
} as const
|
|
65
|
+
|
|
66
|
+
/** Terminal session control utilities (compositions of ANSI primitives) */
|
|
67
|
+
export const Terminal = {
|
|
68
|
+
/** Enter full-screen mode: alternate screen + hide cursor */
|
|
69
|
+
enterFullscreen: ANSI.screen.altEnter + ANSI.cursor.hide,
|
|
70
|
+
|
|
71
|
+
/** Exit full-screen mode: reset + show cursor + exit alternate screen */
|
|
72
|
+
exitFullscreen: ANSI.reset + ANSI.cursor.show + ANSI.screen.altExit,
|
|
73
|
+
|
|
74
|
+
/** Clear screen and move cursor to home */
|
|
75
|
+
clearAndHome: ANSI.screen.clear + ANSI.cursor.home,
|
|
76
|
+
|
|
77
|
+
/** Convenience shorthands for common toggles */
|
|
78
|
+
bracketedPasteOn: ANSI.paste.bracketed.on,
|
|
79
|
+
bracketedPasteOff: ANSI.paste.bracketed.off,
|
|
80
|
+
mouseOn: ANSI.mouse.on,
|
|
81
|
+
mouseOff: ANSI.mouse.off,
|
|
82
|
+
reportCursorPosition: ANSI.report.cursorPosition,
|
|
83
|
+
} as const
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Re-export core modules
|
|
2
|
+
export * from "./render/surface.js"
|
|
3
|
+
export * from "./render/buffer.js"
|
|
4
|
+
export * from "./render/measure.js"
|
|
5
|
+
export * from "./anim.js"
|
|
6
|
+
export * from "./keys.js"
|
|
7
|
+
|
|
8
|
+
// UI (canonical View and curated exports)
|
|
9
|
+
export { View } from "./ui/index.js"
|
|
10
|
+
export { ShinyText } from "./ui/index.js"
|
|
11
|
+
export { TextInput, TextInputState, editTextInput } from "./ui/index.js"
|
|
12
|
+
export { Markdown } from "./ui/index.js"
|
|
13
|
+
export type { TextInputStateLike, TextInputStatePlain, TextInputGeom, TextInputOptions } from "./ui/index.js"
|
|
14
|
+
export type { MarkdownOptions } from "./ui/index.js"
|
|
15
|
+
// Geometry store (last-known layout)
|
|
16
|
+
export { geometryStore } from "./ui/core/geometry-store.js"
|
|
17
|
+
export type { ViewId, Rect as GeometryRect, InputGeom } from "./ui/core/geometry-store.js"
|
|
18
|
+
|
|
19
|
+
// Renderers (optional public API)
|
|
20
|
+
export * from "./present/display.js"
|
|
21
|
+
export type { TerminalBackend } from "./runtime/backend_node.js"
|
package/src/keys.ts
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// Minimal key decoder for Node raw input → semantic key messages.
|
|
2
|
+
|
|
3
|
+
export type KeyName =
|
|
4
|
+
| "up"
|
|
5
|
+
| "down"
|
|
6
|
+
| "left"
|
|
7
|
+
| "right"
|
|
8
|
+
| "enter"
|
|
9
|
+
| "escape"
|
|
10
|
+
| "tab"
|
|
11
|
+
| "shift-tab"
|
|
12
|
+
| "backspace"
|
|
13
|
+
| "delete"
|
|
14
|
+
| "home"
|
|
15
|
+
| "end"
|
|
16
|
+
| "pageup"
|
|
17
|
+
| "pagedown"
|
|
18
|
+
| "insert"
|
|
19
|
+
| "return"
|
|
20
|
+
| "space"
|
|
21
|
+
| "char" // printable
|
|
22
|
+
|
|
23
|
+
export type KeyMsg = {
|
|
24
|
+
type: "key"
|
|
25
|
+
name: KeyName
|
|
26
|
+
text?: string // for printable chars
|
|
27
|
+
ctrl?: boolean
|
|
28
|
+
meta?: boolean
|
|
29
|
+
shift?: boolean
|
|
30
|
+
/** Optional phase; currently always "press" but left extensible. */
|
|
31
|
+
phase?: "press" | "repeat" | "release"
|
|
32
|
+
/** Set by renderer to allow handlers to stop further propagation. */
|
|
33
|
+
defaultPrevented?: boolean
|
|
34
|
+
preventDefault?: () => void
|
|
35
|
+
/** Source protocol (raw|csiu|modifyOther|kitty). */
|
|
36
|
+
source?: "raw" | "csiu" | "modify-other" | "kitty"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ESC = "\x1b"
|
|
40
|
+
|
|
41
|
+
export function* decodeKeys(buf: Buffer): Iterable<KeyMsg> {
|
|
42
|
+
// Handle control characters (0x00-0x1F)
|
|
43
|
+
// These are generated by Ctrl+letter combinations
|
|
44
|
+
if (buf.length === 1 && buf[0] !== undefined && buf[0] < 0x20) {
|
|
45
|
+
const byte = buf[0]
|
|
46
|
+
|
|
47
|
+
// Special control characters with specific meanings
|
|
48
|
+
switch (byte) {
|
|
49
|
+
case 0x03: // Ctrl-C (ETX - End of Text)
|
|
50
|
+
yield { type: "key", name: "char", text: "c", ctrl: true }
|
|
51
|
+
return
|
|
52
|
+
case 0x04: // Ctrl-D (EOT - End of Transmission)
|
|
53
|
+
yield { type: "key", name: "char", text: "d", ctrl: true }
|
|
54
|
+
return
|
|
55
|
+
case 0x17: // Ctrl-W (ETB - End of Transmission Block)
|
|
56
|
+
yield { type: "key", name: "char", text: "w", ctrl: true }
|
|
57
|
+
return
|
|
58
|
+
case 0x1a: // Ctrl-Z (SUB - Substitute)
|
|
59
|
+
yield { type: "key", name: "char", text: "z", ctrl: true }
|
|
60
|
+
return
|
|
61
|
+
case 0x0c: // Ctrl-L (FF - Form Feed)
|
|
62
|
+
yield { type: "key", name: "char", text: "l", ctrl: true }
|
|
63
|
+
return
|
|
64
|
+
case 0x09: // Tab (HT - Horizontal Tab)
|
|
65
|
+
yield { type: "key", name: "tab" }
|
|
66
|
+
return
|
|
67
|
+
case 0x0d: // Enter (CR - Carriage Return)
|
|
68
|
+
yield { type: "key", name: "enter" }
|
|
69
|
+
return
|
|
70
|
+
case 0x0a: // Line Feed (LF)
|
|
71
|
+
yield { type: "key", name: "enter" }
|
|
72
|
+
return
|
|
73
|
+
case 0x08: // Backspace (BS)
|
|
74
|
+
yield { type: "key", name: "backspace" }
|
|
75
|
+
return
|
|
76
|
+
case 0x1b: // Escape (ESC)
|
|
77
|
+
yield { type: "key", name: "escape" }
|
|
78
|
+
return
|
|
79
|
+
default:
|
|
80
|
+
// Other control characters: convert back to letter
|
|
81
|
+
// Ctrl-A = 0x01, Ctrl-B = 0x02, etc.
|
|
82
|
+
if (byte >= 0x01 && byte <= 0x1a) {
|
|
83
|
+
const letter = String.fromCharCode(byte + 96) // 0x01 + 96 = 'a'
|
|
84
|
+
yield { type: "key", name: "char", text: letter, ctrl: true }
|
|
85
|
+
} else {
|
|
86
|
+
// Unknown control character, emit raw
|
|
87
|
+
yield { type: "key", name: "char", text: String.fromCharCode(byte), ctrl: true }
|
|
88
|
+
}
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Handle 0x7F (DEL) separately as it's outside the control character range
|
|
94
|
+
if (buf.length === 1 && buf[0] === 0x7f) {
|
|
95
|
+
yield { type: "key", name: "backspace" }
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let s = buf.toString("utf8")
|
|
100
|
+
|
|
101
|
+
// Handle one or more SGR mouse reports possibly coalesced in a single chunk.
|
|
102
|
+
// Format: ESC [ < Cb ; Cx ; Cy (M|m) — terminals vary on the trailing letter; accept any A-Z.
|
|
103
|
+
const sgrRe = /\x1b\x5b<(?<cb>\d+);(?<cx>\d+);(?<cy>\d+)(?<updown>[A-Za-z])/g
|
|
104
|
+
let matchedAny = false
|
|
105
|
+
for (const m of s.matchAll(sgrRe)) {
|
|
106
|
+
matchedAny = true
|
|
107
|
+
const code = parseInt(m.groups?.cb ?? "0", 10)
|
|
108
|
+
if (code === 64) {
|
|
109
|
+
yield { type: "key", name: "pageup", meta: true }
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
if (code === 65) {
|
|
113
|
+
yield { type: "key", name: "pagedown", meta: true }
|
|
114
|
+
}
|
|
115
|
+
// ignore other mouse events
|
|
116
|
+
}
|
|
117
|
+
if (matchedAny) {
|
|
118
|
+
// Strip SGR mouse sequences so they never leak into the input as text
|
|
119
|
+
// Create a fresh regex since the global one was consumed by matchAll()
|
|
120
|
+
s = s.replace(/\x1b\x5b<(?<cb>\d+);(?<cx>\d+);(?<cy>\d+)(?<updown>[A-Za-z])/g, "")
|
|
121
|
+
if (s.length === 0) return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Legacy single SGR report path (when only one arrives)
|
|
125
|
+
const sgr = /^\x1b\[<(?<cb>\d+);(?<cx>\d+);(?<cy>\d+)(?<updown>[A-Za-z])$/.exec(s)
|
|
126
|
+
if (sgr?.groups) {
|
|
127
|
+
const code = parseInt(sgr.groups.cb ?? "0", 10)
|
|
128
|
+
if (code === 64) {
|
|
129
|
+
yield { type: "key", name: "pageup", meta: true }
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
if (code === 65) {
|
|
133
|
+
yield { type: "key", name: "pagedown", meta: true }
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Meta+Backspace (Option+Backspace on macOS terminals) — often ESC + DEL / BS
|
|
140
|
+
if (s === `${ESC}\x7f` || s === `${ESC}\b` || s === `${ESC}\x08`) {
|
|
141
|
+
yield { type: "key", name: "backspace", meta: true }
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Alt/Meta + Enter (many terminals send ESC + CR/LF)
|
|
146
|
+
if (s === `${ESC}\r` || s === `${ESC}\n`) {
|
|
147
|
+
yield { type: "key", name: "enter", meta: true }
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// modifyOtherKeys (xterm / some terminals): CSI 27;modifier;code~
|
|
152
|
+
const moo = /^\x1b\[27;(?<mod>\d+);(?<code>\d+)~$/.exec(s)
|
|
153
|
+
if (moo?.groups) {
|
|
154
|
+
const mod = parseInt(moo.groups.mod ?? "1", 10) - 1
|
|
155
|
+
const code = parseInt(moo.groups.code ?? "0", 10)
|
|
156
|
+
const shift = !!(mod & 1)
|
|
157
|
+
const alt = !!(mod & 2)
|
|
158
|
+
const ctrl = !!(mod & 4)
|
|
159
|
+
const meta = !!(mod & 8) || alt
|
|
160
|
+
const key = fromCharCode(code, { shift, ctrl, meta, source: "modify-other" })
|
|
161
|
+
if (key) {
|
|
162
|
+
yield key
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// CSI-u (modern modifyOtherKeys): CSI <code> ; <modifier> u
|
|
168
|
+
const csiu = /^\x1b\[(?<code>\d+);(?<mod>\d+)u$/.exec(s)
|
|
169
|
+
if (csiu?.groups) {
|
|
170
|
+
const mod = parseInt(csiu.groups.mod ?? "1", 10) - 1
|
|
171
|
+
const code = parseInt(csiu.groups.code ?? "0", 10)
|
|
172
|
+
const shift = !!(mod & 1)
|
|
173
|
+
const alt = !!(mod & 2)
|
|
174
|
+
const ctrl = !!(mod & 4)
|
|
175
|
+
const meta = !!(mod & 8) || alt
|
|
176
|
+
const key = fromCharCode(code, { shift, ctrl, meta, source: "csiu" })
|
|
177
|
+
if (key) {
|
|
178
|
+
yield key
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Escape (lone ESC) - handle after meta combinations
|
|
184
|
+
if (s === ESC) {
|
|
185
|
+
yield { type: "key", name: "escape" }
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Single printable
|
|
190
|
+
if (s.length === 1 && s !== ESC && s !== "\x7f") {
|
|
191
|
+
if (s === " ") {
|
|
192
|
+
yield { type: "key", name: "space", text: " " }
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
if (s >= " ") {
|
|
196
|
+
yield { type: "key", name: "char", text: s }
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Common escapes
|
|
202
|
+
switch (s) {
|
|
203
|
+
case `${ESC}[A`:
|
|
204
|
+
yield { type: "key", name: "up" }
|
|
205
|
+
return
|
|
206
|
+
case `${ESC}[B`:
|
|
207
|
+
yield { type: "key", name: "down" }
|
|
208
|
+
return
|
|
209
|
+
case `${ESC}[C`:
|
|
210
|
+
yield { type: "key", name: "right" }
|
|
211
|
+
return
|
|
212
|
+
case `${ESC}[D`:
|
|
213
|
+
yield { type: "key", name: "left" }
|
|
214
|
+
return
|
|
215
|
+
case `${ESC}[1;3C`:
|
|
216
|
+
yield { type: "key", name: "right", meta: true }
|
|
217
|
+
return
|
|
218
|
+
case `${ESC}[1;3D`:
|
|
219
|
+
yield { type: "key", name: "left", meta: true }
|
|
220
|
+
return
|
|
221
|
+
case `${ESC}OP`: // F1 (ignored)
|
|
222
|
+
return
|
|
223
|
+
case `${ESC}[H`:
|
|
224
|
+
yield { type: "key", name: "home" }
|
|
225
|
+
return
|
|
226
|
+
case `${ESC}[F`:
|
|
227
|
+
yield { type: "key", name: "end" }
|
|
228
|
+
return
|
|
229
|
+
case `${ESC}[5~`:
|
|
230
|
+
yield { type: "key", name: "pageup" }
|
|
231
|
+
return
|
|
232
|
+
case `${ESC}[6~`:
|
|
233
|
+
yield { type: "key", name: "pagedown" }
|
|
234
|
+
return
|
|
235
|
+
case `${ESC}[2~`:
|
|
236
|
+
yield { type: "key", name: "insert" }
|
|
237
|
+
return
|
|
238
|
+
case `${ESC}[3~`:
|
|
239
|
+
yield { type: "key", name: "delete" }
|
|
240
|
+
return
|
|
241
|
+
case `${ESC}[3;3~`: // Meta+Delete (best-effort for some terminals)
|
|
242
|
+
yield { type: "key", name: "delete", meta: true }
|
|
243
|
+
return
|
|
244
|
+
case `${ESC}[Z`:
|
|
245
|
+
yield { type: "key", name: "shift-tab", shift: true }
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Alt/Meta + char (ESC <char>)
|
|
250
|
+
if (s.length === 2 && s[0] === ESC && s[1] && s[1] >= " ") {
|
|
251
|
+
const ch = s[1]
|
|
252
|
+
if (ch === " ") {
|
|
253
|
+
yield { type: "key", name: "space", text: " ", meta: true }
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
yield { type: "key", name: "char", text: ch, meta: true }
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Fallback: emit printable chars individually
|
|
261
|
+
for (const ch of s) {
|
|
262
|
+
if (ch >= " ") yield { type: "key", name: "char", text: ch }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function fromCharCode(
|
|
267
|
+
code: number,
|
|
268
|
+
mods: { shift?: boolean; ctrl?: boolean; meta?: boolean; source: KeyMsg["source"] },
|
|
269
|
+
): KeyMsg | null {
|
|
270
|
+
const { shift, ctrl, meta, source } = mods
|
|
271
|
+
const common: Omit<KeyMsg, "name"> = {
|
|
272
|
+
type: "key",
|
|
273
|
+
ctrl,
|
|
274
|
+
meta,
|
|
275
|
+
shift,
|
|
276
|
+
phase: "press",
|
|
277
|
+
source,
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
switch (code) {
|
|
281
|
+
case 13:
|
|
282
|
+
return { ...common, name: "enter" }
|
|
283
|
+
case 27:
|
|
284
|
+
return { ...common, name: "escape" }
|
|
285
|
+
case 9:
|
|
286
|
+
return { ...common, name: "tab" }
|
|
287
|
+
case 32:
|
|
288
|
+
return { ...common, name: "space", text: " " }
|
|
289
|
+
case 127:
|
|
290
|
+
case 8:
|
|
291
|
+
return { ...common, name: "backspace" }
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (code >= 0 && code < 32) {
|
|
295
|
+
// Control character -> map to letter when possible
|
|
296
|
+
const letter = String.fromCharCode(code + 96)
|
|
297
|
+
return { ...common, name: "char", text: letter, ctrl: true }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const ch = String.fromCharCode(code)
|
|
301
|
+
return { ...common, name: "char", text: ch }
|
|
302
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// linearStack.ts — SwiftUI-like linear stacks with flexible children
|
|
2
|
+
|
|
3
|
+
export type Axis = "horizontal" | "vertical"
|
|
4
|
+
export type Size = { w: number; h: number }
|
|
5
|
+
export type Rect = { x: number; y: number; w: number; h: number }
|
|
6
|
+
export type HAlign = "leading" | "center" | "trailing"
|
|
7
|
+
export type VAlign = "top" | "center" | "bottom"
|
|
8
|
+
|
|
9
|
+
/** flexBase[i] = null for fixed, or the child's base main-size for flexible. */
|
|
10
|
+
export function measureLinear(
|
|
11
|
+
axis: Axis,
|
|
12
|
+
maxW: number,
|
|
13
|
+
maxH: number,
|
|
14
|
+
spacing: number,
|
|
15
|
+
sizes: Size[],
|
|
16
|
+
flexBase: (number | null)[],
|
|
17
|
+
): Size {
|
|
18
|
+
const n = sizes.length
|
|
19
|
+
const gaps = Math.max(0, n - 1) * Math.max(0, spacing)
|
|
20
|
+
|
|
21
|
+
let mainSum = 0
|
|
22
|
+
let crossMax = 0
|
|
23
|
+
let hasFlex = false
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < n; i++) {
|
|
26
|
+
const s = sizes[i]
|
|
27
|
+
if (!s) continue
|
|
28
|
+
const main = axis === "horizontal" ? s.w : s.h
|
|
29
|
+
const cross = axis === "horizontal" ? s.h : s.w
|
|
30
|
+
crossMax = Math.max(crossMax, cross)
|
|
31
|
+
hasFlex ||= flexBase[i] != null
|
|
32
|
+
mainSum += flexBase[i] ?? main
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const maxMain = axis === "horizontal" ? maxW : maxH
|
|
36
|
+
const totalMain = hasFlex ? maxMain : Math.min(maxMain, mainSum + gaps)
|
|
37
|
+
|
|
38
|
+
return axis === "horizontal"
|
|
39
|
+
? { w: Math.min(maxW, totalMain), h: Math.min(maxH, crossMax) }
|
|
40
|
+
: { w: Math.min(maxW, crossMax), h: Math.min(maxH, totalMain) }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function layoutLinear(
|
|
44
|
+
axis: Axis,
|
|
45
|
+
rect: Rect,
|
|
46
|
+
spacing: number,
|
|
47
|
+
crossAlign: HAlign | VAlign,
|
|
48
|
+
sizes: Size[],
|
|
49
|
+
flexBase: (number | null)[],
|
|
50
|
+
): Rect[] {
|
|
51
|
+
const n = sizes.length
|
|
52
|
+
const out: Rect[] = new Array(n)
|
|
53
|
+
const gaps = Math.max(0, n - 1) * Math.max(0, spacing)
|
|
54
|
+
|
|
55
|
+
const mainAvail = axis === "horizontal" ? rect.w : rect.h
|
|
56
|
+
const crossAvail = axis === "horizontal" ? rect.h : rect.w
|
|
57
|
+
|
|
58
|
+
let baseSum = 0
|
|
59
|
+
let flexCount = 0
|
|
60
|
+
for (let i = 0; i < n; i++) {
|
|
61
|
+
if (flexBase[i] != null) {
|
|
62
|
+
baseSum += flexBase[i]!
|
|
63
|
+
flexCount++
|
|
64
|
+
} else {
|
|
65
|
+
const size = sizes[i]
|
|
66
|
+
if (size) {
|
|
67
|
+
baseSum += axis === "horizontal" ? size.w : size.h
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const leftover = Math.max(0, mainAvail - baseSum - gaps)
|
|
73
|
+
const per = flexCount > 0 ? Math.floor(leftover / flexCount) : 0
|
|
74
|
+
let rem = flexCount > 0 ? leftover - per * flexCount : 0
|
|
75
|
+
|
|
76
|
+
const crossOffset = (childCross: number) => {
|
|
77
|
+
if (crossAlign === "center") return Math.floor((crossAvail - childCross) / 2)
|
|
78
|
+
if ((crossAlign === "trailing" && axis === "vertical") || (crossAlign === "bottom" && axis === "horizontal"))
|
|
79
|
+
return Math.max(0, crossAvail - childCross)
|
|
80
|
+
return 0
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let pos = 0
|
|
84
|
+
for (let i = 0; i < n; i++) {
|
|
85
|
+
const s = sizes[i]
|
|
86
|
+
if (!s) continue
|
|
87
|
+
const baseMain = flexBase[i] ?? (axis === "horizontal" ? s.w : s.h)
|
|
88
|
+
const flexExtra = flexBase[i] != null ? per + (rem > 0 ? (rem--, 1) : 0) : 0
|
|
89
|
+
const wantMain = baseMain + flexExtra
|
|
90
|
+
const childCross = Math.min(crossAvail, axis === "horizontal" ? s.h : s.w)
|
|
91
|
+
const remaining = Math.max(0, mainAvail - pos)
|
|
92
|
+
const takeMain = Math.min(wantMain, remaining)
|
|
93
|
+
const cs = crossOffset(childCross)
|
|
94
|
+
|
|
95
|
+
out[i] =
|
|
96
|
+
axis === "horizontal"
|
|
97
|
+
? { x: rect.x + pos, y: rect.y + cs, w: Math.max(0, takeMain), h: Math.max(0, childCross) }
|
|
98
|
+
: { x: rect.x + cs, y: rect.y + pos, w: Math.max(0, childCross), h: Math.max(0, takeMain) }
|
|
99
|
+
|
|
100
|
+
pos += takeMain
|
|
101
|
+
if (i < n - 1) pos += spacing
|
|
102
|
+
|
|
103
|
+
if (pos >= mainAvail) {
|
|
104
|
+
for (let j = i + 1; j < n; j++) {
|
|
105
|
+
out[j] =
|
|
106
|
+
axis === "horizontal"
|
|
107
|
+
? { x: rect.x + mainAvail, y: rect.y, w: 0, h: 0 }
|
|
108
|
+
: { x: rect.x, y: rect.y + mainAvail, w: 0, h: 0 }
|
|
109
|
+
}
|
|
110
|
+
break
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return out
|
|
115
|
+
}
|