@rlabs-inc/tui 0.1.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 (44) hide show
  1. package/README.md +141 -0
  2. package/index.ts +45 -0
  3. package/package.json +59 -0
  4. package/src/api/index.ts +7 -0
  5. package/src/api/mount.ts +230 -0
  6. package/src/engine/arrays/core.ts +60 -0
  7. package/src/engine/arrays/dimensions.ts +68 -0
  8. package/src/engine/arrays/index.ts +166 -0
  9. package/src/engine/arrays/interaction.ts +112 -0
  10. package/src/engine/arrays/layout.ts +175 -0
  11. package/src/engine/arrays/spacing.ts +100 -0
  12. package/src/engine/arrays/text.ts +55 -0
  13. package/src/engine/arrays/visual.ts +140 -0
  14. package/src/engine/index.ts +25 -0
  15. package/src/engine/inheritance.ts +138 -0
  16. package/src/engine/registry.ts +180 -0
  17. package/src/pipeline/frameBuffer.ts +473 -0
  18. package/src/pipeline/layout/index.ts +105 -0
  19. package/src/pipeline/layout/titan-engine.ts +798 -0
  20. package/src/pipeline/layout/types.ts +194 -0
  21. package/src/pipeline/layout/utils/hierarchy.ts +202 -0
  22. package/src/pipeline/layout/utils/math.ts +134 -0
  23. package/src/pipeline/layout/utils/text-measure.ts +160 -0
  24. package/src/pipeline/layout.ts +30 -0
  25. package/src/primitives/box.ts +312 -0
  26. package/src/primitives/index.ts +12 -0
  27. package/src/primitives/text.ts +199 -0
  28. package/src/primitives/types.ts +222 -0
  29. package/src/primitives/utils.ts +37 -0
  30. package/src/renderer/ansi.ts +625 -0
  31. package/src/renderer/buffer.ts +667 -0
  32. package/src/renderer/index.ts +40 -0
  33. package/src/renderer/input.ts +518 -0
  34. package/src/renderer/output.ts +451 -0
  35. package/src/state/cursor.ts +176 -0
  36. package/src/state/focus.ts +241 -0
  37. package/src/state/index.ts +43 -0
  38. package/src/state/keyboard.ts +771 -0
  39. package/src/state/mouse.ts +524 -0
  40. package/src/state/scroll.ts +341 -0
  41. package/src/state/theme.ts +687 -0
  42. package/src/types/color.ts +401 -0
  43. package/src/types/index.ts +316 -0
  44. package/src/utils/text.ts +471 -0
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # TUI Framework
2
+
3
+ **The Terminal UI Framework for TypeScript/Bun**
4
+
5
+ A blazing-fast, fine-grained reactive terminal UI framework with complete flexbox layout and zero CPU when idle.
6
+
7
+ ## Performance
8
+
9
+ | Metric | Value |
10
+ |--------|-------|
11
+ | Single update latency | 0.028ms |
12
+ | Updates per second | 41,000+ |
13
+ | Layout (10K components) | 0.66ms |
14
+ | Memory per component | ~500 bytes |
15
+ | Maximum components tested | 1,000,000 |
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ bun add tui @rlabs-inc/signals
21
+ ```
22
+
23
+ ```typescript
24
+ import { mount, box, text } from 'tui'
25
+ import { signal } from '@rlabs-inc/signals'
26
+
27
+ // Create reactive state
28
+ const count = signal(0)
29
+
30
+ // Build your UI
31
+ box({ width: 40, height: 10, children: () => {
32
+ text({ content: `Count: ${count.value}` })
33
+ }})
34
+
35
+ // Mount to terminal
36
+ mount()
37
+
38
+ // Update anywhere - UI reacts automatically
39
+ setInterval(() => count.value++, 1000)
40
+ ```
41
+
42
+ ## Features
43
+
44
+ ### Complete Flexbox Layout (TITAN Engine)
45
+ - Direction: row, column, row-reverse, column-reverse
46
+ - Wrap: nowrap, wrap, wrap-reverse
47
+ - Justify: flex-start, center, flex-end, space-between, space-around, space-evenly
48
+ - Align: stretch, flex-start, center, flex-end
49
+ - Grow, shrink, basis, gap
50
+ - Min/max constraints
51
+ - Percentage dimensions
52
+
53
+ ### Fine-Grained Reactivity
54
+ - Signals for primitive values
55
+ - Derived for computed values
56
+ - Effects for side effects
57
+ - Bindings for prop connections
58
+ - Zero reconciliation - reactivity IS the update mechanism
59
+
60
+ ### Svelte-like .tui Files (Compiler)
61
+ ```html
62
+ <script>
63
+ const name = signal('World')
64
+ </script>
65
+
66
+ <box width={40} height={3}>
67
+ <text content={`Hello, ${name.value}!`} />
68
+ </box>
69
+ ```
70
+
71
+ ### State Modules
72
+ - **keyboard** - Key events, shortcuts, input buffering
73
+ - **mouse** - Click, hover, drag, wheel events
74
+ - **focus** - Tab navigation, focus trapping
75
+ - **scroll** - Scroll state and navigation
76
+ - **theme** - 14 color variants, theming
77
+
78
+ ## Architecture
79
+
80
+ ```
81
+ User Signals → bind() → Parallel Arrays → layoutDerived → frameBufferDerived → render
82
+ ```
83
+
84
+ ### Why So Fast?
85
+
86
+ 1. **Parallel Arrays** - ECS-like data layout for cache efficiency
87
+ 2. **No Reconciliation** - Fine-grained reactivity replaces diffing
88
+ 3. **Single Effect** - One render effect for entire app
89
+ 4. **TITAN Layout** - O(n) flexbox in pure TypeScript
90
+ 5. **Zero CPU Idle** - Only updates when signals change
91
+
92
+ ## Examples
93
+
94
+ ```bash
95
+ # Run examples
96
+ bun run examples/hello.ts
97
+ bun run examples/showcase.ts
98
+
99
+ # Run tests
100
+ bun run examples/tests/01-box-basics.ts
101
+ bun run examples/tests/03-layout-flex.ts
102
+ ```
103
+
104
+ ## Documentation
105
+
106
+ - [CLAUDE.md](./CLAUDE.md) - Development guide and philosophy
107
+ - [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) - Deep dive into the architecture
108
+ - [docs/API.md](./docs/API.md) - Complete API reference
109
+ - [docs/BIND_PRIMITIVE.md](./docs/BIND_PRIMITIVE.md) - Understanding reactivity
110
+
111
+ ## Primitives
112
+
113
+ | Primitive | Status | Description |
114
+ |-----------|--------|-------------|
115
+ | `box` | Complete | Container with flexbox layout |
116
+ | `text` | Complete | Text display with styling |
117
+ | `input` | Planned | Text input field |
118
+ | `select` | Planned | Dropdown selection |
119
+ | `progress` | Planned | Progress bar |
120
+
121
+ ## Test Coverage
122
+
123
+ - **130 tests** passing
124
+ - TITAN layout engine: 48 tests
125
+ - Parallel arrays: 17 tests
126
+ - Focus manager: 29 tests
127
+ - Compiler: 36 tests (unit + integration)
128
+
129
+ ## Requirements
130
+
131
+ - Bun 1.0+ (for runtime and build)
132
+ - Node.js 18+ (for compatibility)
133
+ - Terminal with ANSI support
134
+
135
+ ## License
136
+
137
+ MIT
138
+
139
+ ---
140
+
141
+ Built with love by Rusty & Watson in São Paulo, Brazil.
package/index.ts ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * TUI Framework
3
+ *
4
+ * The definitive TypeScript/Bun terminal UI framework.
5
+ * Fine-grained reactivity, parallel arrays, zero fixed-FPS rendering.
6
+ */
7
+
8
+ // API - User-facing
9
+ export { mount } from './src/api'
10
+
11
+ // Primitives - UI building blocks
12
+ export { box, text } from './src/primitives'
13
+ export type { BoxProps, TextProps, Cleanup } from './src/primitives'
14
+
15
+ // State modules - Input handling
16
+ export { keyboard } from './src/state/keyboard'
17
+ export { focusManager, focusedIndex } from './src/state/focus'
18
+ export { scroll } from './src/state/scroll'
19
+ export { mouse, hitGrid } from './src/state/mouse'
20
+
21
+ // Types
22
+ export * from './src/types'
23
+ export * from './src/types/color'
24
+
25
+ // Signals re-export for convenience
26
+ export { signal, state, derived, effect, bind, signals } from '@rlabs-inc/signals'
27
+
28
+ // Theme
29
+ export {
30
+ theme,
31
+ themes,
32
+ setTheme,
33
+ resolveColor,
34
+ resolvedTheme,
35
+ t,
36
+ getVariantStyle,
37
+ variantStyle,
38
+ } from './src/state/theme'
39
+ export type { Variant, VariantStyle, ThemeColor } from './src/state/theme'
40
+
41
+ // Renderer (advanced)
42
+ export * from './src/renderer'
43
+
44
+ // Engine (advanced)
45
+ export * from './src/engine'
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@rlabs-inc/tui",
3
+ "version": "0.1.0",
4
+ "description": "The Terminal UI Framework for TypeScript/Bun - Blazing-fast, fine-grained reactive terminal UI with complete flexbox layout",
5
+ "module": "index.ts",
6
+ "main": "index.ts",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": "./index.ts",
10
+ "./primitives": "./src/primitives/index.ts",
11
+ "./state": "./src/state/index.ts"
12
+ },
13
+ "files": [
14
+ "index.ts",
15
+ "src"
16
+ ],
17
+ "scripts": {
18
+ "dev": "bun run examples/hello.ts",
19
+ "test": "bun test",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "keywords": [
23
+ "tui",
24
+ "terminal",
25
+ "ui",
26
+ "framework",
27
+ "reactive",
28
+ "signals",
29
+ "flexbox",
30
+ "bun",
31
+ "typescript",
32
+ "cli"
33
+ ],
34
+ "author": "Rusty & Watson",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/rlabs-inc/tui.git"
39
+ },
40
+ "homepage": "https://github.com/rlabs-inc/tui",
41
+ "bugs": {
42
+ "url": "https://github.com/rlabs-inc/tui/issues"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "engines": {
48
+ "node": ">=18"
49
+ },
50
+ "devDependencies": {
51
+ "@types/bun": "latest"
52
+ },
53
+ "peerDependencies": {
54
+ "typescript": "^5.0.0"
55
+ },
56
+ "dependencies": {
57
+ "@rlabs-inc/signals": "^1.6.0"
58
+ }
59
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * TUI Framework - API
3
+ *
4
+ * User-facing API for mounting applications.
5
+ */
6
+
7
+ export { mount } from './mount'
@@ -0,0 +1,230 @@
1
+ /**
2
+ * TUI Framework - Mount API
3
+ *
4
+ * The entry point for TUI applications.
5
+ * Sets up terminal, creates the render effect, handles cleanup.
6
+ *
7
+ * Usage:
8
+ * ```ts
9
+ * const cleanup = await mount(() => {
10
+ * box({
11
+ * width: 40,
12
+ * height: 10,
13
+ * border: 1,
14
+ * children: () => {
15
+ * text({ content: 'Hello, TUI!' })
16
+ * }
17
+ * })
18
+ * })
19
+ *
20
+ * // Later...
21
+ * await cleanup()
22
+ * ```
23
+ */
24
+
25
+ import { effect } from '@rlabs-inc/signals'
26
+ import type { MountOptions, ResizeEvent } from '../types'
27
+ import {
28
+ DiffRenderer,
29
+ InlineRenderer,
30
+ } from '../renderer/output'
31
+ import * as ansi from '../renderer/ansi'
32
+ import { frameBufferDerived } from '../pipeline/frameBuffer'
33
+ import { layoutDerived, terminalWidth, terminalHeight, updateTerminalSize, renderMode } from '../pipeline/layout'
34
+ import { resetRegistry } from '../engine/registry'
35
+ import { hitGrid, clearHitGrid } from '../state/mouse'
36
+ import { keyboard } from '../state/keyboard'
37
+
38
+ // =============================================================================
39
+ // MOUNT
40
+ // =============================================================================
41
+
42
+ /**
43
+ * Mount a TUI application.
44
+ *
45
+ * @param root - Function that creates the component tree
46
+ * @param options - Mount options (mode, mouse, keyboard)
47
+ * @returns Cleanup function to unmount
48
+ */
49
+ export async function mount(
50
+ root: () => void,
51
+ options: MountOptions = {}
52
+ ): Promise<() => Promise<void>> {
53
+ const {
54
+ mode = 'fullscreen',
55
+ mouse = true,
56
+ kittyKeyboard = true,
57
+ } = options
58
+
59
+ // Set render mode signal BEFORE creating components
60
+ // This affects how layout and frameBuffer compute dimensions
61
+ renderMode.value = mode
62
+
63
+ // Create renderer based on mode
64
+ // Fullscreen uses DiffRenderer (absolute positioning)
65
+ // Inline uses InlineRenderer (eraseLines + sequential write)
66
+ // Append uses DiffRenderer for now (TODO: may need AppendRenderer)
67
+ const diffRenderer = new DiffRenderer()
68
+ const inlineRenderer = new InlineRenderer()
69
+
70
+ // Mode-specific state
71
+ let previousHeight = 0 // For append mode: track last rendered height
72
+ let isFirstRender = true
73
+
74
+ // Resize handlers (keyboard module handles key/mouse)
75
+ const resizeHandlers: Set<(event: ResizeEvent) => void> = new Set()
76
+
77
+ // Setup terminal
78
+ const setupSequence: string[] = []
79
+
80
+ if (mode === 'fullscreen') {
81
+ setupSequence.push(ansi.enterAlternativeScreen)
82
+ setupSequence.push(ansi.clearScreen)
83
+ setupSequence.push(ansi.cursorTo(1, 1)) // Cursor home
84
+ }
85
+ // Inline/Append: no special setup needed
86
+ // InlineRenderer handles erasing previous content
87
+
88
+ setupSequence.push(ansi.hideCursor)
89
+
90
+ if (mouse) {
91
+ setupSequence.push(ansi.enableMouse)
92
+ }
93
+
94
+ if (kittyKeyboard) {
95
+ setupSequence.push(ansi.enableKittyKeyboard)
96
+ }
97
+
98
+ setupSequence.push(ansi.enableBracketedPaste)
99
+ setupSequence.push(ansi.enableFocusReporting)
100
+
101
+ // Write setup sequence
102
+ process.stdout.write(setupSequence.join(''))
103
+
104
+ // Initialize keyboard module (handles stdin, raw mode, input parsing)
105
+ // Pass cleanup callback for proper shutdown on Ctrl+C
106
+ keyboard.initialize()
107
+
108
+ // Handle resize
109
+ const handleResize = () => {
110
+ updateTerminalSize()
111
+
112
+ // For fullscreen, invalidate diff renderer to force full redraw
113
+ if (mode === 'fullscreen') {
114
+ diffRenderer.invalidate()
115
+ }
116
+ // For inline/append, InlineRenderer always redraws (eraseLines approach)
117
+
118
+ for (const handler of resizeHandlers) {
119
+ handler({ width: terminalWidth.value, height: terminalHeight.value })
120
+ }
121
+ }
122
+ process.stdout.on('resize', handleResize)
123
+
124
+ // Initialize terminal size
125
+ updateTerminalSize()
126
+
127
+ // Create the component tree
128
+ root()
129
+
130
+ // THE ONE RENDER EFFECT
131
+ // This is where the magic happens - reactive rendering!
132
+ // Side effects (HitGrid) are applied HERE, not in the derived.
133
+ let stopEffect: (() => void) | null = null
134
+
135
+ stopEffect = effect(() => {
136
+ const start = Bun.nanoseconds()
137
+
138
+ // Time layout separately (reading .value triggers computation if needed)
139
+ // const layoutStart = Bun.nanoseconds()
140
+ // const _layout = layoutDerived.value
141
+ // const layoutNs = Bun.nanoseconds() - layoutStart
142
+
143
+ // Time buffer computation
144
+ const bufferStart = Bun.nanoseconds()
145
+ const { buffer, hitRegions, terminalSize } = frameBufferDerived.value
146
+ const bufferNs = Bun.nanoseconds() - bufferStart
147
+
148
+ // Apply hit regions to HitGrid (side effect happens in effect, not derived!)
149
+ if (hitGrid.width !== terminalSize.width || hitGrid.height !== terminalSize.height) {
150
+ hitGrid.resize(terminalSize.width, terminalSize.height)
151
+ } else {
152
+ clearHitGrid()
153
+ }
154
+ for (const region of hitRegions) {
155
+ hitGrid.fillRect(region.x, region.y, region.width, region.height, region.componentIndex)
156
+ }
157
+
158
+ // Time render to terminal
159
+ const renderStart = Bun.nanoseconds()
160
+ if (mode === 'fullscreen') {
161
+ diffRenderer.render(buffer)
162
+ } else if (mode === 'inline') {
163
+ inlineRenderer.render(buffer)
164
+ } else {
165
+ inlineRenderer.render(buffer)
166
+ }
167
+ const renderNs = Bun.nanoseconds() - renderStart
168
+
169
+ const totalNs = Bun.nanoseconds() - start
170
+
171
+ // Show all timing stats in window title
172
+ // const layoutMs = layoutNs / 1_000_000
173
+ const bufferMs = bufferNs / 1_000_000
174
+ const renderMs = renderNs / 1_000_000
175
+ const totalMs = totalNs / 1_000_000
176
+ // process.stdout.write(`\x1b]0;TUI | ${mode} | layout: ${layoutMs.toFixed(3)}ms | buffer: ${bufferMs.toFixed(3)}ms | render: ${renderMs.toFixed(3)}ms | total: ${totalMs.toFixed(3)}ms\x07`)
177
+ process.stdout.write(`\x1b]0;TUI | ${mode} | buffer: ${bufferMs.toFixed(3)}ms | render: ${renderMs.toFixed(3)}ms | total: ${totalMs.toFixed(3)}ms\x07`)
178
+ })
179
+
180
+ // Cleanup function
181
+ return async () => {
182
+ // Stop the render effect
183
+ if (stopEffect) {
184
+ stopEffect()
185
+ stopEffect = null
186
+ }
187
+
188
+ // Cleanup keyboard module (removes stdin listener, restores terminal)
189
+ keyboard.cleanup()
190
+
191
+ // Remove resize listener
192
+ process.stdout.removeListener('resize', handleResize)
193
+
194
+ // Restore terminal
195
+ const restoreSequence: string[] = []
196
+
197
+ restoreSequence.push(ansi.disableFocusReporting)
198
+ restoreSequence.push(ansi.disableBracketedPaste)
199
+
200
+ if (kittyKeyboard) {
201
+ restoreSequence.push(ansi.disableKittyKeyboard)
202
+ }
203
+
204
+ if (mouse) {
205
+ restoreSequence.push(ansi.disableMouse)
206
+ }
207
+
208
+ restoreSequence.push(ansi.showCursor)
209
+ restoreSequence.push(ansi.reset)
210
+
211
+ if (mode === 'fullscreen') {
212
+ restoreSequence.push(ansi.exitAlternativeScreen)
213
+ } else if (mode === 'inline' || mode === 'append') {
214
+ // For inline/append: content is already on screen, just reset and add newline
215
+ // InlineRenderer leaves cursor at end of content
216
+ restoreSequence.push('\n')
217
+ }
218
+
219
+ process.stdout.write(restoreSequence.join(''))
220
+
221
+ // Disable raw mode
222
+ if (process.stdin.isTTY) {
223
+ process.stdin.setRawMode(false)
224
+ }
225
+
226
+ // Reset registry for clean slate
227
+ resetRegistry()
228
+ }
229
+ }
230
+
@@ -0,0 +1,60 @@
1
+ /**
2
+ * TUI Framework - Core Arrays
3
+ *
4
+ * The most fundamental component arrays:
5
+ * - componentType: What kind of component (box, text, etc.)
6
+ * - parentIndex: Parent in hierarchy
7
+ * - visible: Is component rendered
8
+ *
9
+ * NOTE: Focus state (focusable, tabIndex) is in interaction.ts
10
+ *
11
+ * CRITICAL: Arrays storing Bindings must be regular arrays (NOT state!)
12
+ * state() proxies snapshot getter values, breaking reactivity.
13
+ * componentType is the exception - it stores values directly, not bindings.
14
+ */
15
+
16
+ import { bind, disconnectBinding, type Binding } from '@rlabs-inc/signals'
17
+ import { ComponentType } from '../../types'
18
+ import type { ComponentTypeValue } from '../../types'
19
+
20
+ /** Component type (box, text, input, etc.) - stores values directly */
21
+ export const componentType: ComponentTypeValue[] = []
22
+
23
+ /** Parent component index (-1 for root) */
24
+ export const parentIndex: Binding<number>[] = []
25
+
26
+ /** Is component visible (0/false = hidden, 1/true = visible) */
27
+ export const visible: Binding<number | boolean>[] = []
28
+
29
+ /** Component ID (for debugging and lookups) */
30
+ export const componentId: Binding<string>[] = []
31
+
32
+ /**
33
+ * Ensure array has capacity for the given index.
34
+ * Called by registry when allocating.
35
+ *
36
+ * LAZY BINDING: We push undefined here, not bindings.
37
+ * Primitives create bindings only for props they actually use.
38
+ * This reduces memory from ~70 bindings/component to ~5-10.
39
+ */
40
+ export function ensureCapacity(index: number): void {
41
+ while (componentType.length <= index) {
42
+ componentType.push(ComponentType.NONE)
43
+ parentIndex.push(undefined as any)
44
+ visible.push(undefined as any)
45
+ componentId.push(undefined as any)
46
+ }
47
+ }
48
+
49
+ /** Clear values at index (called when releasing) */
50
+ export function clearAtIndex(index: number): void {
51
+ if (index < componentType.length) {
52
+ componentType[index] = ComponentType.NONE
53
+ disconnectBinding(parentIndex[index])
54
+ disconnectBinding(visible[index])
55
+ disconnectBinding(componentId[index])
56
+ parentIndex[index] = undefined as any
57
+ visible[index] = undefined as any
58
+ componentId[index] = undefined as any
59
+ }
60
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * TUI Framework - Dimension Arrays
3
+ *
4
+ * Width, height, min/max constraints.
5
+ * These are INPUT values that components write.
6
+ * The layout derived READS these and RETURNS computed positions.
7
+ *
8
+ * CRITICAL: Use regular arrays (NOT state!) to preserve binding getters.
9
+ * state() proxies snapshot getter values, breaking reactivity.
10
+ *
11
+ * Supports both absolute and percentage dimensions:
12
+ * - number: Absolute value in cells (e.g., 50)
13
+ * - string: Percentage of parent (e.g., '50%', '100%')
14
+ *
15
+ * TITAN resolves percentages against parent computed sizes at layout time.
16
+ *
17
+ * Note: 0 means "auto" for width/height, "no constraint" for min/max.
18
+ */
19
+
20
+ import { bind, disconnectBinding, type Binding } from '@rlabs-inc/signals'
21
+ import type { Dimension } from '../../types'
22
+
23
+ /** Requested width (0 = auto, '100%' = full parent width) */
24
+ export const width: Binding<Dimension>[] = []
25
+
26
+ /** Requested height (0 = auto, '100%' = full parent height) */
27
+ export const height: Binding<Dimension>[] = []
28
+
29
+ /** Minimum width constraint */
30
+ export const minWidth: Binding<Dimension>[] = []
31
+
32
+ /** Minimum height constraint */
33
+ export const minHeight: Binding<Dimension>[] = []
34
+
35
+ /** Maximum width constraint (0 = no max) */
36
+ export const maxWidth: Binding<Dimension>[] = []
37
+
38
+ /** Maximum height constraint (0 = no max) */
39
+ export const maxHeight: Binding<Dimension>[] = []
40
+
41
+ /** LAZY BINDING: Push undefined, primitives create bindings for used props only */
42
+ export function ensureCapacity(index: number): void {
43
+ while (width.length <= index) {
44
+ width.push(undefined as any)
45
+ height.push(undefined as any)
46
+ minWidth.push(undefined as any)
47
+ minHeight.push(undefined as any)
48
+ maxWidth.push(undefined as any)
49
+ maxHeight.push(undefined as any)
50
+ }
51
+ }
52
+
53
+ export function clearAtIndex(index: number): void {
54
+ if (index < width.length) {
55
+ disconnectBinding(width[index])
56
+ disconnectBinding(height[index])
57
+ disconnectBinding(minWidth[index])
58
+ disconnectBinding(minHeight[index])
59
+ disconnectBinding(maxWidth[index])
60
+ disconnectBinding(maxHeight[index])
61
+ width[index] = undefined as any
62
+ height[index] = undefined as any
63
+ minWidth[index] = undefined as any
64
+ minHeight[index] = undefined as any
65
+ maxWidth[index] = undefined as any
66
+ maxHeight[index] = undefined as any
67
+ }
68
+ }