[READ-ONLY] Mirror of https://github.com/bombshell-dev/tty. Platform independent 2D layout engine for terminal applications based on Clay
5

Configure Feed

Select the types of activity you want to include in your feed.

♻️ use string literals for floating options

Ryan Rauh (May 10, 2026, 7:07 AM EDT) ea34f5bd bd2dcc9d

+148 -66
+69 -34
ops.ts
··· 171 171 o += 4; 172 172 view.setUint32( 173 173 o, 174 - (f.attachTo ?? 0) | 175 - ((f.attachPoints?.element ?? 0) << 8) | 176 - ((f.attachPoints?.parent ?? 0) << 16) | 177 - ((f.pointerCaptureMode ?? 0) << 24), 174 + encodeAttachTo(f.attachTo) | 175 + (encodeAttachPoint(f.attachPoints?.element) << 8) | 176 + (encodeAttachPoint(f.attachPoints?.parent) << 16) | 177 + (encodePointerCaptureMode(f.pointerCaptureMode) << 24), 178 178 true, 179 179 ); 180 180 o += 4; 181 181 view.setUint32( 182 182 o, 183 - (f.clipTo ?? 0) | (((f.zIndex ?? 0) & 0xffff) << 8), 183 + encodeClipTo(f.clipTo) | (((f.zIndex ?? 0) & 0xffff) << 8), 184 184 true, 185 185 ); 186 186 o += 4; ··· 278 278 y?: number; 279 279 expand?: { width?: number; height?: number }; 280 280 parent?: number; 281 - attachTo?: number; 282 - attachPoints?: { element?: number; parent?: number }; 283 - pointerCaptureMode?: number; 284 - clipTo?: number; 281 + attachTo?: AttachTo; 282 + attachPoints?: { element?: AttachPoint; parent?: AttachPoint }; 283 + pointerCaptureMode?: PointerCaptureMode; 284 + clipTo?: ClipTo; 285 285 zIndex?: number; 286 286 }; 287 287 } 288 288 289 - export const ATTACH_POINT = { 290 - LEFT_TOP: 0, 291 - LEFT_CENTER: 1, 292 - LEFT_BOTTOM: 2, 293 - CENTER_TOP: 3, 294 - CENTER_CENTER: 4, 295 - CENTER_BOTTOM: 5, 296 - RIGHT_TOP: 6, 297 - RIGHT_CENTER: 7, 298 - RIGHT_BOTTOM: 8, 299 - } as const; 289 + export type AttachPoint = 290 + | "left-top" 291 + | "left-center" 292 + | "left-bottom" 293 + | "center-top" 294 + | "center-center" 295 + | "center-bottom" 296 + | "right-top" 297 + | "right-center" 298 + | "right-bottom"; 299 + 300 + export type AttachTo = "none" | "parent" | "element" | "root"; 301 + 302 + export type PointerCaptureMode = "capture" | "passthrough"; 303 + 304 + export type ClipTo = "none" | "attached-parent"; 305 + 306 + const ATTACH_POINT: Record<AttachPoint, number> = { 307 + "left-top": 0, 308 + "left-center": 1, 309 + "left-bottom": 2, 310 + "center-top": 3, 311 + "center-center": 4, 312 + "center-bottom": 5, 313 + "right-top": 6, 314 + "right-center": 7, 315 + "right-bottom": 8, 316 + }; 317 + 318 + const ATTACH_TO: Record<AttachTo, number> = { 319 + none: 0, 320 + parent: 1, 321 + element: 2, 322 + root: 3, 323 + }; 324 + 325 + const POINTER_CAPTURE_MODE: Record<PointerCaptureMode, number> = { 326 + capture: 0, 327 + passthrough: 1, 328 + }; 300 329 301 - export const ATTACH_TO = { 302 - NONE: 0, 303 - PARENT: 1, 304 - ELEMENT_WITH_ID: 2, 305 - ROOT: 3, 306 - } as const; 330 + const CLIP_TO: Record<ClipTo, number> = { 331 + none: 0, 332 + "attached-parent": 1, 333 + }; 307 334 308 - export const POINTER_CAPTURE_MODE = { 309 - CAPTURE: 0, 310 - PASSTHROUGH: 1, 311 - } as const; 335 + function encodeAttachPoint(value: AttachPoint | undefined): number { 336 + return value === undefined ? 0 : ATTACH_POINT[value]; 337 + } 338 + 339 + function encodeAttachTo(value: AttachTo | undefined): number { 340 + return value === undefined ? 0 : ATTACH_TO[value]; 341 + } 342 + 343 + function encodePointerCaptureMode( 344 + value: PointerCaptureMode | undefined, 345 + ): number { 346 + return value === undefined ? 0 : POINTER_CAPTURE_MODE[value]; 347 + } 312 348 313 - export const CLIP_TO = { 314 - NONE: 0, 315 - ATTACHED_PARENT: 1, 316 - } as const; 349 + function encodeClipTo(value: ClipTo | undefined): number { 350 + return value === undefined ? 0 : CLIP_TO[value]; 351 + } 317 352 318 353 export interface Text { 319 354 directive: typeof OP_TEXT;
+32 -10
specs/renderer-spec.md
··· 622 622 y?: number; 623 623 expand?: { width?: number; height?: number }; 624 624 parent?: number; 625 - attachTo?: number; 625 + attachTo?: "none" | "parent" | "element" | "root"; 626 626 attachPoints?: { 627 - element?: number; 628 - parent?: number; 627 + element?: 628 + | "left-top" 629 + | "left-center" 630 + | "left-bottom" 631 + | "center-top" 632 + | "center-center" 633 + | "center-bottom" 634 + | "right-top" 635 + | "right-center" 636 + | "right-bottom"; 637 + parent?: 638 + | "left-top" 639 + | "left-center" 640 + | "left-bottom" 641 + | "center-top" 642 + | "center-center" 643 + | "center-bottom" 644 + | "right-top" 645 + | "right-center" 646 + | "right-bottom"; 629 647 }; 630 - pointerCaptureMode?: number; 631 - clipTo?: number; 648 + pointerCaptureMode?: "capture" | "passthrough"; 649 + clipTo?: "none" | "attached-parent"; 632 650 zIndex?: number; 633 651 } 634 652 ``` 635 653 636 - This shape extends the earlier floating surface in two ways. First, 637 - `attachPoints` is structured as separate element and parent anchor values 638 - instead of a single packed enum. Second, the surface exposes additional Clay 639 - floating controls that were previously unavailable at the TypeScript layer: 640 - `expand`, `pointerCaptureMode`, and `clipTo`. 654 + The `floating` object configures Clay floating layout behavior. `x` and `y` 655 + provide the floating offset. `expand` expands the floating bounds. `parent` 656 + identifies the target element when `attachTo` is `"element"`. `attachTo` selects 657 + whether the element is attached to no target, its parent, an element, or the 658 + layout root. `attachPoints.element` describes the anchor on the floating 659 + element, and `attachPoints.parent` describes the anchor on the attached target. 660 + `pointerCaptureMode` controls whether the floating element captures pointer 661 + input or lets it pass through, `clipTo` controls inherited clipping, and 662 + `zIndex` controls floating order. 641 663 642 664 The `text()` constructor currently accepts: `color`, `fontSize`, 643 665 `letterSpacing`, `lineHeight`, and attribute flags (`bold`, `italic`,
+4 -13
test/term.test.ts
··· 1 1 import { beforeEach, describe, expect, it } from "./suite.ts"; 2 2 import { createTerm, type Term } from "../term.ts"; 3 - import { 4 - ATTACH_POINT, 5 - ATTACH_TO, 6 - close, 7 - fixed, 8 - grow, 9 - open, 10 - rgba, 11 - text, 12 - } from "../ops.ts"; 3 + import { close, fixed, grow, open, rgba, text } from "../ops.ts"; 13 4 import { print } from "./print.ts"; 14 5 15 6 const decode = (bytes: Uint8Array) => new TextDecoder().decode(bytes); ··· 224 215 floating: { 225 216 x: 3, 226 217 y: 1, 227 - attachTo: ATTACH_TO.ROOT, 218 + attachTo: "root", 228 219 attachPoints: { 229 - element: ATTACH_POINT.CENTER_CENTER, 230 - parent: ATTACH_POINT.CENTER_CENTER, 220 + element: "center-center", 221 + parent: "center-center", 231 222 }, 232 223 }, 233 224 }),
+9 -4
test/validate.test.ts
··· 83 83 expect(validate([ 84 84 open("x", { 85 85 floating: { 86 - attachPoints: { element: 4, parent: 4 }, 86 + attachPoints: { element: "center-center", parent: "center-center" }, 87 87 }, 88 88 }), 89 89 close(), ··· 95 95 open("x", { 96 96 floating: { 97 97 expand: { width: 2, height: 3 }, 98 - pointerCaptureMode: 1, 99 - clipTo: 1, 98 + pointerCaptureMode: "passthrough", 99 + clipTo: "attached-parent", 100 100 zIndex: 1024, 101 101 }, 102 102 }), ··· 104 104 ])).toBe(true); 105 105 }); 106 106 107 - it("rejects numeric floating attachPoints legacy shape", () => { 107 + it("rejects numeric floating enum values", () => { 108 + expect(validate([ 109 + // deno-lint-ignore no-explicit-any 110 + open("x", { floating: { attachTo: 3 as any } }), 111 + close(), 112 + ])).toBe(false); 108 113 expect(validate([ 109 114 // deno-lint-ignore no-explicit-any 110 115 open("x", { floating: { attachPoints: 4 as any } }),
+34 -5
validate.ts
··· 80 80 vertical: Type.Optional(Type.Boolean()), 81 81 }); 82 82 83 + const AttachPoint = Type.Union([ 84 + Type.Literal("left-top"), 85 + Type.Literal("left-center"), 86 + Type.Literal("left-bottom"), 87 + Type.Literal("center-top"), 88 + Type.Literal("center-center"), 89 + Type.Literal("center-bottom"), 90 + Type.Literal("right-top"), 91 + Type.Literal("right-center"), 92 + Type.Literal("right-bottom"), 93 + ]); 94 + 95 + const AttachTo = Type.Union([ 96 + Type.Literal("none"), 97 + Type.Literal("parent"), 98 + Type.Literal("element"), 99 + Type.Literal("root"), 100 + ]); 101 + 102 + const PointerCaptureMode = Type.Union([ 103 + Type.Literal("capture"), 104 + Type.Literal("passthrough"), 105 + ]); 106 + 107 + const ClipTo = Type.Union([ 108 + Type.Literal("none"), 109 + Type.Literal("attached-parent"), 110 + ]); 111 + 83 112 const Floating = Type.Object({ 84 113 x: Type.Optional(Type.Number()), 85 114 y: Type.Optional(Type.Number()), ··· 88 117 height: Type.Optional(Type.Number()), 89 118 })), 90 119 parent: Type.Optional(Type.Integer({ minimum: 0 })), 91 - attachTo: Type.Optional(u8), 120 + attachTo: Type.Optional(AttachTo), 92 121 attachPoints: Type.Optional(Type.Object({ 93 - element: Type.Optional(u8), 94 - parent: Type.Optional(u8), 122 + element: Type.Optional(AttachPoint), 123 + parent: Type.Optional(AttachPoint), 95 124 })), 96 - pointerCaptureMode: Type.Optional(u8), 97 - clipTo: Type.Optional(u8), 125 + pointerCaptureMode: Type.Optional(PointerCaptureMode), 126 + clipTo: Type.Optional(ClipTo), 98 127 zIndex: Type.Optional(u16), 99 128 }); 100 129