@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
@@ -0,0 +1,241 @@
1
+ /**
2
+ * TUI Framework - Focus State Module
3
+ *
4
+ * Manages focus state and navigation:
5
+ * - focusedIndex signal (currently focused component)
6
+ * - Focus cycling (Tab/Shift+Tab)
7
+ * - Focus trapping for modals
8
+ * - Focus history for restoration
9
+ */
10
+
11
+ import { signal, derived, unwrap } from '@rlabs-inc/signals'
12
+ import { focusable, tabIndex, focusedIndex as _focusedIndex } from '../engine/arrays/interaction'
13
+ import { visible } from '../engine/arrays/core'
14
+ import { getAllocatedIndices } from '../engine/registry'
15
+
16
+ // Re-export the focusedIndex from interaction arrays
17
+ export const focusedIndex = _focusedIndex
18
+
19
+ // =============================================================================
20
+ // FOCUS TRAP (for modals/dialogs)
21
+ // =============================================================================
22
+
23
+ const focusTrapStack: number[] = []
24
+
25
+ /** Push a focus trap - focus will be contained within this component's children */
26
+ export function pushFocusTrap(containerIndex: number): void {
27
+ focusTrapStack.push(containerIndex)
28
+ }
29
+
30
+ /** Pop the current focus trap */
31
+ export function popFocusTrap(): number | undefined {
32
+ return focusTrapStack.pop()
33
+ }
34
+
35
+ /** Check if focus is currently trapped */
36
+ export function isFocusTrapped(): boolean {
37
+ return focusTrapStack.length > 0
38
+ }
39
+
40
+ /** Get the current focus trap container */
41
+ export function getFocusTrapContainer(): number | undefined {
42
+ return focusTrapStack[focusTrapStack.length - 1]
43
+ }
44
+
45
+ // =============================================================================
46
+ // FOCUS HISTORY (for restoration)
47
+ // =============================================================================
48
+
49
+ const focusHistory: number[] = []
50
+ const MAX_HISTORY = 10
51
+
52
+ /** Save current focus to history */
53
+ export function saveFocusToHistory(): void {
54
+ const current = focusedIndex.value
55
+ if (current >= 0) {
56
+ focusHistory.push(current)
57
+ if (focusHistory.length > MAX_HISTORY) {
58
+ focusHistory.shift()
59
+ }
60
+ }
61
+ }
62
+
63
+ /** Restore focus from history */
64
+ export function restoreFocusFromHistory(): boolean {
65
+ while (focusHistory.length > 0) {
66
+ const index = focusHistory.pop()!
67
+ // Check if component is still valid and focusable
68
+ if (unwrap(focusable[index]) && unwrap(visible[index])) {
69
+ focusedIndex.value = index
70
+ return true
71
+ }
72
+ }
73
+ return false
74
+ }
75
+
76
+ // =============================================================================
77
+ // FOCUSABLE QUERIES
78
+ // =============================================================================
79
+
80
+ /** Get all focusable component indices, sorted by tabIndex */
81
+ export function getFocusableIndices(): number[] {
82
+ const indices = getAllocatedIndices()
83
+ const result: number[] = []
84
+
85
+ for (const i of indices) {
86
+ if (unwrap(focusable[i]) && unwrap(visible[i])) {
87
+ result.push(i)
88
+ }
89
+ }
90
+
91
+ // Sort by tabIndex (components with same tabIndex keep allocation order)
92
+ result.sort((a, b) => {
93
+ const tabA = unwrap(tabIndex[a]) ?? 0
94
+ const tabB = unwrap(tabIndex[b]) ?? 0
95
+ if (tabA !== tabB) return tabA - tabB
96
+ return a - b // Stable sort by index
97
+ })
98
+
99
+ return result
100
+ }
101
+
102
+ /** Derived: all focusable indices */
103
+ export const focusableIndices = derived(getFocusableIndices)
104
+
105
+ /** Derived: is any component focused */
106
+ export const hasFocus = derived(() => focusedIndex.value >= 0)
107
+
108
+ /** Derived: is specific component focused */
109
+ export function isFocused(index: number): boolean {
110
+ return focusedIndex.value === index
111
+ }
112
+
113
+ // =============================================================================
114
+ // FOCUS NAVIGATION
115
+ // =============================================================================
116
+
117
+ /** Find next focusable component */
118
+ function findNextFocusable(fromIndex: number, direction: 1 | -1): number {
119
+ let focusables = getFocusableIndices()
120
+
121
+ // Apply focus trap if active
122
+ if (isFocusTrapped()) {
123
+ const trapContainer = getFocusTrapContainer()
124
+ // In a real implementation, we'd filter to children of trapContainer
125
+ // For now, this is a placeholder
126
+ }
127
+
128
+ if (focusables.length === 0) return -1
129
+
130
+ const currentPos = focusables.indexOf(fromIndex)
131
+
132
+ if (currentPos === -1) {
133
+ // Not currently focused on a focusable
134
+ return direction === 1 ? focusables[0]! : focusables[focusables.length - 1]!
135
+ }
136
+
137
+ // Move in direction with wrap
138
+ const nextPos = (currentPos + direction + focusables.length) % focusables.length
139
+ return focusables[nextPos]!
140
+ }
141
+
142
+ /** Move focus to next focusable component */
143
+ export function focusNext(): boolean {
144
+ const next = findNextFocusable(focusedIndex.value, 1)
145
+ if (next !== -1 && next !== focusedIndex.value) {
146
+ saveFocusToHistory()
147
+ focusedIndex.value = next
148
+ return true
149
+ }
150
+ return false
151
+ }
152
+
153
+ /** Move focus to previous focusable component */
154
+ export function focusPrevious(): boolean {
155
+ const prev = findNextFocusable(focusedIndex.value, -1)
156
+ if (prev !== -1 && prev !== focusedIndex.value) {
157
+ saveFocusToHistory()
158
+ focusedIndex.value = prev
159
+ return true
160
+ }
161
+ return false
162
+ }
163
+
164
+ /** Focus a specific component by index */
165
+ export function focus(index: number): boolean {
166
+ if (unwrap(focusable[index]) && unwrap(visible[index])) {
167
+ if (focusedIndex.value !== index) {
168
+ saveFocusToHistory()
169
+ focusedIndex.value = index
170
+ }
171
+ return true
172
+ }
173
+ return false
174
+ }
175
+
176
+ /** Clear focus (no component focused) */
177
+ export function blur(): void {
178
+ if (focusedIndex.value >= 0) {
179
+ saveFocusToHistory()
180
+ focusedIndex.value = -1
181
+ }
182
+ }
183
+
184
+ /** Focus the first focusable component */
185
+ export function focusFirst(): boolean {
186
+ const focusables = getFocusableIndices()
187
+ if (focusables.length > 0) {
188
+ return focus(focusables[0]!)
189
+ }
190
+ return false
191
+ }
192
+
193
+ /** Focus the last focusable component */
194
+ export function focusLast(): boolean {
195
+ const focusables = getFocusableIndices()
196
+ if (focusables.length > 0) {
197
+ return focus(focusables[focusables.length - 1]!)
198
+ }
199
+ return false
200
+ }
201
+
202
+ // =============================================================================
203
+ // RESET (for testing)
204
+ // =============================================================================
205
+
206
+ /** Reset all focus state (for testing) */
207
+ export function resetFocusState(): void {
208
+ focusedIndex.value = -1
209
+ focusTrapStack.length = 0
210
+ focusHistory.length = 0
211
+ }
212
+
213
+ // =============================================================================
214
+ // EXPORT
215
+ // =============================================================================
216
+
217
+ export const focusManager = {
218
+ // State
219
+ get focusedIndex() { return focusedIndex.value },
220
+ get hasFocus() { return hasFocus.value },
221
+ get focusableIndices() { return focusableIndices.value },
222
+ isFocused,
223
+
224
+ // Navigation
225
+ focusNext,
226
+ focusPrevious,
227
+ focus,
228
+ blur,
229
+ focusFirst,
230
+ focusLast,
231
+
232
+ // Focus trap
233
+ pushFocusTrap,
234
+ popFocusTrap,
235
+ isFocusTrapped,
236
+ getFocusTrapContainer,
237
+
238
+ // History
239
+ saveFocusToHistory,
240
+ restoreFocusFromHistory,
241
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * TUI Framework - State Modules
3
+ *
4
+ * Input handling and interaction state management.
5
+ *
6
+ * Modules:
7
+ * - keyboard: Key events, focus navigation, Kitty protocol
8
+ * - mouse: Mouse events, HitGrid, SGR protocol
9
+ * - focus: Focus management, trapping, history
10
+ * - scroll: Scroll state, wheel/keyboard handling
11
+ * - cursor: Cursor visibility, shape, position
12
+ */
13
+
14
+ // Keyboard is primary for focus navigation
15
+ export * from './keyboard'
16
+ export * from './mouse'
17
+ // Focus exports - exclude duplicates that are in keyboard
18
+ export {
19
+ focusedIndex,
20
+ pushFocusTrap,
21
+ popFocusTrap,
22
+ isFocusTrapped,
23
+ getFocusTrapContainer,
24
+ saveFocusToHistory,
25
+ restoreFocusFromHistory,
26
+ getFocusableIndices,
27
+ focusableIndices,
28
+ hasFocus,
29
+ isFocused,
30
+ focus,
31
+ blur,
32
+ focusFirst,
33
+ focusLast,
34
+ focusManager,
35
+ } from './focus'
36
+ export * from './scroll'
37
+ export * from './cursor'
38
+
39
+ // Convenient namespace exports
40
+ export { keyboard } from './keyboard'
41
+ export { mouse } from './mouse'
42
+ export { scroll } from './scroll'
43
+ export { cursor } from './cursor'