@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.
- package/README.md +571 -0
- package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
- package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
- package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
- package/dist/chunk-3EFDH7PK.js +5235 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3X5IR6WE.js +884 -0
- package/dist/chunk-4FV5ZDCE.js +5236 -0
- package/dist/chunk-4OVMSF2J.js +243 -0
- package/dist/chunk-63FEETIS.js +4048 -0
- package/dist/chunk-B43KP7XJ.js +884 -0
- package/dist/chunk-BMTJXWUV.js +655 -0
- package/dist/chunk-C3SVH4N7.js +882 -0
- package/dist/chunk-EVWR7Y47.js +874 -0
- package/dist/chunk-F6A5VWUC.js +1285 -0
- package/dist/chunk-FD7KW7GE.js +882 -0
- package/dist/chunk-GBQ6UD6I.js +655 -0
- package/dist/chunk-GMDD3M6U.js +5227 -0
- package/dist/chunk-JBHRXOXM.js +1058 -0
- package/dist/chunk-JFOO3EYO.js +1182 -0
- package/dist/chunk-JQ5H3WXL.js +1291 -0
- package/dist/chunk-JQD5NASE.js +234 -0
- package/dist/chunk-KRHJP5R7.js +592 -0
- package/dist/chunk-KWF6WVJE.js +962 -0
- package/dist/chunk-LHYQVN3H.js +1038 -0
- package/dist/chunk-M3TLQLGC.js +1032 -0
- package/dist/chunk-MVW4Q5OP.js +240 -0
- package/dist/chunk-NXCZSWLU.js +1294 -0
- package/dist/chunk-O25TNRO6.js +607 -0
- package/dist/chunk-PNECDA2I.js +884 -0
- package/dist/chunk-QIHWRLJR.js +962 -0
- package/dist/chunk-QW5YMQ7K.js +882 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/chunk-RP2MVQLR.js +962 -0
- package/dist/chunk-TP6RXGXA.js +1087 -0
- package/dist/chunk-TQQSTITZ.js +655 -0
- package/dist/chunk-X24GWXQV.js +1281 -0
- package/dist/components/index.d.ts +802 -0
- package/dist/components/index.js +149 -0
- package/dist/data/index.d.ts +2554 -0
- package/dist/data/index.js +51 -0
- package/dist/forms/index.d.ts +1596 -0
- package/dist/forms/index.js +464 -0
- package/dist/index-CQRFZntR.d.ts +867 -0
- package/dist/index.d.ts +579 -0
- package/dist/index.js +786 -0
- package/dist/interactive-D0JkWosD.d.ts +217 -0
- package/dist/keyboard/index.d.ts +2 -0
- package/dist/keyboard/index.js +43 -0
- package/dist/renderers/index.d.ts +546 -0
- package/dist/renderers/index.js +2157 -0
- package/dist/storybook/index.d.ts +396 -0
- package/dist/storybook/index.js +641 -0
- package/dist/theme/index.d.ts +1339 -0
- package/dist/theme/index.js +123 -0
- package/dist/types-Bxu5PAgA.d.ts +710 -0
- package/dist/types-CIlop5Ji.d.ts +701 -0
- package/dist/types-Ca8p_p5X.d.ts +710 -0
- package/package.json +90 -0
- package/src/__tests__/components/data/card.test.ts +458 -0
- package/src/__tests__/components/data/list.test.ts +473 -0
- package/src/__tests__/components/data/metrics.test.ts +541 -0
- package/src/__tests__/components/data/table.test.ts +448 -0
- package/src/__tests__/components/input/field.test.ts +555 -0
- package/src/__tests__/components/input/form.test.ts +870 -0
- package/src/__tests__/components/input/search.test.ts +1238 -0
- package/src/__tests__/components/input/select.test.ts +658 -0
- package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
- package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
- package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
- package/src/__tests__/components/navigation/tabs.test.ts +995 -0
- package/src/__tests__/components.test.tsx +1197 -0
- package/src/__tests__/core/compiler.test.ts +986 -0
- package/src/__tests__/core/parser.test.ts +785 -0
- package/src/__tests__/core/tier-switcher.test.ts +1103 -0
- package/src/__tests__/core/types.test.ts +1398 -0
- package/src/__tests__/data/collections.test.ts +1337 -0
- package/src/__tests__/data/db.test.ts +1265 -0
- package/src/__tests__/data/reactive.test.ts +1010 -0
- package/src/__tests__/data/sync.test.ts +1614 -0
- package/src/__tests__/errors.test.ts +660 -0
- package/src/__tests__/forms/integration.test.ts +444 -0
- package/src/__tests__/integration.test.ts +905 -0
- package/src/__tests__/keyboard.test.ts +1791 -0
- package/src/__tests__/renderer.test.ts +489 -0
- package/src/__tests__/renderers/ansi-css.test.ts +948 -0
- package/src/__tests__/renderers/ansi.test.ts +1366 -0
- package/src/__tests__/renderers/ascii.test.ts +1360 -0
- package/src/__tests__/renderers/interactive.test.ts +2353 -0
- package/src/__tests__/renderers/markdown.test.ts +1483 -0
- package/src/__tests__/renderers/text.test.ts +1369 -0
- package/src/__tests__/renderers/unicode.test.ts +1307 -0
- package/src/__tests__/theme.test.ts +639 -0
- package/src/__tests__/utils/assertions.ts +685 -0
- package/src/__tests__/utils/index.ts +115 -0
- package/src/__tests__/utils/test-renderer.ts +381 -0
- package/src/__tests__/utils/utils.test.ts +560 -0
- package/src/components/containers/card.ts +56 -0
- package/src/components/containers/dialog.ts +53 -0
- package/src/components/containers/index.ts +9 -0
- package/src/components/containers/panel.ts +59 -0
- package/src/components/feedback/badge.ts +40 -0
- package/src/components/feedback/index.ts +8 -0
- package/src/components/feedback/spinner.ts +23 -0
- package/src/components/helpers.ts +81 -0
- package/src/components/index.ts +153 -0
- package/src/components/layout/breadcrumb.ts +31 -0
- package/src/components/layout/index.ts +10 -0
- package/src/components/layout/list.ts +29 -0
- package/src/components/layout/sidebar.ts +79 -0
- package/src/components/layout/table.ts +62 -0
- package/src/components/primitives/box.ts +95 -0
- package/src/components/primitives/button.ts +54 -0
- package/src/components/primitives/index.ts +11 -0
- package/src/components/primitives/input.ts +88 -0
- package/src/components/primitives/select.ts +97 -0
- package/src/components/primitives/text.ts +60 -0
- package/src/components/render.ts +155 -0
- package/src/components/templates/app.ts +43 -0
- package/src/components/templates/index.ts +8 -0
- package/src/components/templates/site.ts +54 -0
- package/src/components/types.ts +777 -0
- package/src/core/compiler.ts +718 -0
- package/src/core/parser.ts +127 -0
- package/src/core/tier-switcher.ts +607 -0
- package/src/core/types.ts +672 -0
- package/src/data/collection.ts +316 -0
- package/src/data/collections.ts +50 -0
- package/src/data/context.tsx +174 -0
- package/src/data/db.ts +127 -0
- package/src/data/hooks.ts +532 -0
- package/src/data/index.ts +138 -0
- package/src/data/reactive.ts +1225 -0
- package/src/data/saas-collections.ts +375 -0
- package/src/data/sync.ts +1213 -0
- package/src/data/types.ts +660 -0
- package/src/forms/converters.ts +512 -0
- package/src/forms/index.ts +133 -0
- package/src/forms/schemas.ts +403 -0
- package/src/forms/types.ts +476 -0
- package/src/index.ts +542 -0
- package/src/keyboard/focus.ts +748 -0
- package/src/keyboard/index.ts +96 -0
- package/src/keyboard/integration.ts +371 -0
- package/src/keyboard/manager.ts +377 -0
- package/src/keyboard/presets.ts +90 -0
- package/src/renderers/ansi-css.ts +576 -0
- package/src/renderers/ansi.ts +802 -0
- package/src/renderers/ascii.ts +680 -0
- package/src/renderers/breadcrumb.ts +480 -0
- package/src/renderers/command-palette.ts +802 -0
- package/src/renderers/components/field.ts +210 -0
- package/src/renderers/components/form.ts +327 -0
- package/src/renderers/components/index.ts +21 -0
- package/src/renderers/components/search.ts +449 -0
- package/src/renderers/components/select.ts +222 -0
- package/src/renderers/index.ts +101 -0
- package/src/renderers/interactive/component-handlers.ts +622 -0
- package/src/renderers/interactive/cursor-manager.ts +147 -0
- package/src/renderers/interactive/focus-manager.ts +279 -0
- package/src/renderers/interactive/index.ts +661 -0
- package/src/renderers/interactive/input-handler.ts +164 -0
- package/src/renderers/interactive/keyboard-handler.ts +212 -0
- package/src/renderers/interactive/mouse-handler.ts +167 -0
- package/src/renderers/interactive/state-manager.ts +109 -0
- package/src/renderers/interactive/types.ts +338 -0
- package/src/renderers/interactive-string.ts +299 -0
- package/src/renderers/interactive.ts +59 -0
- package/src/renderers/markdown.ts +950 -0
- package/src/renderers/sidebar.ts +549 -0
- package/src/renderers/tabs.ts +682 -0
- package/src/renderers/text.ts +791 -0
- package/src/renderers/unicode.ts +917 -0
- package/src/renderers/utils.ts +942 -0
- package/src/router/adapters.ts +383 -0
- package/src/router/types.ts +140 -0
- package/src/router/utils.ts +452 -0
- package/src/schemas.ts +205 -0
- package/src/storybook/index.ts +91 -0
- package/src/storybook/interactive-decorator.tsx +659 -0
- package/src/storybook/keyboard-simulator.ts +501 -0
- package/src/theme/ansi-codes.ts +80 -0
- package/src/theme/box-drawing.ts +132 -0
- package/src/theme/color-convert.ts +254 -0
- package/src/theme/color-support.ts +321 -0
- package/src/theme/index.ts +134 -0
- package/src/theme/strip-ansi.ts +50 -0
- package/src/theme/tailwind-map.ts +469 -0
- package/src/theme/text-styles.ts +206 -0
- package/src/theme/theme-system.ts +568 -0
- package/src/types.ts +103 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal Router Adapters
|
|
3
|
+
*
|
|
4
|
+
* Pre-built router adapters for common routing libraries and patterns.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
RouterAdapter,
|
|
9
|
+
RouteMatchMode,
|
|
10
|
+
RouterConfig,
|
|
11
|
+
MemoryRouterState,
|
|
12
|
+
HistoryEntry,
|
|
13
|
+
} from './types'
|
|
14
|
+
import { matchPath, normalizePath } from './utils'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a callback-based router adapter.
|
|
18
|
+
* Use when you want to handle navigation with custom callbacks.
|
|
19
|
+
*
|
|
20
|
+
* @param config - Configuration with callbacks
|
|
21
|
+
* @returns RouterAdapter instance
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const [currentPath, setCurrentPath] = useState('/')
|
|
26
|
+
*
|
|
27
|
+
* const router = createCallbackRouterAdapter({
|
|
28
|
+
* currentPath,
|
|
29
|
+
* onNavigate: setCurrentPath,
|
|
30
|
+
* })
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function createCallbackRouterAdapter(config: {
|
|
34
|
+
currentPath?: string
|
|
35
|
+
onNavigate?: (path: string) => void
|
|
36
|
+
onReplace?: (path: string) => void
|
|
37
|
+
}): RouterAdapter {
|
|
38
|
+
return {
|
|
39
|
+
getCurrentPath: () => config.currentPath || '/',
|
|
40
|
+
navigate: (path: string) => config.onNavigate?.(path),
|
|
41
|
+
replace: (path: string) => (config.onReplace || config.onNavigate)?.(path),
|
|
42
|
+
isActive: (path: string, mode: RouteMatchMode = 'exact') => {
|
|
43
|
+
const current = config.currentPath || '/'
|
|
44
|
+
return matchPath(current, path, mode)
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates an in-memory router adapter with full history support.
|
|
51
|
+
* Useful for testing, CLI applications, and server-side rendering.
|
|
52
|
+
*
|
|
53
|
+
* @param config - Router configuration
|
|
54
|
+
* @returns RouterAdapter with additional memory router methods
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const router = createMemoryRouterAdapter({ initialPath: '/home' })
|
|
59
|
+
*
|
|
60
|
+
* router.navigate('/products')
|
|
61
|
+
* router.back() // Returns to /home
|
|
62
|
+
*
|
|
63
|
+
* const unsubscribe = router.subscribe?.((path) => {
|
|
64
|
+
* console.log('Navigated to:', path)
|
|
65
|
+
* })
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function createMemoryRouterAdapter(config?: RouterConfig): RouterAdapter & {
|
|
69
|
+
getState(): MemoryRouterState
|
|
70
|
+
getHistory(): HistoryEntry[]
|
|
71
|
+
reset(path?: string): void
|
|
72
|
+
} {
|
|
73
|
+
const state: MemoryRouterState = {
|
|
74
|
+
entries: [{
|
|
75
|
+
path: normalizePath(config?.initialPath || '/'),
|
|
76
|
+
timestamp: Date.now(),
|
|
77
|
+
}],
|
|
78
|
+
index: 0,
|
|
79
|
+
listeners: new Set(),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const basePath = config?.basePath || ''
|
|
83
|
+
const defaultMode = config?.defaultMatchMode || 'exact'
|
|
84
|
+
|
|
85
|
+
function notifyListeners() {
|
|
86
|
+
const currentPath = state.entries[state.index]?.path || '/'
|
|
87
|
+
state.listeners.forEach(listener => listener(currentPath))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resolvePath(path: string): string {
|
|
91
|
+
if (basePath && !path.startsWith(basePath)) {
|
|
92
|
+
return normalizePath(basePath + path)
|
|
93
|
+
}
|
|
94
|
+
return normalizePath(path)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
getCurrentPath: () => state.entries[state.index]?.path || '/',
|
|
99
|
+
|
|
100
|
+
navigate: (path: string) => {
|
|
101
|
+
const resolved = resolvePath(path)
|
|
102
|
+
// Truncate forward history when navigating from a point in history
|
|
103
|
+
state.entries = state.entries.slice(0, state.index + 1)
|
|
104
|
+
state.entries.push({ path: resolved, timestamp: Date.now() })
|
|
105
|
+
state.index = state.entries.length - 1
|
|
106
|
+
notifyListeners()
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
replace: (path: string) => {
|
|
110
|
+
const resolved = resolvePath(path)
|
|
111
|
+
state.entries[state.index] = { path: resolved, timestamp: Date.now() }
|
|
112
|
+
notifyListeners()
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
isActive: (path: string, mode: RouteMatchMode = defaultMode) => {
|
|
116
|
+
const current = state.entries[state.index]?.path || '/'
|
|
117
|
+
return matchPath(current, resolvePath(path), mode)
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
subscribe: (callback: (path: string) => void) => {
|
|
121
|
+
state.listeners.add(callback)
|
|
122
|
+
return () => {
|
|
123
|
+
state.listeners.delete(callback)
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
back: () => {
|
|
128
|
+
if (state.index > 0) {
|
|
129
|
+
state.index--
|
|
130
|
+
notifyListeners()
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
forward: () => {
|
|
135
|
+
if (state.index < state.entries.length - 1) {
|
|
136
|
+
state.index++
|
|
137
|
+
notifyListeners()
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
getParams: () => ({}), // Would need route patterns to extract params
|
|
142
|
+
|
|
143
|
+
getQuery: () => {
|
|
144
|
+
const current = state.entries[state.index]?.path || '/'
|
|
145
|
+
const queryStart = current.indexOf('?')
|
|
146
|
+
if (queryStart === -1) return {}
|
|
147
|
+
|
|
148
|
+
const queryString = current.slice(queryStart + 1)
|
|
149
|
+
const params: Record<string, string> = {}
|
|
150
|
+
for (const pair of queryString.split('&')) {
|
|
151
|
+
const [key, value] = pair.split('=')
|
|
152
|
+
if (key) {
|
|
153
|
+
params[decodeURIComponent(key)] = decodeURIComponent(value || '')
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return params
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// Memory router specific methods
|
|
160
|
+
getState: () => ({ ...state, listeners: new Set(state.listeners) }),
|
|
161
|
+
getHistory: () => [...state.entries],
|
|
162
|
+
reset: (path?: string) => {
|
|
163
|
+
state.entries = [{
|
|
164
|
+
path: normalizePath(path || config?.initialPath || '/'),
|
|
165
|
+
timestamp: Date.now(),
|
|
166
|
+
}]
|
|
167
|
+
state.index = 0
|
|
168
|
+
notifyListeners()
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Creates a URL-sync router adapter that syncs with browser URL.
|
|
175
|
+
* Falls back to in-memory routing when window is not available.
|
|
176
|
+
*
|
|
177
|
+
* @param config - Router configuration
|
|
178
|
+
* @returns RouterAdapter that syncs with browser URL
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```typescript
|
|
182
|
+
* const router = createUrlSyncRouterAdapter({
|
|
183
|
+
* basePath: '/app',
|
|
184
|
+
* })
|
|
185
|
+
*
|
|
186
|
+
* // Navigating updates browser URL
|
|
187
|
+
* router.navigate('/dashboard')
|
|
188
|
+
* // URL becomes: /app/dashboard
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export function createUrlSyncRouterAdapter(config?: RouterConfig): RouterAdapter {
|
|
192
|
+
// Check if we're in a browser environment
|
|
193
|
+
const isBrowser = typeof window !== 'undefined' && typeof window.history !== 'undefined'
|
|
194
|
+
|
|
195
|
+
if (!isBrowser) {
|
|
196
|
+
// Fall back to memory router in non-browser environments
|
|
197
|
+
return createMemoryRouterAdapter(config)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const basePath = config?.basePath || ''
|
|
201
|
+
const defaultMode = config?.defaultMatchMode || 'exact'
|
|
202
|
+
const listeners = new Set<(path: string) => void>()
|
|
203
|
+
|
|
204
|
+
function getCurrentPath(): string {
|
|
205
|
+
const path = window.location.pathname
|
|
206
|
+
if (basePath && path.startsWith(basePath)) {
|
|
207
|
+
return normalizePath(path.slice(basePath.length) || '/')
|
|
208
|
+
}
|
|
209
|
+
return normalizePath(path)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function resolvePath(path: string): string {
|
|
213
|
+
if (basePath) {
|
|
214
|
+
return basePath + normalizePath(path)
|
|
215
|
+
}
|
|
216
|
+
return normalizePath(path)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Listen for popstate events (back/forward)
|
|
220
|
+
if (isBrowser) {
|
|
221
|
+
window.addEventListener('popstate', () => {
|
|
222
|
+
const path = getCurrentPath()
|
|
223
|
+
listeners.forEach(listener => listener(path))
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
getCurrentPath,
|
|
229
|
+
|
|
230
|
+
navigate: (path: string) => {
|
|
231
|
+
const resolved = resolvePath(path)
|
|
232
|
+
window.history.pushState(null, '', resolved)
|
|
233
|
+
listeners.forEach(listener => listener(normalizePath(path)))
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
replace: (path: string) => {
|
|
237
|
+
const resolved = resolvePath(path)
|
|
238
|
+
window.history.replaceState(null, '', resolved)
|
|
239
|
+
listeners.forEach(listener => listener(normalizePath(path)))
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
isActive: (path: string, mode: RouteMatchMode = defaultMode) => {
|
|
243
|
+
return matchPath(getCurrentPath(), path, mode)
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
subscribe: (callback: (path: string) => void) => {
|
|
247
|
+
listeners.add(callback)
|
|
248
|
+
return () => {
|
|
249
|
+
listeners.delete(callback)
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
back: () => {
|
|
254
|
+
window.history.back()
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
forward: () => {
|
|
258
|
+
window.history.forward()
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
getParams: () => ({}),
|
|
262
|
+
|
|
263
|
+
getQuery: () => {
|
|
264
|
+
const params: Record<string, string> = {}
|
|
265
|
+
const searchParams = new URLSearchParams(window.location.search)
|
|
266
|
+
searchParams.forEach((value, key) => {
|
|
267
|
+
params[key] = value
|
|
268
|
+
})
|
|
269
|
+
return params
|
|
270
|
+
},
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Creates a hash-based router adapter using URL hash for routing.
|
|
276
|
+
* Useful for static hosting where server-side routing is not available.
|
|
277
|
+
*
|
|
278
|
+
* @param config - Router configuration
|
|
279
|
+
* @returns RouterAdapter that uses URL hash
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```typescript
|
|
283
|
+
* const router = createHashRouterAdapter()
|
|
284
|
+
*
|
|
285
|
+
* router.navigate('/products')
|
|
286
|
+
* // URL becomes: /#/products
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
export function createHashRouterAdapter(config?: RouterConfig): RouterAdapter {
|
|
290
|
+
// Check if we're in a browser environment
|
|
291
|
+
const isBrowser = typeof window !== 'undefined'
|
|
292
|
+
|
|
293
|
+
if (!isBrowser) {
|
|
294
|
+
return createMemoryRouterAdapter(config)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const defaultMode = config?.defaultMatchMode || 'exact'
|
|
298
|
+
const listeners = new Set<(path: string) => void>()
|
|
299
|
+
|
|
300
|
+
function getCurrentPath(): string {
|
|
301
|
+
const hash = window.location.hash
|
|
302
|
+
const path = hash.startsWith('#') ? hash.slice(1) : hash
|
|
303
|
+
return normalizePath(path || '/')
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Listen for hashchange events
|
|
307
|
+
if (isBrowser) {
|
|
308
|
+
window.addEventListener('hashchange', () => {
|
|
309
|
+
const path = getCurrentPath()
|
|
310
|
+
listeners.forEach(listener => listener(path))
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
getCurrentPath,
|
|
316
|
+
|
|
317
|
+
navigate: (path: string) => {
|
|
318
|
+
window.location.hash = normalizePath(path)
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
replace: (path: string) => {
|
|
322
|
+
const url = new URL(window.location.href)
|
|
323
|
+
url.hash = normalizePath(path)
|
|
324
|
+
window.history.replaceState(null, '', url.toString())
|
|
325
|
+
listeners.forEach(listener => listener(normalizePath(path)))
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
isActive: (path: string, mode: RouteMatchMode = defaultMode) => {
|
|
329
|
+
return matchPath(getCurrentPath(), path, mode)
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
subscribe: (callback: (path: string) => void) => {
|
|
333
|
+
listeners.add(callback)
|
|
334
|
+
return () => {
|
|
335
|
+
listeners.delete(callback)
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
back: () => {
|
|
340
|
+
window.history.back()
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
forward: () => {
|
|
344
|
+
window.history.forward()
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
getParams: () => ({}),
|
|
348
|
+
|
|
349
|
+
getQuery: () => {
|
|
350
|
+
const hash = window.location.hash
|
|
351
|
+
const queryStart = hash.indexOf('?')
|
|
352
|
+
if (queryStart === -1) return {}
|
|
353
|
+
|
|
354
|
+
const params: Record<string, string> = {}
|
|
355
|
+
const searchParams = new URLSearchParams(hash.slice(queryStart + 1))
|
|
356
|
+
searchParams.forEach((value, key) => {
|
|
357
|
+
params[key] = value
|
|
358
|
+
})
|
|
359
|
+
return params
|
|
360
|
+
},
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Creates a static router adapter for server-side rendering or testing.
|
|
366
|
+
* The path never changes - it's purely read-only.
|
|
367
|
+
*
|
|
368
|
+
* @param path - Static path to use
|
|
369
|
+
* @returns Read-only RouterAdapter
|
|
370
|
+
*/
|
|
371
|
+
export function createStaticRouterAdapter(path: string = '/'): RouterAdapter {
|
|
372
|
+
const normalizedPath = normalizePath(path)
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
getCurrentPath: () => normalizedPath,
|
|
376
|
+
navigate: () => {
|
|
377
|
+
// No-op in static router
|
|
378
|
+
},
|
|
379
|
+
isActive: (targetPath: string, mode: RouteMatchMode = 'exact') => {
|
|
380
|
+
return matchPath(normalizedPath, targetPath, mode)
|
|
381
|
+
},
|
|
382
|
+
}
|
|
383
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal Router Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the router integration system.
|
|
5
|
+
* These types enable router-agnostic navigation across all navigation components.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Route matching mode for navigation components.
|
|
10
|
+
* - `exact`: Path must match exactly
|
|
11
|
+
* - `prefix`: Path starts with target (for nested routes)
|
|
12
|
+
* - `pattern`: Path matches pattern with :param and * wildcards
|
|
13
|
+
*/
|
|
14
|
+
export type RouteMatchMode = 'exact' | 'prefix' | 'pattern'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Router adapter interface - implement this to integrate with any router library.
|
|
18
|
+
* This abstraction allows components to work with React Router, Next.js, Vue Router,
|
|
19
|
+
* or any custom routing solution.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // React Router adapter
|
|
24
|
+
* const reactRouterAdapter: RouterAdapter = {
|
|
25
|
+
* getCurrentPath: () => location.pathname,
|
|
26
|
+
* navigate: (path) => navigate(path),
|
|
27
|
+
* isActive: (path, mode) => matchPath(location.pathname, path, mode),
|
|
28
|
+
* subscribe: (callback) => {
|
|
29
|
+
* return history.listen(({ location }) => callback(location.pathname))
|
|
30
|
+
* },
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* // Next.js adapter
|
|
34
|
+
* const nextAdapter: RouterAdapter = {
|
|
35
|
+
* getCurrentPath: () => router.asPath,
|
|
36
|
+
* navigate: (path) => router.push(path),
|
|
37
|
+
* isActive: (path, mode) => matchPath(router.asPath, path, mode),
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export interface RouterAdapter {
|
|
42
|
+
/** Get the current path/location */
|
|
43
|
+
getCurrentPath(): string
|
|
44
|
+
/** Navigate to a path */
|
|
45
|
+
navigate(path: string): void
|
|
46
|
+
/** Check if a path matches the current route */
|
|
47
|
+
isActive(path: string, mode?: RouteMatchMode): boolean
|
|
48
|
+
/** Subscribe to route changes (returns unsubscribe function) */
|
|
49
|
+
subscribe?(callback: (path: string) => void): () => void
|
|
50
|
+
/** Replace current history entry instead of push */
|
|
51
|
+
replace?(path: string): void
|
|
52
|
+
/** Go back in history */
|
|
53
|
+
back?(): void
|
|
54
|
+
/** Go forward in history */
|
|
55
|
+
forward?(): void
|
|
56
|
+
/** Get route parameters */
|
|
57
|
+
getParams?(): Record<string, string>
|
|
58
|
+
/** Get query parameters */
|
|
59
|
+
getQuery?(): Record<string, string>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Router context value for React context integration.
|
|
64
|
+
*/
|
|
65
|
+
export interface RouterContextValue {
|
|
66
|
+
/** Current path */
|
|
67
|
+
path: string
|
|
68
|
+
/** Router adapter instance */
|
|
69
|
+
adapter: RouterAdapter
|
|
70
|
+
/** Route parameters */
|
|
71
|
+
params: Record<string, string>
|
|
72
|
+
/** Query parameters */
|
|
73
|
+
query: Record<string, string>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Configuration for creating router adapters.
|
|
78
|
+
*/
|
|
79
|
+
export interface RouterConfig {
|
|
80
|
+
/** Initial path */
|
|
81
|
+
initialPath?: string
|
|
82
|
+
/** Base path for all routes */
|
|
83
|
+
basePath?: string
|
|
84
|
+
/** Default route match mode */
|
|
85
|
+
defaultMatchMode?: RouteMatchMode
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* History entry for in-memory router.
|
|
90
|
+
*/
|
|
91
|
+
export interface HistoryEntry {
|
|
92
|
+
path: string
|
|
93
|
+
state?: unknown
|
|
94
|
+
timestamp: number
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* In-memory router state.
|
|
99
|
+
*/
|
|
100
|
+
export interface MemoryRouterState {
|
|
101
|
+
entries: HistoryEntry[]
|
|
102
|
+
index: number
|
|
103
|
+
listeners: Set<(path: string) => void>
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Navigation item with optional path for router integration.
|
|
108
|
+
*/
|
|
109
|
+
export interface NavigableItem {
|
|
110
|
+
id: string
|
|
111
|
+
path?: string
|
|
112
|
+
label?: string
|
|
113
|
+
icon?: string
|
|
114
|
+
disabled?: boolean
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Breadcrumb segment with path information.
|
|
119
|
+
*/
|
|
120
|
+
export interface BreadcrumbSegment {
|
|
121
|
+
label: string
|
|
122
|
+
path?: string
|
|
123
|
+
icon?: string
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Options for generating breadcrumbs from a path.
|
|
128
|
+
*/
|
|
129
|
+
export interface BreadcrumbGeneratorOptions {
|
|
130
|
+
/** Custom labels for specific paths (e.g., { '/products': 'Shop' }) */
|
|
131
|
+
labels?: Record<string, string>
|
|
132
|
+
/** Function to generate label from segment (default: capitalize) */
|
|
133
|
+
labelGenerator?: (segment: string, fullPath: string) => string
|
|
134
|
+
/** Whether to include home segment */
|
|
135
|
+
includeHome?: boolean
|
|
136
|
+
/** Home segment label */
|
|
137
|
+
homeLabel?: string
|
|
138
|
+
/** Home segment icon */
|
|
139
|
+
homeIcon?: string
|
|
140
|
+
}
|