@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,147 @@
1
+ /**
2
+ * @mdxui/terminal Cursor Manager
3
+ *
4
+ * Handles cursor position, visibility, style, and blinking
5
+ * for the interactive renderer.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { CursorManagerState, FocusManagerState } from './types'
11
+
12
+ /**
13
+ * Creates the cursor manager state
14
+ */
15
+ export function createCursorManagerState(): CursorManagerState {
16
+ return {
17
+ cursorPosition: { x: 0, y: 0 },
18
+ cursorVisible: true,
19
+ cursorStyle: 'block',
20
+ cursorBlinking: false,
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Set cursor position
26
+ */
27
+ export function setCursorPosition(
28
+ cursorState: CursorManagerState,
29
+ x: number,
30
+ y: number,
31
+ destroyed?: boolean
32
+ ): void {
33
+ if (destroyed) return
34
+ cursorState.cursorPosition = { x, y }
35
+ }
36
+
37
+ /**
38
+ * Get cursor position
39
+ */
40
+ export function getCursorPosition(
41
+ cursorState: CursorManagerState,
42
+ destroyed?: boolean
43
+ ): { x: number; y: number } {
44
+ if (destroyed) return { x: 0, y: 0 }
45
+ return { ...cursorState.cursorPosition }
46
+ }
47
+
48
+ /**
49
+ * Show the cursor
50
+ */
51
+ export function showCursor(
52
+ cursorState: CursorManagerState,
53
+ destroyed?: boolean
54
+ ): void {
55
+ if (destroyed) return
56
+ cursorState.cursorVisible = true
57
+ }
58
+
59
+ /**
60
+ * Hide the cursor
61
+ */
62
+ export function hideCursor(
63
+ cursorState: CursorManagerState,
64
+ destroyed?: boolean
65
+ ): void {
66
+ if (destroyed) return
67
+ cursorState.cursorVisible = false
68
+ }
69
+
70
+ /**
71
+ * Check if cursor is visible
72
+ */
73
+ export function isCursorVisible(
74
+ cursorState: CursorManagerState,
75
+ destroyed?: boolean
76
+ ): boolean {
77
+ if (destroyed) return false
78
+ return cursorState.cursorVisible
79
+ }
80
+
81
+ /**
82
+ * Set cursor style
83
+ */
84
+ export function setCursorStyle(
85
+ cursorState: CursorManagerState,
86
+ style: 'block' | 'underline' | 'bar',
87
+ destroyed?: boolean
88
+ ): void {
89
+ if (destroyed) return
90
+ cursorState.cursorStyle = style
91
+ }
92
+
93
+ /**
94
+ * Get cursor style
95
+ */
96
+ export function getCursorStyle(
97
+ cursorState: CursorManagerState,
98
+ destroyed?: boolean
99
+ ): string {
100
+ if (destroyed) return 'block'
101
+ return cursorState.cursorStyle
102
+ }
103
+
104
+ /**
105
+ * Set cursor blink state
106
+ */
107
+ export function setCursorBlink(
108
+ cursorState: CursorManagerState,
109
+ blink: boolean,
110
+ destroyed?: boolean
111
+ ): void {
112
+ if (destroyed) return
113
+ cursorState.cursorBlinking = blink
114
+ }
115
+
116
+ /**
117
+ * Check if cursor is blinking
118
+ */
119
+ export function isCursorBlinking(
120
+ cursorState: CursorManagerState,
121
+ destroyed?: boolean
122
+ ): boolean {
123
+ if (destroyed) return false
124
+ return cursorState.cursorBlinking
125
+ }
126
+
127
+ /**
128
+ * Update cursor position for a focusable element
129
+ */
130
+ export function updateCursorPosition(
131
+ cursorState: CursorManagerState,
132
+ focusState: FocusManagerState,
133
+ id: string,
134
+ position: { x: number; y: number },
135
+ destroyed?: boolean
136
+ ): void {
137
+ if (destroyed) return
138
+ // Update focusable's cursor position
139
+ const entry = focusState.focusables.get(id)
140
+ if (entry) {
141
+ entry.options.cursorPosition = position
142
+ }
143
+ // If this is the focused element, update cursor
144
+ if (focusState.focusedId === id) {
145
+ cursorState.cursorPosition = { ...position }
146
+ }
147
+ }
@@ -0,0 +1,279 @@
1
+ /**
2
+ * @mdxui/terminal Focus Manager
3
+ *
4
+ * Handles focus navigation, focus traps, and focus groups for the interactive renderer.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ import type {
10
+ FocusableOptions,
11
+ FocusableEntry,
12
+ FocusTrapEntry,
13
+ FocusManagerState,
14
+ CursorManagerState,
15
+ } from './types'
16
+
17
+ /**
18
+ * Creates the focus manager state
19
+ */
20
+ export function createFocusManagerState(wrapFocus: boolean): FocusManagerState {
21
+ return {
22
+ focusables: new Map<string, FocusableEntry>(),
23
+ focusedId: null,
24
+ activeGroup: null,
25
+ focusTrapStack: [],
26
+ registrationCounter: 0,
27
+ wrapFocus,
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Gets sorted focusable IDs based on tabIndex and registration order
33
+ */
34
+ export function getSortedFocusableIds(
35
+ focusState: FocusManagerState,
36
+ destroyed: boolean
37
+ ): string[] {
38
+ if (destroyed) return []
39
+
40
+ const { focusables, activeGroup, focusTrapStack } = focusState
41
+
42
+ const entries = Array.from(focusables.values())
43
+ .filter(e => {
44
+ // Exclude negative tabIndex
45
+ if (e.options.tabIndex < 0) return false
46
+ // Exclude disabled
47
+ if (e.options.disabled) return false
48
+ // Filter by active group if set
49
+ if (activeGroup !== null && e.options.group !== activeGroup) return false
50
+ // If in focus trap, only include elements from that group
51
+ if (focusTrapStack.length > 0) {
52
+ const currentTrap = focusTrapStack[focusTrapStack.length - 1]
53
+ if (e.options.group !== currentTrap.group) return false
54
+ }
55
+ return true
56
+ })
57
+ .sort((a, b) => {
58
+ // Sort by tabIndex first
59
+ if (a.options.tabIndex !== b.options.tabIndex) {
60
+ return a.options.tabIndex - b.options.tabIndex
61
+ }
62
+ // Then by registration order
63
+ return a.registrationOrder - b.registrationOrder
64
+ })
65
+
66
+ return entries.map(e => e.id)
67
+ }
68
+
69
+ /**
70
+ * Gets the current focus index
71
+ */
72
+ export function getCurrentFocusIndex(
73
+ focusState: FocusManagerState,
74
+ destroyed: boolean
75
+ ): number {
76
+ if (focusState.focusedId === null) return -1
77
+ const ids = getSortedFocusableIds(focusState, destroyed)
78
+ return ids.indexOf(focusState.focusedId)
79
+ }
80
+
81
+ /**
82
+ * Focus the next focusable element
83
+ */
84
+ export function focusNext(
85
+ focusState: FocusManagerState,
86
+ cursorState: CursorManagerState,
87
+ destroyed: boolean
88
+ ): void {
89
+ if (destroyed) return
90
+ const ids = getSortedFocusableIds(focusState, destroyed)
91
+ if (ids.length === 0) return
92
+
93
+ const currentIdx = getCurrentFocusIndex(focusState, destroyed)
94
+
95
+ if (currentIdx === -1) {
96
+ // Nothing focused, focus first
97
+ focusById(focusState, cursorState, ids[0], destroyed)
98
+ } else if (currentIdx < ids.length - 1) {
99
+ // Focus next
100
+ focusById(focusState, cursorState, ids[currentIdx + 1], destroyed)
101
+ } else if (focusState.wrapFocus) {
102
+ // Wrap to first
103
+ focusById(focusState, cursorState, ids[0], destroyed)
104
+ }
105
+ // If not wrapping and at end, stay at current
106
+ }
107
+
108
+ /**
109
+ * Focus the previous focusable element
110
+ */
111
+ export function focusPrev(
112
+ focusState: FocusManagerState,
113
+ cursorState: CursorManagerState,
114
+ destroyed: boolean
115
+ ): void {
116
+ if (destroyed) return
117
+ const ids = getSortedFocusableIds(focusState, destroyed)
118
+ if (ids.length === 0) return
119
+
120
+ const currentIdx = getCurrentFocusIndex(focusState, destroyed)
121
+
122
+ if (currentIdx === -1) {
123
+ // Nothing focused, focus last
124
+ focusById(focusState, cursorState, ids[ids.length - 1], destroyed)
125
+ } else if (currentIdx > 0) {
126
+ // Focus prev
127
+ focusById(focusState, cursorState, ids[currentIdx - 1], destroyed)
128
+ } else if (focusState.wrapFocus) {
129
+ // Wrap to last
130
+ focusById(focusState, cursorState, ids[ids.length - 1], destroyed)
131
+ }
132
+ // If not wrapping and at start, stay at current
133
+ }
134
+
135
+ /**
136
+ * Focus an element by ID
137
+ */
138
+ export function focusById(
139
+ focusState: FocusManagerState,
140
+ cursorState: CursorManagerState,
141
+ id: string,
142
+ destroyed: boolean
143
+ ): void {
144
+ if (destroyed) return
145
+ const entry = focusState.focusables.get(id)
146
+ if (!entry) return
147
+
148
+ // Skip if already focused
149
+ if (focusState.focusedId === id) return
150
+
151
+ // Blur previous
152
+ if (focusState.focusedId) {
153
+ const prevEntry = focusState.focusables.get(focusState.focusedId)
154
+ if (prevEntry?.options.onBlur) {
155
+ prevEntry.options.onBlur({ id: focusState.focusedId })
156
+ }
157
+ }
158
+
159
+ // Focus new
160
+ focusState.focusedId = id
161
+ if (entry.options.onFocus) {
162
+ entry.options.onFocus({ id })
163
+ }
164
+
165
+ // Update cursor
166
+ if (entry.options.cursorPosition) {
167
+ cursorState.cursorPosition = { ...entry.options.cursorPosition }
168
+ }
169
+ if (entry.options.showCursor !== undefined) {
170
+ cursorState.cursorVisible = entry.options.showCursor
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Get the currently focused ID
176
+ */
177
+ export function getFocusedId(
178
+ focusState: FocusManagerState,
179
+ destroyed: boolean
180
+ ): string | null {
181
+ if (destroyed) return null
182
+ return focusState.focusedId
183
+ }
184
+
185
+ /**
186
+ * Get all focusable IDs
187
+ */
188
+ export function getFocusableIds(
189
+ focusState: FocusManagerState,
190
+ destroyed: boolean
191
+ ): string[] {
192
+ return getSortedFocusableIds(focusState, destroyed)
193
+ }
194
+
195
+ /**
196
+ * Register a focusable element
197
+ */
198
+ export function registerFocusable(
199
+ focusState: FocusManagerState,
200
+ id: string,
201
+ options: FocusableOptions,
202
+ destroyed?: boolean
203
+ ): void {
204
+ if (destroyed) return
205
+ // Remove existing if present (handles re-registration)
206
+ focusState.focusables.delete(id)
207
+ focusState.focusables.set(id, {
208
+ id,
209
+ options,
210
+ registrationOrder: focusState.registrationCounter++,
211
+ })
212
+ }
213
+
214
+ /**
215
+ * Unregister a focusable element
216
+ */
217
+ export function unregisterFocusable(
218
+ focusState: FocusManagerState,
219
+ id: string,
220
+ destroyed?: boolean
221
+ ): void {
222
+ if (destroyed) return
223
+ focusState.focusables.delete(id)
224
+ if (focusState.focusedId === id) {
225
+ focusState.focusedId = null
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Set the active focus group
231
+ */
232
+ export function setActiveGroup(
233
+ focusState: FocusManagerState,
234
+ group: string,
235
+ destroyed: boolean
236
+ ): void {
237
+ if (destroyed) return
238
+ focusState.activeGroup = group
239
+ }
240
+
241
+ /**
242
+ * Push a focus trap onto the stack
243
+ */
244
+ export function pushFocusTrap(
245
+ focusState: FocusManagerState,
246
+ group: string,
247
+ destroyed: boolean
248
+ ): void {
249
+ if (destroyed) return
250
+ focusState.focusTrapStack.push({
251
+ group,
252
+ previousFocusId: focusState.focusedId,
253
+ })
254
+ focusState.activeGroup = group
255
+ }
256
+
257
+ /**
258
+ * Pop a focus trap from the stack
259
+ */
260
+ export function popFocusTrap(
261
+ focusState: FocusManagerState,
262
+ cursorState: CursorManagerState,
263
+ destroyed: boolean
264
+ ): void {
265
+ if (destroyed) return
266
+ const trap = focusState.focusTrapStack.pop()
267
+ if (trap) {
268
+ // Restore previous focus
269
+ if (trap.previousFocusId) {
270
+ focusById(focusState, cursorState, trap.previousFocusId, destroyed)
271
+ }
272
+ // Update active group
273
+ if (focusState.focusTrapStack.length > 0) {
274
+ focusState.activeGroup = focusState.focusTrapStack[focusState.focusTrapStack.length - 1].group
275
+ } else {
276
+ focusState.activeGroup = null
277
+ }
278
+ }
279
+ }