@rlabs-inc/tui 0.1.0 → 0.2.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.
Files changed (39) hide show
  1. package/README.md +126 -13
  2. package/index.ts +11 -5
  3. package/package.json +2 -2
  4. package/src/api/history.ts +451 -0
  5. package/src/api/mount.ts +66 -31
  6. package/src/engine/arrays/core.ts +13 -21
  7. package/src/engine/arrays/dimensions.ts +22 -32
  8. package/src/engine/arrays/index.ts +88 -86
  9. package/src/engine/arrays/interaction.ts +34 -48
  10. package/src/engine/arrays/layout.ts +67 -92
  11. package/src/engine/arrays/spacing.ts +37 -52
  12. package/src/engine/arrays/text.ts +23 -31
  13. package/src/engine/arrays/visual.ts +56 -75
  14. package/src/engine/inheritance.ts +18 -18
  15. package/src/engine/registry.ts +15 -0
  16. package/src/pipeline/frameBuffer.ts +26 -26
  17. package/src/pipeline/layout/index.ts +2 -2
  18. package/src/pipeline/layout/titan-engine.ts +112 -84
  19. package/src/primitives/animation.ts +194 -0
  20. package/src/primitives/box.ts +74 -86
  21. package/src/primitives/each.ts +87 -0
  22. package/src/primitives/index.ts +7 -0
  23. package/src/primitives/scope.ts +215 -0
  24. package/src/primitives/show.ts +77 -0
  25. package/src/primitives/text.ts +63 -59
  26. package/src/primitives/types.ts +1 -1
  27. package/src/primitives/when.ts +102 -0
  28. package/src/renderer/append-region.ts +159 -0
  29. package/src/renderer/index.ts +4 -2
  30. package/src/renderer/output.ts +11 -34
  31. package/src/state/focus.ts +16 -5
  32. package/src/state/global-keys.ts +184 -0
  33. package/src/state/index.ts +44 -8
  34. package/src/state/input.ts +534 -0
  35. package/src/state/keyboard.ts +98 -674
  36. package/src/state/mouse.ts +163 -340
  37. package/src/state/scroll.ts +7 -9
  38. package/src/types/index.ts +23 -2
  39. package/src/renderer/input.ts +0 -518
package/src/api/mount.ts CHANGED
@@ -23,17 +23,19 @@
23
23
  */
24
24
 
25
25
  import { effect } from '@rlabs-inc/signals'
26
- import type { MountOptions, ResizeEvent } from '../types'
26
+ import type { MountOptions, ResizeEvent, AppendMountResult } from '../types'
27
27
  import {
28
28
  DiffRenderer,
29
29
  InlineRenderer,
30
30
  } from '../renderer/output'
31
+ import { AppendRegionRenderer } from '../renderer/append-region'
32
+ import { HistoryWriter, createRenderToHistory } from './history'
31
33
  import * as ansi from '../renderer/ansi'
32
34
  import { frameBufferDerived } from '../pipeline/frameBuffer'
33
35
  import { layoutDerived, terminalWidth, terminalHeight, updateTerminalSize, renderMode } from '../pipeline/layout'
34
36
  import { resetRegistry } from '../engine/registry'
35
- import { hitGrid, clearHitGrid } from '../state/mouse'
36
- import { keyboard } from '../state/keyboard'
37
+ import { hitGrid, clearHitGrid, mouse } from '../state/mouse'
38
+ import { globalKeys } from '../state/global-keys'
37
39
 
38
40
  // =============================================================================
39
41
  // MOUNT
@@ -49,7 +51,7 @@ import { keyboard } from '../state/keyboard'
49
51
  export async function mount(
50
52
  root: () => void,
51
53
  options: MountOptions = {}
52
- ): Promise<() => Promise<void>> {
54
+ ): Promise<(() => Promise<void>) | AppendMountResult> {
53
55
  const {
54
56
  mode = 'fullscreen',
55
57
  mouse = true,
@@ -63,12 +65,21 @@ export async function mount(
63
65
  // Create renderer based on mode
64
66
  // Fullscreen uses DiffRenderer (absolute positioning)
65
67
  // Inline uses InlineRenderer (eraseLines + sequential write)
66
- // Append uses DiffRenderer for now (TODO: may need AppendRenderer)
68
+ // Append uses AppendRegionRenderer (eraseDown + render active)
67
69
  const diffRenderer = new DiffRenderer()
68
70
  const inlineRenderer = new InlineRenderer()
71
+ const appendRegionRenderer = new AppendRegionRenderer()
72
+
73
+ // For append mode: create history writer and renderToHistory function
74
+ let historyWriter: HistoryWriter | null = null
75
+ let renderToHistory: ((componentFn: () => void) => void) | null = null
76
+
77
+ if (mode === 'append') {
78
+ historyWriter = new HistoryWriter()
79
+ renderToHistory = createRenderToHistory(historyWriter, appendRegionRenderer)
80
+ }
69
81
 
70
82
  // Mode-specific state
71
- let previousHeight = 0 // For append mode: track last rendered height
72
83
  let isFirstRender = true
73
84
 
74
85
  // Resize handlers (keyboard module handles key/mouse)
@@ -87,9 +98,7 @@ export async function mount(
87
98
 
88
99
  setupSequence.push(ansi.hideCursor)
89
100
 
90
- if (mouse) {
91
- setupSequence.push(ansi.enableMouse)
92
- }
101
+ // Mouse tracking is handled by globalKeys.initialize() via mouse.enableTracking()
93
102
 
94
103
  if (kittyKeyboard) {
95
104
  setupSequence.push(ansi.enableKittyKeyboard)
@@ -101,9 +110,8 @@ export async function mount(
101
110
  // Write setup sequence
102
111
  process.stdout.write(setupSequence.join(''))
103
112
 
104
- // Initialize keyboard module (handles stdin, raw mode, input parsing)
105
- // Pass cleanup callback for proper shutdown on Ctrl+C
106
- keyboard.initialize()
113
+ // Initialize global input system (stdin, keyboard, mouse, shortcuts)
114
+ globalKeys.initialize({ enableMouse: mouse })
107
115
 
108
116
  // Handle resize
109
117
  const handleResize = () => {
@@ -127,20 +135,29 @@ export async function mount(
127
135
  // Create the component tree
128
136
  root()
129
137
 
138
+ // Global error handlers for debugging
139
+ process.on('uncaughtException', (err) => {
140
+ console.error('[TUI] Uncaught exception:', err)
141
+ })
142
+ process.on('unhandledRejection', (err) => {
143
+ console.error('[TUI] Unhandled rejection:', err)
144
+ })
145
+
130
146
  // THE ONE RENDER EFFECT
131
147
  // This is where the magic happens - reactive rendering!
132
148
  // Side effects (HitGrid) are applied HERE, not in the derived.
133
149
  let stopEffect: (() => void) | null = null
134
150
 
135
151
  stopEffect = effect(() => {
152
+ try {
136
153
  const start = Bun.nanoseconds()
137
154
 
138
155
  // 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
156
+ const layoutStart = Bun.nanoseconds()
157
+ const computedLayout = layoutDerived.value
158
+ const layoutNs = Bun.nanoseconds() - layoutStart
142
159
 
143
- // Time buffer computation
160
+ // Time buffer computation (layout is cached, so this is just framebuffer)
144
161
  const bufferStart = Bun.nanoseconds()
145
162
  const { buffer, hitRegions, terminalSize } = frameBufferDerived.value
146
163
  const bufferNs = Bun.nanoseconds() - bufferStart
@@ -161,7 +178,12 @@ export async function mount(
161
178
  diffRenderer.render(buffer)
162
179
  } else if (mode === 'inline') {
163
180
  inlineRenderer.render(buffer)
181
+ } else if (mode === 'append') {
182
+ // Append mode: render active content only
183
+ // History is written via renderToHistory() by the app
184
+ appendRegionRenderer.render(buffer)
164
185
  } else {
186
+ // Fallback to inline for unknown modes
165
187
  inlineRenderer.render(buffer)
166
188
  }
167
189
  const renderNs = Bun.nanoseconds() - renderStart
@@ -169,24 +191,34 @@ export async function mount(
169
191
  const totalNs = Bun.nanoseconds() - start
170
192
 
171
193
  // Show all timing stats in window title
172
- // const layoutMs = layoutNs / 1_000_000
194
+ const layoutMs = layoutNs / 1_000_000
173
195
  const bufferMs = bufferNs / 1_000_000
174
196
  const renderMs = renderNs / 1_000_000
175
197
  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`)
198
+ process.stdout.write(`\x1b]0;TUI | h:${computedLayout.contentHeight} | layout: ${layoutMs.toFixed(3)}ms | buffer: ${bufferMs.toFixed(3)}ms | render: ${renderMs.toFixed(3)}ms | total: ${totalMs.toFixed(3)}ms\x07`)
199
+ } catch (err) {
200
+ console.error('[TUI] Render effect error:', err)
201
+ }
178
202
  })
179
203
 
180
204
  // Cleanup function
181
- return async () => {
205
+ const cleanup = async () => {
182
206
  // Stop the render effect
183
207
  if (stopEffect) {
184
208
  stopEffect()
185
209
  stopEffect = null
186
210
  }
187
211
 
188
- // Cleanup keyboard module (removes stdin listener, restores terminal)
189
- keyboard.cleanup()
212
+ // Cleanup global input system
213
+ globalKeys.cleanup()
214
+
215
+ // Cleanup append mode resources
216
+ if (mode === 'append') {
217
+ appendRegionRenderer.cleanup()
218
+ if (historyWriter) {
219
+ historyWriter.end()
220
+ }
221
+ }
190
222
 
191
223
  // Remove resize listener
192
224
  process.stdout.removeListener('resize', handleResize)
@@ -201,11 +233,7 @@ export async function mount(
201
233
  restoreSequence.push(ansi.disableKittyKeyboard)
202
234
  }
203
235
 
204
- if (mouse) {
205
- restoreSequence.push(ansi.disableMouse)
206
- }
207
-
208
- restoreSequence.push(ansi.showCursor)
236
+ // Mouse disable and cursor show handled by globalKeys.cleanup()
209
237
  restoreSequence.push(ansi.reset)
210
238
 
211
239
  if (mode === 'fullscreen') {
@@ -218,13 +246,20 @@ export async function mount(
218
246
 
219
247
  process.stdout.write(restoreSequence.join(''))
220
248
 
221
- // Disable raw mode
222
- if (process.stdin.isTTY) {
223
- process.stdin.setRawMode(false)
224
- }
249
+ // Raw mode disabled by globalKeys.cleanup() -> input.cleanup()
225
250
 
226
251
  // Reset registry for clean slate
227
252
  resetRegistry()
228
253
  }
254
+
255
+ // Return based on mode
256
+ if (mode === 'append' && renderToHistory) {
257
+ return {
258
+ cleanup,
259
+ renderToHistory,
260
+ }
261
+ }
262
+
263
+ return cleanup
229
264
  }
230
265
 
@@ -8,53 +8,45 @@
8
8
  *
9
9
  * NOTE: Focus state (focusable, tabIndex) is in interaction.ts
10
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.
11
+ * Uses slotArray for stable reactive cells that NEVER get replaced.
12
+ * componentType is the exception - it stores values directly, not reactively.
14
13
  */
15
14
 
16
- import { bind, disconnectBinding, type Binding } from '@rlabs-inc/signals'
15
+ import { slotArray, type SlotArray } from '@rlabs-inc/signals'
17
16
  import { ComponentType } from '../../types'
18
17
  import type { ComponentTypeValue } from '../../types'
19
18
 
20
- /** Component type (box, text, input, etc.) - stores values directly */
19
+ /** Component type (box, text, input, etc.) - stores values directly (not reactive) */
21
20
  export const componentType: ComponentTypeValue[] = []
22
21
 
23
22
  /** Parent component index (-1 for root) */
24
- export const parentIndex: Binding<number>[] = []
23
+ export const parentIndex: SlotArray<number> = slotArray<number>(-1)
25
24
 
26
25
  /** Is component visible (0/false = hidden, 1/true = visible) */
27
- export const visible: Binding<number | boolean>[] = []
26
+ export const visible: SlotArray<number | boolean> = slotArray<number | boolean>(1)
28
27
 
29
28
  /** Component ID (for debugging and lookups) */
30
- export const componentId: Binding<string>[] = []
29
+ export const componentId: SlotArray<string> = slotArray<string>('')
31
30
 
32
31
  /**
33
32
  * Ensure array has capacity for the given index.
34
33
  * 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
34
  */
40
35
  export function ensureCapacity(index: number): void {
41
36
  while (componentType.length <= index) {
42
37
  componentType.push(ComponentType.NONE)
43
- parentIndex.push(undefined as any)
44
- visible.push(undefined as any)
45
- componentId.push(undefined as any)
46
38
  }
39
+ parentIndex.ensureCapacity(index)
40
+ visible.ensureCapacity(index)
41
+ componentId.ensureCapacity(index)
47
42
  }
48
43
 
49
44
  /** Clear values at index (called when releasing) */
50
45
  export function clearAtIndex(index: number): void {
51
46
  if (index < componentType.length) {
52
47
  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
48
  }
49
+ parentIndex.clear(index)
50
+ visible.clear(index)
51
+ componentId.clear(index)
60
52
  }
@@ -5,8 +5,7 @@
5
5
  * These are INPUT values that components write.
6
6
  * The layout derived READS these and RETURNS computed positions.
7
7
  *
8
- * CRITICAL: Use regular arrays (NOT state!) to preserve binding getters.
9
- * state() proxies snapshot getter values, breaking reactivity.
8
+ * Uses slotArray for stable reactive cells that NEVER get replaced.
10
9
  *
11
10
  * Supports both absolute and percentage dimensions:
12
11
  * - number: Absolute value in cells (e.g., 50)
@@ -17,52 +16,43 @@
17
16
  * Note: 0 means "auto" for width/height, "no constraint" for min/max.
18
17
  */
19
18
 
20
- import { bind, disconnectBinding, type Binding } from '@rlabs-inc/signals'
19
+ import { slotArray, type SlotArray } from '@rlabs-inc/signals'
21
20
  import type { Dimension } from '../../types'
22
21
 
23
22
  /** Requested width (0 = auto, '100%' = full parent width) */
24
- export const width: Binding<Dimension>[] = []
23
+ export const width: SlotArray<Dimension> = slotArray<Dimension>(0)
25
24
 
26
25
  /** Requested height (0 = auto, '100%' = full parent height) */
27
- export const height: Binding<Dimension>[] = []
26
+ export const height: SlotArray<Dimension> = slotArray<Dimension>(0)
28
27
 
29
28
  /** Minimum width constraint */
30
- export const minWidth: Binding<Dimension>[] = []
29
+ export const minWidth: SlotArray<Dimension> = slotArray<Dimension>(0)
31
30
 
32
31
  /** Minimum height constraint */
33
- export const minHeight: Binding<Dimension>[] = []
32
+ export const minHeight: SlotArray<Dimension> = slotArray<Dimension>(0)
34
33
 
35
34
  /** Maximum width constraint (0 = no max) */
36
- export const maxWidth: Binding<Dimension>[] = []
35
+ export const maxWidth: SlotArray<Dimension> = slotArray<Dimension>(0)
37
36
 
38
37
  /** Maximum height constraint (0 = no max) */
39
- export const maxHeight: Binding<Dimension>[] = []
38
+ export const maxHeight: SlotArray<Dimension> = slotArray<Dimension>(0)
40
39
 
41
- /** LAZY BINDING: Push undefined, primitives create bindings for used props only */
40
+ /** Ensure capacity for all dimension arrays */
42
41
  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
- }
42
+ width.ensureCapacity(index)
43
+ height.ensureCapacity(index)
44
+ minWidth.ensureCapacity(index)
45
+ minHeight.ensureCapacity(index)
46
+ maxWidth.ensureCapacity(index)
47
+ maxHeight.ensureCapacity(index)
51
48
  }
52
49
 
50
+ /** Clear slot at index (reset to default) */
53
51
  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
- }
52
+ width.clear(index)
53
+ height.clear(index)
54
+ minWidth.clear(index)
55
+ minHeight.clear(index)
56
+ maxWidth.clear(index)
57
+ maxHeight.clear(index)
68
58
  }
@@ -4,8 +4,11 @@
4
4
  * All component state lives in these parallel arrays.
5
5
  * Each array index corresponds to one component.
6
6
  *
7
- * Components write directly to these arrays.
8
- * Deriveds read from these arrays and RETURN computed values.
7
+ * Components write directly to these arrays using setSource().
8
+ * Deriveds read from these arrays directly (no unwrap needed).
9
+ *
10
+ * All arrays use slotArray for stable reactive cells that NEVER get replaced.
11
+ * This fixes the bind() tracking bug where deriveds miss updates.
9
12
  *
10
13
  * Array categories:
11
14
  * - core: Component type, parent, visibility
@@ -25,7 +28,7 @@ export * as visual from './visual'
25
28
  export * as text from './text'
26
29
  export * as interaction from './interaction'
27
30
 
28
- import { disconnectBinding } from '@rlabs-inc/signals'
31
+ import { type SlotArray } from '@rlabs-inc/signals'
29
32
  import * as core from './core'
30
33
  import * as dimensions from './dimensions'
31
34
  import * as spacing from './spacing'
@@ -62,12 +65,11 @@ export function clearAllAtIndex(index: number): void {
62
65
  interaction.clearAtIndex(index)
63
66
  }
64
67
 
65
- /** Disconnect all bindings in an array before truncating */
66
- function disconnectArray(arr: unknown[]): void {
68
+ /** Clear all slots in a SlotArray (slots are stable, just reset to defaults) */
69
+ function clearSlotArray<T>(arr: SlotArray<T>): void {
67
70
  for (let i = 0; i < arr.length; i++) {
68
- disconnectBinding(arr[i] as any)
71
+ arr.clear(i)
69
72
  }
70
- arr.length = 0
71
73
  }
72
74
 
73
75
  /**
@@ -75,92 +77,92 @@ function disconnectArray(arr: unknown[]): void {
75
77
  * Called automatically when all components are destroyed (allocatedIndices.size === 0).
76
78
  * This is the "reset on zero" cleanup - no manual API needed!
77
79
  *
78
- * IMPORTANT: Disconnects all bindings before truncating to break circular refs.
80
+ * SlotArrays are stable - we just clear them to defaults.
79
81
  */
80
82
  export function resetAllArrays(): void {
81
- // Core arrays
83
+ // Core arrays (componentType is plain array, rest are slotArrays)
82
84
  core.componentType.length = 0
83
- disconnectArray(core.parentIndex)
84
- disconnectArray(core.visible)
85
- disconnectArray(core.componentId)
85
+ clearSlotArray(core.parentIndex)
86
+ clearSlotArray(core.visible)
87
+ clearSlotArray(core.componentId)
86
88
 
87
- // Dimension arrays
88
- disconnectArray(dimensions.width)
89
- disconnectArray(dimensions.height)
90
- disconnectArray(dimensions.minWidth)
91
- disconnectArray(dimensions.minHeight)
92
- disconnectArray(dimensions.maxWidth)
93
- disconnectArray(dimensions.maxHeight)
89
+ // Dimension arrays (all slotArrays)
90
+ clearSlotArray(dimensions.width)
91
+ clearSlotArray(dimensions.height)
92
+ clearSlotArray(dimensions.minWidth)
93
+ clearSlotArray(dimensions.minHeight)
94
+ clearSlotArray(dimensions.maxWidth)
95
+ clearSlotArray(dimensions.maxHeight)
94
96
 
95
- // Spacing arrays
96
- disconnectArray(spacing.marginTop)
97
- disconnectArray(spacing.marginRight)
98
- disconnectArray(spacing.marginBottom)
99
- disconnectArray(spacing.marginLeft)
100
- disconnectArray(spacing.paddingTop)
101
- disconnectArray(spacing.paddingRight)
102
- disconnectArray(spacing.paddingBottom)
103
- disconnectArray(spacing.paddingLeft)
104
- disconnectArray(spacing.gap)
105
- disconnectArray(spacing.rowGap)
106
- disconnectArray(spacing.columnGap)
97
+ // Spacing arrays (all slotArrays)
98
+ clearSlotArray(spacing.marginTop)
99
+ clearSlotArray(spacing.marginRight)
100
+ clearSlotArray(spacing.marginBottom)
101
+ clearSlotArray(spacing.marginLeft)
102
+ clearSlotArray(spacing.paddingTop)
103
+ clearSlotArray(spacing.paddingRight)
104
+ clearSlotArray(spacing.paddingBottom)
105
+ clearSlotArray(spacing.paddingLeft)
106
+ clearSlotArray(spacing.gap)
107
+ clearSlotArray(spacing.rowGap)
108
+ clearSlotArray(spacing.columnGap)
107
109
 
108
- // Layout arrays
109
- disconnectArray(layout.flexDirection)
110
- disconnectArray(layout.flexWrap)
111
- disconnectArray(layout.justifyContent)
112
- disconnectArray(layout.alignItems)
113
- disconnectArray(layout.alignContent)
114
- disconnectArray(layout.flexGrow)
115
- disconnectArray(layout.flexShrink)
116
- disconnectArray(layout.flexBasis)
117
- disconnectArray(layout.alignSelf)
118
- disconnectArray(layout.order)
119
- disconnectArray(layout.position)
120
- disconnectArray(layout.top)
121
- disconnectArray(layout.right)
122
- disconnectArray(layout.bottom)
123
- disconnectArray(layout.left)
124
- disconnectArray(layout.borderTop)
125
- disconnectArray(layout.borderRight)
126
- disconnectArray(layout.borderBottom)
127
- disconnectArray(layout.borderLeft)
128
- disconnectArray(layout.zIndex)
129
- disconnectArray(layout.overflow)
110
+ // Layout arrays (all slotArrays)
111
+ clearSlotArray(layout.flexDirection)
112
+ clearSlotArray(layout.flexWrap)
113
+ clearSlotArray(layout.justifyContent)
114
+ clearSlotArray(layout.alignItems)
115
+ clearSlotArray(layout.alignContent)
116
+ clearSlotArray(layout.flexGrow)
117
+ clearSlotArray(layout.flexShrink)
118
+ clearSlotArray(layout.flexBasis)
119
+ clearSlotArray(layout.alignSelf)
120
+ clearSlotArray(layout.order)
121
+ clearSlotArray(layout.position)
122
+ clearSlotArray(layout.top)
123
+ clearSlotArray(layout.right)
124
+ clearSlotArray(layout.bottom)
125
+ clearSlotArray(layout.left)
126
+ clearSlotArray(layout.borderTop)
127
+ clearSlotArray(layout.borderRight)
128
+ clearSlotArray(layout.borderBottom)
129
+ clearSlotArray(layout.borderLeft)
130
+ clearSlotArray(layout.zIndex)
131
+ clearSlotArray(layout.overflow)
130
132
 
131
- // Visual arrays
132
- disconnectArray(visual.fgColor)
133
- disconnectArray(visual.bgColor)
134
- disconnectArray(visual.opacity)
135
- disconnectArray(visual.borderStyle)
136
- disconnectArray(visual.borderColor)
137
- disconnectArray(visual.borderTop)
138
- disconnectArray(visual.borderRight)
139
- disconnectArray(visual.borderBottom)
140
- disconnectArray(visual.borderLeft)
141
- disconnectArray(visual.borderColorTop)
142
- disconnectArray(visual.borderColorRight)
143
- disconnectArray(visual.borderColorBottom)
144
- disconnectArray(visual.borderColorLeft)
145
- disconnectArray(visual.showFocusRing)
146
- disconnectArray(visual.focusRingColor)
133
+ // Visual arrays (all slotArrays)
134
+ clearSlotArray(visual.fgColor)
135
+ clearSlotArray(visual.bgColor)
136
+ clearSlotArray(visual.opacity)
137
+ clearSlotArray(visual.borderStyle)
138
+ clearSlotArray(visual.borderColor)
139
+ clearSlotArray(visual.borderTop)
140
+ clearSlotArray(visual.borderRight)
141
+ clearSlotArray(visual.borderBottom)
142
+ clearSlotArray(visual.borderLeft)
143
+ clearSlotArray(visual.borderColorTop)
144
+ clearSlotArray(visual.borderColorRight)
145
+ clearSlotArray(visual.borderColorBottom)
146
+ clearSlotArray(visual.borderColorLeft)
147
+ clearSlotArray(visual.showFocusRing)
148
+ clearSlotArray(visual.focusRingColor)
147
149
 
148
- // Text arrays
149
- disconnectArray(text.textContent)
150
- disconnectArray(text.textAttrs)
151
- disconnectArray(text.textAlign)
152
- disconnectArray(text.textWrap)
153
- disconnectArray(text.ellipsis)
150
+ // Text arrays (all slotArrays)
151
+ clearSlotArray(text.textContent)
152
+ clearSlotArray(text.textAttrs)
153
+ clearSlotArray(text.textAlign)
154
+ clearSlotArray(text.textWrap)
155
+ clearSlotArray(text.ellipsis)
154
156
 
155
- // Interaction arrays
156
- disconnectArray(interaction.scrollOffsetX)
157
- disconnectArray(interaction.scrollOffsetY)
158
- disconnectArray(interaction.focusable)
159
- disconnectArray(interaction.tabIndex)
160
- disconnectArray(interaction.hovered)
161
- disconnectArray(interaction.pressed)
162
- disconnectArray(interaction.mouseEnabled)
163
- disconnectArray(interaction.cursorPosition)
164
- disconnectArray(interaction.selectionStart)
165
- disconnectArray(interaction.selectionEnd)
157
+ // Interaction arrays (all slotArrays)
158
+ clearSlotArray(interaction.scrollOffsetX)
159
+ clearSlotArray(interaction.scrollOffsetY)
160
+ clearSlotArray(interaction.focusable)
161
+ clearSlotArray(interaction.tabIndex)
162
+ clearSlotArray(interaction.hovered)
163
+ clearSlotArray(interaction.pressed)
164
+ clearSlotArray(interaction.mouseEnabled)
165
+ clearSlotArray(interaction.cursorPosition)
166
+ clearSlotArray(interaction.selectionStart)
167
+ clearSlotArray(interaction.selectionEnd)
166
168
  }