@mdxui/terminal 2.0.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 (191) hide show
  1. package/README.md +571 -0
  2. package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
  3. package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
  4. package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
  5. package/dist/chunk-3EFDH7PK.js +5235 -0
  6. package/dist/chunk-3RG5ZIWI.js +10 -0
  7. package/dist/chunk-3X5IR6WE.js +884 -0
  8. package/dist/chunk-4FV5ZDCE.js +5236 -0
  9. package/dist/chunk-4OVMSF2J.js +243 -0
  10. package/dist/chunk-63FEETIS.js +4048 -0
  11. package/dist/chunk-B43KP7XJ.js +884 -0
  12. package/dist/chunk-BMTJXWUV.js +655 -0
  13. package/dist/chunk-C3SVH4N7.js +882 -0
  14. package/dist/chunk-EVWR7Y47.js +874 -0
  15. package/dist/chunk-F6A5VWUC.js +1285 -0
  16. package/dist/chunk-FD7KW7GE.js +882 -0
  17. package/dist/chunk-GBQ6UD6I.js +655 -0
  18. package/dist/chunk-GMDD3M6U.js +5227 -0
  19. package/dist/chunk-JBHRXOXM.js +1058 -0
  20. package/dist/chunk-JFOO3EYO.js +1182 -0
  21. package/dist/chunk-JQ5H3WXL.js +1291 -0
  22. package/dist/chunk-JQD5NASE.js +234 -0
  23. package/dist/chunk-KRHJP5R7.js +592 -0
  24. package/dist/chunk-KWF6WVJE.js +962 -0
  25. package/dist/chunk-LHYQVN3H.js +1038 -0
  26. package/dist/chunk-M3TLQLGC.js +1032 -0
  27. package/dist/chunk-MVW4Q5OP.js +240 -0
  28. package/dist/chunk-NXCZSWLU.js +1294 -0
  29. package/dist/chunk-O25TNRO6.js +607 -0
  30. package/dist/chunk-PNECDA2I.js +884 -0
  31. package/dist/chunk-QIHWRLJR.js +962 -0
  32. package/dist/chunk-QW5YMQ7K.js +882 -0
  33. package/dist/chunk-R5U7XKVJ.js +16 -0
  34. package/dist/chunk-RP2MVQLR.js +962 -0
  35. package/dist/chunk-TP6RXGXA.js +1087 -0
  36. package/dist/chunk-TQQSTITZ.js +655 -0
  37. package/dist/chunk-X24GWXQV.js +1281 -0
  38. package/dist/components/index.d.ts +802 -0
  39. package/dist/components/index.js +149 -0
  40. package/dist/data/index.d.ts +2554 -0
  41. package/dist/data/index.js +51 -0
  42. package/dist/forms/index.d.ts +1596 -0
  43. package/dist/forms/index.js +464 -0
  44. package/dist/index-CQRFZntR.d.ts +867 -0
  45. package/dist/index.d.ts +579 -0
  46. package/dist/index.js +786 -0
  47. package/dist/interactive-D0JkWosD.d.ts +217 -0
  48. package/dist/keyboard/index.d.ts +2 -0
  49. package/dist/keyboard/index.js +43 -0
  50. package/dist/renderers/index.d.ts +546 -0
  51. package/dist/renderers/index.js +2157 -0
  52. package/dist/storybook/index.d.ts +396 -0
  53. package/dist/storybook/index.js +641 -0
  54. package/dist/theme/index.d.ts +1339 -0
  55. package/dist/theme/index.js +123 -0
  56. package/dist/types-Bxu5PAgA.d.ts +710 -0
  57. package/dist/types-CIlop5Ji.d.ts +701 -0
  58. package/dist/types-Ca8p_p5X.d.ts +710 -0
  59. package/package.json +90 -0
  60. package/src/__tests__/components/data/card.test.ts +458 -0
  61. package/src/__tests__/components/data/list.test.ts +473 -0
  62. package/src/__tests__/components/data/metrics.test.ts +541 -0
  63. package/src/__tests__/components/data/table.test.ts +448 -0
  64. package/src/__tests__/components/input/field.test.ts +555 -0
  65. package/src/__tests__/components/input/form.test.ts +870 -0
  66. package/src/__tests__/components/input/search.test.ts +1238 -0
  67. package/src/__tests__/components/input/select.test.ts +658 -0
  68. package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
  69. package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
  70. package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
  71. package/src/__tests__/components/navigation/tabs.test.ts +995 -0
  72. package/src/__tests__/components.test.tsx +1197 -0
  73. package/src/__tests__/core/compiler.test.ts +986 -0
  74. package/src/__tests__/core/parser.test.ts +785 -0
  75. package/src/__tests__/core/tier-switcher.test.ts +1103 -0
  76. package/src/__tests__/core/types.test.ts +1398 -0
  77. package/src/__tests__/data/collections.test.ts +1337 -0
  78. package/src/__tests__/data/db.test.ts +1265 -0
  79. package/src/__tests__/data/reactive.test.ts +1010 -0
  80. package/src/__tests__/data/sync.test.ts +1614 -0
  81. package/src/__tests__/errors.test.ts +660 -0
  82. package/src/__tests__/forms/integration.test.ts +444 -0
  83. package/src/__tests__/integration.test.ts +905 -0
  84. package/src/__tests__/keyboard.test.ts +1791 -0
  85. package/src/__tests__/renderer.test.ts +489 -0
  86. package/src/__tests__/renderers/ansi-css.test.ts +948 -0
  87. package/src/__tests__/renderers/ansi.test.ts +1366 -0
  88. package/src/__tests__/renderers/ascii.test.ts +1360 -0
  89. package/src/__tests__/renderers/interactive.test.ts +2353 -0
  90. package/src/__tests__/renderers/markdown.test.ts +1483 -0
  91. package/src/__tests__/renderers/text.test.ts +1369 -0
  92. package/src/__tests__/renderers/unicode.test.ts +1307 -0
  93. package/src/__tests__/theme.test.ts +639 -0
  94. package/src/__tests__/utils/assertions.ts +685 -0
  95. package/src/__tests__/utils/index.ts +115 -0
  96. package/src/__tests__/utils/test-renderer.ts +381 -0
  97. package/src/__tests__/utils/utils.test.ts +560 -0
  98. package/src/components/containers/card.ts +56 -0
  99. package/src/components/containers/dialog.ts +53 -0
  100. package/src/components/containers/index.ts +9 -0
  101. package/src/components/containers/panel.ts +59 -0
  102. package/src/components/feedback/badge.ts +40 -0
  103. package/src/components/feedback/index.ts +8 -0
  104. package/src/components/feedback/spinner.ts +23 -0
  105. package/src/components/helpers.ts +81 -0
  106. package/src/components/index.ts +153 -0
  107. package/src/components/layout/breadcrumb.ts +31 -0
  108. package/src/components/layout/index.ts +10 -0
  109. package/src/components/layout/list.ts +29 -0
  110. package/src/components/layout/sidebar.ts +79 -0
  111. package/src/components/layout/table.ts +62 -0
  112. package/src/components/primitives/box.ts +95 -0
  113. package/src/components/primitives/button.ts +54 -0
  114. package/src/components/primitives/index.ts +11 -0
  115. package/src/components/primitives/input.ts +88 -0
  116. package/src/components/primitives/select.ts +97 -0
  117. package/src/components/primitives/text.ts +60 -0
  118. package/src/components/render.ts +155 -0
  119. package/src/components/templates/app.ts +43 -0
  120. package/src/components/templates/index.ts +8 -0
  121. package/src/components/templates/site.ts +54 -0
  122. package/src/components/types.ts +777 -0
  123. package/src/core/compiler.ts +718 -0
  124. package/src/core/parser.ts +127 -0
  125. package/src/core/tier-switcher.ts +607 -0
  126. package/src/core/types.ts +672 -0
  127. package/src/data/collection.ts +316 -0
  128. package/src/data/collections.ts +50 -0
  129. package/src/data/context.tsx +174 -0
  130. package/src/data/db.ts +127 -0
  131. package/src/data/hooks.ts +532 -0
  132. package/src/data/index.ts +138 -0
  133. package/src/data/reactive.ts +1225 -0
  134. package/src/data/saas-collections.ts +375 -0
  135. package/src/data/sync.ts +1213 -0
  136. package/src/data/types.ts +660 -0
  137. package/src/forms/converters.ts +512 -0
  138. package/src/forms/index.ts +133 -0
  139. package/src/forms/schemas.ts +403 -0
  140. package/src/forms/types.ts +476 -0
  141. package/src/index.ts +542 -0
  142. package/src/keyboard/focus.ts +748 -0
  143. package/src/keyboard/index.ts +96 -0
  144. package/src/keyboard/integration.ts +371 -0
  145. package/src/keyboard/manager.ts +377 -0
  146. package/src/keyboard/presets.ts +90 -0
  147. package/src/renderers/ansi-css.ts +576 -0
  148. package/src/renderers/ansi.ts +802 -0
  149. package/src/renderers/ascii.ts +680 -0
  150. package/src/renderers/breadcrumb.ts +480 -0
  151. package/src/renderers/command-palette.ts +802 -0
  152. package/src/renderers/components/field.ts +210 -0
  153. package/src/renderers/components/form.ts +327 -0
  154. package/src/renderers/components/index.ts +21 -0
  155. package/src/renderers/components/search.ts +449 -0
  156. package/src/renderers/components/select.ts +222 -0
  157. package/src/renderers/index.ts +101 -0
  158. package/src/renderers/interactive/component-handlers.ts +622 -0
  159. package/src/renderers/interactive/cursor-manager.ts +147 -0
  160. package/src/renderers/interactive/focus-manager.ts +279 -0
  161. package/src/renderers/interactive/index.ts +661 -0
  162. package/src/renderers/interactive/input-handler.ts +164 -0
  163. package/src/renderers/interactive/keyboard-handler.ts +212 -0
  164. package/src/renderers/interactive/mouse-handler.ts +167 -0
  165. package/src/renderers/interactive/state-manager.ts +109 -0
  166. package/src/renderers/interactive/types.ts +338 -0
  167. package/src/renderers/interactive-string.ts +299 -0
  168. package/src/renderers/interactive.ts +59 -0
  169. package/src/renderers/markdown.ts +950 -0
  170. package/src/renderers/sidebar.ts +549 -0
  171. package/src/renderers/tabs.ts +682 -0
  172. package/src/renderers/text.ts +791 -0
  173. package/src/renderers/unicode.ts +917 -0
  174. package/src/renderers/utils.ts +942 -0
  175. package/src/router/adapters.ts +383 -0
  176. package/src/router/types.ts +140 -0
  177. package/src/router/utils.ts +452 -0
  178. package/src/schemas.ts +205 -0
  179. package/src/storybook/index.ts +91 -0
  180. package/src/storybook/interactive-decorator.tsx +659 -0
  181. package/src/storybook/keyboard-simulator.ts +501 -0
  182. package/src/theme/ansi-codes.ts +80 -0
  183. package/src/theme/box-drawing.ts +132 -0
  184. package/src/theme/color-convert.ts +254 -0
  185. package/src/theme/color-support.ts +321 -0
  186. package/src/theme/index.ts +134 -0
  187. package/src/theme/strip-ansi.ts +50 -0
  188. package/src/theme/tailwind-map.ts +469 -0
  189. package/src/theme/text-styles.ts +206 -0
  190. package/src/theme/theme-system.ts +568 -0
  191. package/src/types.ts +103 -0
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @mdxui/terminal Input Handler
3
+ *
4
+ * Handles input field registration and key handling
5
+ * for the interactive renderer.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { InputOptions, InputState, InputManagerState, FocusManagerState } from './types'
11
+ import { registerFocusable } from './focus-manager'
12
+
13
+ /**
14
+ * Creates the input manager state
15
+ */
16
+ export function createInputManagerState(): InputManagerState {
17
+ return {
18
+ inputs: new Map<string, InputState>(),
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Register an input field
24
+ */
25
+ export function registerInput(
26
+ inputState: InputManagerState,
27
+ focusState: FocusManagerState,
28
+ id: string,
29
+ options: InputOptions,
30
+ destroyed?: boolean
31
+ ): void {
32
+ if (destroyed) return
33
+ inputState.inputs.set(id, {
34
+ ...options,
35
+ currentValue: options.value,
36
+ currentCursorIndex: options.cursorIndex ?? options.value.length,
37
+ })
38
+
39
+ // Also register as focusable
40
+ registerFocusable(focusState, id, { tabIndex: 0 }, destroyed)
41
+ }
42
+
43
+ /**
44
+ * Get input cursor index
45
+ */
46
+ export function getInputCursorIndex(
47
+ inputState: InputManagerState,
48
+ id: string,
49
+ destroyed?: boolean
50
+ ): number {
51
+ if (destroyed) return 0
52
+ const input = inputState.inputs.get(id)
53
+ return input?.currentCursorIndex ?? 0
54
+ }
55
+
56
+ /**
57
+ * Handle input key press
58
+ */
59
+ export function handleInputKey(
60
+ id: string,
61
+ input: InputState,
62
+ key: string
63
+ ): boolean {
64
+ const { currentValue, currentCursorIndex, onChange, onSubmit, maxLength, validate, mask, multiline } = input
65
+
66
+ // Handle special keys
67
+ if (key === 'backspace') {
68
+ if (currentCursorIndex > 0) {
69
+ const newValue = currentValue.slice(0, currentCursorIndex - 1) + currentValue.slice(currentCursorIndex)
70
+ input.currentValue = newValue
71
+ input.currentCursorIndex = currentCursorIndex - 1
72
+ onChange?.(newValue)
73
+ }
74
+ return true
75
+ }
76
+
77
+ if (key === 'delete') {
78
+ if (currentCursorIndex < currentValue.length) {
79
+ const newValue = currentValue.slice(0, currentCursorIndex) + currentValue.slice(currentCursorIndex + 1)
80
+ input.currentValue = newValue
81
+ onChange?.(newValue)
82
+ }
83
+ return true
84
+ }
85
+
86
+ if (key === 'left') {
87
+ if (currentCursorIndex > 0) {
88
+ input.currentCursorIndex = currentCursorIndex - 1
89
+ }
90
+ return true
91
+ }
92
+
93
+ if (key === 'right') {
94
+ if (currentCursorIndex < currentValue.length) {
95
+ input.currentCursorIndex = currentCursorIndex + 1
96
+ }
97
+ return true
98
+ }
99
+
100
+ if (key === 'home') {
101
+ input.currentCursorIndex = 0
102
+ return true
103
+ }
104
+
105
+ if (key === 'end') {
106
+ input.currentCursorIndex = currentValue.length
107
+ return true
108
+ }
109
+
110
+ if (key === 'enter') {
111
+ if (multiline) {
112
+ const newValue = currentValue + '\n'
113
+ input.currentValue = newValue
114
+ input.currentCursorIndex = newValue.length
115
+ onChange?.(newValue)
116
+ } else {
117
+ onSubmit?.(currentValue)
118
+ }
119
+ return true
120
+ }
121
+
122
+ if (key === 'ctrl+enter') {
123
+ onSubmit?.(currentValue)
124
+ return true
125
+ }
126
+
127
+ // Handle regular character input
128
+ if (key.length === 1) {
129
+ // Check max length
130
+ if (maxLength !== undefined && currentValue.length >= maxLength) {
131
+ return true
132
+ }
133
+
134
+ let newValue = currentValue + key
135
+
136
+ // Apply mask if present
137
+ if (mask) {
138
+ // Simple mask handling: insert formatting chars
139
+ const maskChar = mask[newValue.length - 1]
140
+ if (maskChar && maskChar !== '#') {
141
+ newValue = currentValue + maskChar + key
142
+ }
143
+ }
144
+
145
+ // Validate if validator present
146
+ if (validate && !validate(newValue)) {
147
+ return true
148
+ }
149
+
150
+ input.currentValue = newValue
151
+ input.currentCursorIndex = newValue.length
152
+ onChange?.(newValue)
153
+ return true
154
+ }
155
+
156
+ return false
157
+ }
158
+
159
+ /**
160
+ * Clear input state on destroy
161
+ */
162
+ export function clearInputState(inputState: InputManagerState): void {
163
+ inputState.inputs.clear()
164
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * @mdxui/terminal Keyboard Handler
3
+ *
4
+ * Handles key binding, input handling, sequences, and vim bindings
5
+ * for the interactive renderer.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { KeyHandler, KeyboardManagerState } from './types'
11
+
12
+ /**
13
+ * Creates the keyboard manager state
14
+ */
15
+ export function createKeyboardManagerState(
16
+ vimBindings: boolean,
17
+ sequenceTimeout: number
18
+ ): KeyboardManagerState {
19
+ return {
20
+ keyHandlers: new Map<string, KeyHandler[]>(),
21
+ sequenceHandlers: new Map<string, () => void>(),
22
+ pendingSequence: '',
23
+ sequenceTimer: null,
24
+ sequenceTimeout,
25
+ mode: 'normal',
26
+ searchModeHandler: null,
27
+ cancelHandler: null,
28
+ vimBindings,
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Register a key press handler
34
+ */
35
+ export function onKeyPress(
36
+ keyboardState: KeyboardManagerState,
37
+ key: string,
38
+ handler: () => boolean | void,
39
+ options?: { priority?: number },
40
+ destroyed?: boolean
41
+ ): void {
42
+ if (destroyed) return
43
+ const handlers = keyboardState.keyHandlers.get(key) || []
44
+
45
+ // Check for duplicate handler
46
+ if (handlers.some(h => h.handler === handler)) return
47
+
48
+ handlers.push({ handler, priority: options?.priority ?? 0 })
49
+ // Sort by priority descending
50
+ handlers.sort((a, b) => b.priority - a.priority)
51
+ keyboardState.keyHandlers.set(key, handlers)
52
+ }
53
+
54
+ /**
55
+ * Unregister a key press handler
56
+ */
57
+ export function offKeyPress(
58
+ keyboardState: KeyboardManagerState,
59
+ key: string,
60
+ handler: () => boolean | void,
61
+ destroyed?: boolean
62
+ ): void {
63
+ if (destroyed) return
64
+ const handlers = keyboardState.keyHandlers.get(key)
65
+ if (handlers) {
66
+ const idx = handlers.findIndex(h => h.handler === handler)
67
+ if (idx !== -1) {
68
+ handlers.splice(idx, 1)
69
+ }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Emit a key event
75
+ */
76
+ export function emitKey(
77
+ keyboardState: KeyboardManagerState,
78
+ key: string,
79
+ handleComponentKey: (key: string) => boolean,
80
+ focusedId: string | null,
81
+ destroyed?: boolean
82
+ ): void {
83
+ if (destroyed) return
84
+
85
+ // Check for sequence handling
86
+ if (keyboardState.sequenceHandlers.size > 0) {
87
+ const newSequence = keyboardState.pendingSequence + key
88
+
89
+ // Check if this completes a sequence
90
+ const sequenceHandler = keyboardState.sequenceHandlers.get(newSequence)
91
+ if (sequenceHandler) {
92
+ keyboardState.pendingSequence = ''
93
+ if (keyboardState.sequenceTimer) {
94
+ clearTimeout(keyboardState.sequenceTimer)
95
+ keyboardState.sequenceTimer = null
96
+ }
97
+ sequenceHandler()
98
+ return
99
+ }
100
+
101
+ // Check if this could be part of a sequence
102
+ const couldBeSequence = Array.from(keyboardState.sequenceHandlers.keys()).some(s =>
103
+ s.startsWith(newSequence)
104
+ )
105
+ if (couldBeSequence) {
106
+ keyboardState.pendingSequence = newSequence
107
+ // Reset timeout
108
+ if (keyboardState.sequenceTimer) {
109
+ clearTimeout(keyboardState.sequenceTimer)
110
+ }
111
+ keyboardState.sequenceTimer = setTimeout(() => {
112
+ keyboardState.pendingSequence = ''
113
+ keyboardState.sequenceTimer = null
114
+ }, keyboardState.sequenceTimeout)
115
+ return
116
+ }
117
+
118
+ // Not part of any sequence, clear and process normally
119
+ keyboardState.pendingSequence = ''
120
+ if (keyboardState.sequenceTimer) {
121
+ clearTimeout(keyboardState.sequenceTimer)
122
+ keyboardState.sequenceTimer = null
123
+ }
124
+ }
125
+
126
+ // Process key handlers
127
+ const handlers = keyboardState.keyHandlers.get(key)
128
+ if (handlers) {
129
+ for (const { handler } of handlers) {
130
+ const result = handler()
131
+ if (result === true) {
132
+ return // Stop propagation
133
+ }
134
+ }
135
+ }
136
+
137
+ // If it's a single character, pass to focused input
138
+ if (key.length === 1 && focusedId) {
139
+ handleComponentKey(key)
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Register a key sequence handler
145
+ */
146
+ export function onKeySequence(
147
+ keyboardState: KeyboardManagerState,
148
+ sequence: string,
149
+ handler: () => void,
150
+ destroyed?: boolean
151
+ ): void {
152
+ if (destroyed) return
153
+ keyboardState.sequenceHandlers.set(sequence, handler)
154
+ }
155
+
156
+ /**
157
+ * Get the pending sequence
158
+ */
159
+ export function getPendingSequence(
160
+ keyboardState: KeyboardManagerState,
161
+ destroyed?: boolean
162
+ ): string {
163
+ if (destroyed) return ''
164
+ return keyboardState.pendingSequence
165
+ }
166
+
167
+ /**
168
+ * Register search mode handler
169
+ */
170
+ export function onSearchMode(
171
+ keyboardState: KeyboardManagerState,
172
+ handler: () => void,
173
+ destroyed?: boolean
174
+ ): void {
175
+ if (destroyed) return
176
+ keyboardState.searchModeHandler = handler
177
+ }
178
+
179
+ /**
180
+ * Register cancel handler
181
+ */
182
+ export function onCancel(
183
+ keyboardState: KeyboardManagerState,
184
+ handler: () => void,
185
+ destroyed?: boolean
186
+ ): void {
187
+ if (destroyed) return
188
+ keyboardState.cancelHandler = handler
189
+ }
190
+
191
+ /**
192
+ * Get the current mode
193
+ */
194
+ export function getMode(
195
+ keyboardState: KeyboardManagerState,
196
+ destroyed?: boolean
197
+ ): string {
198
+ if (destroyed) return 'normal'
199
+ return keyboardState.mode
200
+ }
201
+
202
+ /**
203
+ * Clear keyboard state on destroy
204
+ */
205
+ export function clearKeyboardState(keyboardState: KeyboardManagerState): void {
206
+ keyboardState.keyHandlers.clear()
207
+ keyboardState.sequenceHandlers.clear()
208
+ if (keyboardState.sequenceTimer) {
209
+ clearTimeout(keyboardState.sequenceTimer)
210
+ keyboardState.sequenceTimer = null
211
+ }
212
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * @mdxui/terminal Mouse Handler
3
+ *
4
+ * Handles click events, clickable areas, and scroll events
5
+ * for the interactive renderer.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { ClickableOptions, MouseManagerState, FocusManagerState, CursorManagerState } from './types'
11
+ import { focusById } from './focus-manager'
12
+
13
+ /**
14
+ * Creates the mouse manager state
15
+ */
16
+ export function createMouseManagerState(): MouseManagerState {
17
+ return {
18
+ clickables: new Map<string, ClickableOptions>(),
19
+ clickHandlers: [],
20
+ scrollHandler: null,
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Register a click handler
26
+ */
27
+ export function onClick(
28
+ mouseState: MouseManagerState,
29
+ handler: (event: { x: number; y: number }) => void,
30
+ destroyed?: boolean
31
+ ): void {
32
+ if (destroyed) return
33
+ mouseState.clickHandlers.push(handler)
34
+ }
35
+
36
+ /**
37
+ * Unregister a click handler
38
+ */
39
+ export function offClick(
40
+ mouseState: MouseManagerState,
41
+ handler: (event: { x: number; y: number }) => void,
42
+ destroyed?: boolean
43
+ ): void {
44
+ if (destroyed) return
45
+ const idx = mouseState.clickHandlers.indexOf(handler)
46
+ if (idx !== -1) {
47
+ mouseState.clickHandlers.splice(idx, 1)
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Emit a click event
53
+ */
54
+ export function emitClick(
55
+ mouseState: MouseManagerState,
56
+ focusState: FocusManagerState,
57
+ cursorState: CursorManagerState,
58
+ x: number,
59
+ y: number,
60
+ options?: { clickCount?: number; button?: string },
61
+ destroyed?: boolean
62
+ ): void {
63
+ if (destroyed) return
64
+
65
+ // Find clickable at position with highest z-index
66
+ const clickableEntries = Array.from(mouseState.clickables.entries())
67
+ .filter(([, c]) => {
68
+ return x >= c.x && x < c.x + c.width && y >= c.y && y < c.y + c.height
69
+ })
70
+ .sort((a, b) => (b[1].zIndex ?? 0) - (a[1].zIndex ?? 0))
71
+
72
+ if (clickableEntries.length > 0) {
73
+ const [id, clickable] = clickableEntries[0]
74
+
75
+ // Handle focus
76
+ if (clickable.focusable !== false && focusState.focusables.has(id)) {
77
+ focusById(focusState, cursorState, id, false)
78
+ }
79
+
80
+ // Handle click type
81
+ if (options?.button === 'right' && clickable.onRightClick) {
82
+ clickable.onRightClick()
83
+ } else if (options?.clickCount === 2 && clickable.onDoubleClick) {
84
+ clickable.onDoubleClick()
85
+ } else if (clickable.onClick) {
86
+ clickable.onClick({ x, y, id })
87
+ }
88
+ }
89
+
90
+ // Call global click handlers
91
+ for (const handler of mouseState.clickHandlers) {
92
+ handler({ x, y })
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Register a clickable area
98
+ */
99
+ export function registerClickable(
100
+ mouseState: MouseManagerState,
101
+ id: string,
102
+ options: ClickableOptions,
103
+ destroyed?: boolean
104
+ ): void {
105
+ if (destroyed) return
106
+ mouseState.clickables.set(id, options)
107
+ }
108
+
109
+ /**
110
+ * Unregister a clickable area
111
+ */
112
+ export function unregisterClickable(
113
+ mouseState: MouseManagerState,
114
+ id: string,
115
+ destroyed?: boolean
116
+ ): void {
117
+ if (destroyed) return
118
+ mouseState.clickables.delete(id)
119
+ }
120
+
121
+ /**
122
+ * Get all clickable areas
123
+ */
124
+ export function getClickableAreas(
125
+ mouseState: MouseManagerState,
126
+ destroyed?: boolean
127
+ ): Array<{ id: string } & ClickableOptions> {
128
+ if (destroyed) return []
129
+ return Array.from(mouseState.clickables.entries()).map(([id, options]) => ({
130
+ id,
131
+ ...options,
132
+ }))
133
+ }
134
+
135
+ /**
136
+ * Register scroll handler
137
+ */
138
+ export function onScroll(
139
+ mouseState: MouseManagerState,
140
+ handler: (event: { deltaY: number }) => void,
141
+ destroyed?: boolean
142
+ ): void {
143
+ if (destroyed) return
144
+ mouseState.scrollHandler = handler
145
+ }
146
+
147
+ /**
148
+ * Emit scroll event
149
+ */
150
+ export function emitScroll(
151
+ mouseState: MouseManagerState,
152
+ _x: number,
153
+ _y: number,
154
+ options: { deltaY: number },
155
+ destroyed?: boolean
156
+ ): void {
157
+ if (destroyed) return
158
+ mouseState.scrollHandler?.(options)
159
+ }
160
+
161
+ /**
162
+ * Clear mouse state on destroy
163
+ */
164
+ export function clearMouseState(mouseState: MouseManagerState): void {
165
+ mouseState.clickables.clear()
166
+ mouseState.clickHandlers.length = 0
167
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @mdxui/terminal State Manager
3
+ *
4
+ * Handles component state tracking, subscriptions, and render callbacks
5
+ * for the interactive renderer.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { StateManagerState } from './types'
11
+
12
+ /**
13
+ * Creates the state manager state
14
+ */
15
+ export function createStateManagerState(targetFps: number): StateManagerState {
16
+ return {
17
+ state: new Map<string, unknown>(),
18
+ stateSubscribers: new Map<string, Set<(newValue: unknown, oldValue: unknown) => void>>(),
19
+ renderHandler: null,
20
+ targetFps,
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Set a state value
26
+ */
27
+ export function setState(
28
+ stateManager: StateManagerState,
29
+ key: string,
30
+ value: unknown,
31
+ destroyed?: boolean
32
+ ): void {
33
+ if (destroyed) return
34
+ const oldValue = stateManager.state.get(key)
35
+ stateManager.state.set(key, value)
36
+
37
+ // Notify subscribers
38
+ const subscribers = stateManager.stateSubscribers.get(key)
39
+ if (subscribers) {
40
+ for (const subscriber of subscribers) {
41
+ subscriber(value, oldValue)
42
+ }
43
+ }
44
+
45
+ // Trigger render
46
+ stateManager.renderHandler?.()
47
+ }
48
+
49
+ /**
50
+ * Get a state value
51
+ */
52
+ export function getState(
53
+ stateManager: StateManagerState,
54
+ key: string,
55
+ destroyed?: boolean
56
+ ): unknown {
57
+ if (destroyed) return undefined
58
+ return stateManager.state.get(key)
59
+ }
60
+
61
+ /**
62
+ * Subscribe to state changes
63
+ */
64
+ export function subscribe(
65
+ stateManager: StateManagerState,
66
+ key: string,
67
+ subscriber: (newValue: unknown, oldValue: unknown) => void,
68
+ destroyed?: boolean
69
+ ): () => void {
70
+ if (destroyed) return () => {}
71
+ if (!stateManager.stateSubscribers.has(key)) {
72
+ stateManager.stateSubscribers.set(key, new Set())
73
+ }
74
+ stateManager.stateSubscribers.get(key)!.add(subscriber)
75
+
76
+ return () => {
77
+ const subscribers = stateManager.stateSubscribers.get(key)
78
+ if (subscribers) {
79
+ subscribers.delete(subscriber)
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Register render handler
86
+ */
87
+ export function onRender(
88
+ stateManager: StateManagerState,
89
+ handler: () => void,
90
+ destroyed?: boolean
91
+ ): void {
92
+ if (destroyed) return
93
+ stateManager.renderHandler = handler
94
+ }
95
+
96
+ /**
97
+ * Get target FPS
98
+ */
99
+ export function getTargetFps(stateManager: StateManagerState): number {
100
+ return stateManager.targetFps
101
+ }
102
+
103
+ /**
104
+ * Clear state manager on destroy
105
+ */
106
+ export function clearStateManager(stateManager: StateManagerState): void {
107
+ stateManager.state.clear()
108
+ stateManager.stateSubscribers.clear()
109
+ }