@zenithbuild/language-server 0.2.2
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/.github/workflows/release.yml +254 -0
- package/.releaserc.json +73 -0
- package/CHANGELOG.md +37 -0
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/bun.lock +83 -0
- package/package.json +34 -0
- package/scripts/release.ts +554 -0
- package/src/diagnostics.ts +260 -0
- package/src/imports.ts +207 -0
- package/src/metadata/core-imports.ts +163 -0
- package/src/metadata/directive-metadata.ts +109 -0
- package/src/metadata/plugin-imports.ts +116 -0
- package/src/project.ts +167 -0
- package/src/router.ts +180 -0
- package/src/server.ts +841 -0
- package/test/fixtures/content-plugin.zen +77 -0
- package/test/fixtures/core-only.zen +59 -0
- package/test/fixtures/no-plugins.zen +115 -0
- package/test/fixtures/router-enabled.zen +76 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directive Metadata
|
|
3
|
+
*
|
|
4
|
+
* Compile-time directive definitions for Zenith.
|
|
5
|
+
* These are compiler directives, not runtime attributes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface DirectiveMetadata {
|
|
9
|
+
name: string;
|
|
10
|
+
category: 'control-flow' | 'iteration' | 'reactive-effect' | 'conditional-visibility';
|
|
11
|
+
description: string;
|
|
12
|
+
syntax: string;
|
|
13
|
+
placement: ('element' | 'component')[];
|
|
14
|
+
example: string;
|
|
15
|
+
createsScope?: boolean;
|
|
16
|
+
scopeVariables?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* All Zenith compiler directives
|
|
21
|
+
*
|
|
22
|
+
* These are processed at compile-time and transformed into static DOM instructions.
|
|
23
|
+
* The LSP must describe these directives without assuming runtime behavior.
|
|
24
|
+
*/
|
|
25
|
+
export const DIRECTIVES: Record<string, DirectiveMetadata> = {
|
|
26
|
+
'zen:if': {
|
|
27
|
+
name: 'zen:if',
|
|
28
|
+
category: 'control-flow',
|
|
29
|
+
description: 'Compile-time conditional directive. Conditionally renders the element based on a boolean expression.',
|
|
30
|
+
syntax: 'zen:if="condition"',
|
|
31
|
+
placement: ['element', 'component'],
|
|
32
|
+
example: '<div zen:if="isVisible">Conditionally rendered</div>'
|
|
33
|
+
},
|
|
34
|
+
'zen:for': {
|
|
35
|
+
name: 'zen:for',
|
|
36
|
+
category: 'iteration',
|
|
37
|
+
description: 'Compile-time iteration directive. Repeats the element for each item in a collection.',
|
|
38
|
+
syntax: 'zen:for="item in items" or zen:for="item, index in items"',
|
|
39
|
+
placement: ['element', 'component'],
|
|
40
|
+
example: '<li zen:for="item in items">{item.name}</li>',
|
|
41
|
+
createsScope: true,
|
|
42
|
+
scopeVariables: ['item', 'index']
|
|
43
|
+
},
|
|
44
|
+
'zen:effect': {
|
|
45
|
+
name: 'zen:effect',
|
|
46
|
+
category: 'reactive-effect',
|
|
47
|
+
description: 'Compile-time reactive effect directive. Attaches a side effect to the element lifecycle.',
|
|
48
|
+
syntax: 'zen:effect="expression"',
|
|
49
|
+
placement: ['element', 'component'],
|
|
50
|
+
example: '<div zen:effect="console.log(\'rendered\')">Content</div>'
|
|
51
|
+
},
|
|
52
|
+
'zen:show': {
|
|
53
|
+
name: 'zen:show',
|
|
54
|
+
category: 'conditional-visibility',
|
|
55
|
+
description: 'Compile-time visibility directive. Toggles element visibility without removing from DOM.',
|
|
56
|
+
syntax: 'zen:show="condition"',
|
|
57
|
+
placement: ['element', 'component'],
|
|
58
|
+
example: '<div zen:show="isOpen">Toggle visibility</div>'
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if a string is a valid directive name
|
|
64
|
+
*/
|
|
65
|
+
export function isDirective(name: string): name is keyof typeof DIRECTIVES {
|
|
66
|
+
return name in DIRECTIVES;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get directive metadata by name
|
|
71
|
+
*/
|
|
72
|
+
export function getDirective(name: string): DirectiveMetadata | undefined {
|
|
73
|
+
return DIRECTIVES[name];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get all directive names
|
|
78
|
+
*/
|
|
79
|
+
export function getDirectiveNames(): string[] {
|
|
80
|
+
return Object.keys(DIRECTIVES);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if a directive can be placed on a specific element type
|
|
85
|
+
*/
|
|
86
|
+
export function canPlaceDirective(directiveName: string, elementType: 'element' | 'component' | 'slot'): boolean {
|
|
87
|
+
const directive = DIRECTIVES[directiveName];
|
|
88
|
+
if (!directive) return false;
|
|
89
|
+
|
|
90
|
+
// Directives cannot be placed on <slot>
|
|
91
|
+
if (elementType === 'slot') return false;
|
|
92
|
+
|
|
93
|
+
return directive.placement.includes(elementType as 'element' | 'component');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parse a zen:for expression to extract variables
|
|
98
|
+
*/
|
|
99
|
+
export function parseForExpression(expression: string): { itemVar: string; indexVar?: string; source: string } | null {
|
|
100
|
+
// Match: "item in items" or "item, index in items"
|
|
101
|
+
const match = expression.match(/^\s*([a-zA-Z_$][\w$]*)(?:\s*,\s*([a-zA-Z_$][\w$]*))?\s+in\s+(.+)\s*$/);
|
|
102
|
+
if (!match) return null;
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
itemVar: match[1],
|
|
106
|
+
indexVar: match[2],
|
|
107
|
+
source: match[3].trim()
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Import Metadata
|
|
3
|
+
*
|
|
4
|
+
* Static metadata for Zenith plugin modules.
|
|
5
|
+
* Plugin modules use the zenith:* namespace.
|
|
6
|
+
*
|
|
7
|
+
* Important: The LSP must NOT assume plugin presence.
|
|
8
|
+
* If a plugin is not installed, diagnostics should be soft warnings, not errors.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface PluginExport {
|
|
12
|
+
name: string;
|
|
13
|
+
kind: 'function' | 'type' | 'variable';
|
|
14
|
+
description: string;
|
|
15
|
+
signature?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PluginModuleMetadata {
|
|
19
|
+
module: string;
|
|
20
|
+
description: string;
|
|
21
|
+
exports: PluginExport[];
|
|
22
|
+
required: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Known Zenith plugin modules
|
|
27
|
+
*/
|
|
28
|
+
export const PLUGIN_MODULES: Record<string, PluginModuleMetadata> = {
|
|
29
|
+
'zenith:content': {
|
|
30
|
+
module: 'zenith:content',
|
|
31
|
+
description: 'Content collections plugin for Zenith. Provides type-safe content management for Markdown, MDX, and JSON files.',
|
|
32
|
+
exports: [
|
|
33
|
+
{
|
|
34
|
+
name: 'zenCollection',
|
|
35
|
+
kind: 'function',
|
|
36
|
+
description: 'Define a content collection with schema validation.',
|
|
37
|
+
signature: 'zenCollection<T>(options: { name: string; schema: T }): Collection<T>'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'getCollection',
|
|
41
|
+
kind: 'function',
|
|
42
|
+
description: 'Get all entries from a content collection.',
|
|
43
|
+
signature: 'getCollection(name: string): Promise<CollectionEntry[]>'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'getEntry',
|
|
47
|
+
kind: 'function',
|
|
48
|
+
description: 'Get a single entry from a content collection.',
|
|
49
|
+
signature: 'getEntry(collection: string, slug: string): Promise<CollectionEntry | undefined>'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'useZenOrder',
|
|
53
|
+
kind: 'function',
|
|
54
|
+
description: 'Hook to sort collection entries by frontmatter order field.',
|
|
55
|
+
signature: 'useZenOrder(entries: CollectionEntry[]): CollectionEntry[]'
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
required: false
|
|
59
|
+
},
|
|
60
|
+
'zenith:image': {
|
|
61
|
+
module: 'zenith:image',
|
|
62
|
+
description: 'Image optimization plugin for Zenith.',
|
|
63
|
+
exports: [
|
|
64
|
+
{
|
|
65
|
+
name: 'Image',
|
|
66
|
+
kind: 'function',
|
|
67
|
+
description: 'Optimized image component with automatic format conversion and lazy loading.',
|
|
68
|
+
signature: 'Image({ src: string; alt: string; width?: number; height?: number })'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'getImage',
|
|
72
|
+
kind: 'function',
|
|
73
|
+
description: 'Get optimized image metadata.',
|
|
74
|
+
signature: 'getImage(src: string, options?: ImageOptions): Promise<ImageMetadata>'
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
required: false
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get a plugin module by name
|
|
83
|
+
*/
|
|
84
|
+
export function getPluginModule(moduleName: string): PluginModuleMetadata | undefined {
|
|
85
|
+
return PLUGIN_MODULES[moduleName];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get all plugin module names
|
|
90
|
+
*/
|
|
91
|
+
export function getPluginModuleNames(): string[] {
|
|
92
|
+
return Object.keys(PLUGIN_MODULES);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get an export from a plugin module
|
|
97
|
+
*/
|
|
98
|
+
export function getPluginExport(moduleName: string, exportName: string): PluginExport | undefined {
|
|
99
|
+
const module = PLUGIN_MODULES[moduleName];
|
|
100
|
+
if (!module) return undefined;
|
|
101
|
+
return module.exports.find(e => e.name === exportName);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if a module is a plugin module (zenith:* namespace)
|
|
106
|
+
*/
|
|
107
|
+
export function isPluginModule(moduleName: string): boolean {
|
|
108
|
+
return moduleName.startsWith('zenith:');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if a plugin module is known
|
|
113
|
+
*/
|
|
114
|
+
export function isKnownPluginModule(moduleName: string): boolean {
|
|
115
|
+
return moduleName in PLUGIN_MODULES;
|
|
116
|
+
}
|
package/src/project.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zenith Project Graph
|
|
3
|
+
*
|
|
4
|
+
* Uses the compiler's discovery logic to build a project graph
|
|
5
|
+
* Ensures LSP understands the same structure as the compiler
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
|
|
11
|
+
export interface ComponentInfo {
|
|
12
|
+
name: string;
|
|
13
|
+
filePath: string;
|
|
14
|
+
type: 'layout' | 'component' | 'page';
|
|
15
|
+
props: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ProjectGraph {
|
|
19
|
+
root: string;
|
|
20
|
+
layouts: Map<string, ComponentInfo>;
|
|
21
|
+
components: Map<string, ComponentInfo>;
|
|
22
|
+
pages: Map<string, ComponentInfo>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Detect Zenith project root
|
|
27
|
+
* Looks for zenith.config.ts, src/, or app/
|
|
28
|
+
*/
|
|
29
|
+
export function detectProjectRoot(startPath: string): string | null {
|
|
30
|
+
let current = startPath;
|
|
31
|
+
|
|
32
|
+
while (current !== path.dirname(current)) {
|
|
33
|
+
// Check for zenith.config.ts
|
|
34
|
+
if (fs.existsSync(path.join(current, 'zenith.config.ts'))) {
|
|
35
|
+
return current;
|
|
36
|
+
}
|
|
37
|
+
// Check for src/ directory with Zenith files
|
|
38
|
+
const srcDir = path.join(current, 'src');
|
|
39
|
+
if (fs.existsSync(srcDir)) {
|
|
40
|
+
const hasPages = fs.existsSync(path.join(srcDir, 'pages'));
|
|
41
|
+
const hasLayouts = fs.existsSync(path.join(srcDir, 'layouts'));
|
|
42
|
+
if (hasPages || hasLayouts) {
|
|
43
|
+
return current;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Check for app/ directory
|
|
47
|
+
const appDir = path.join(current, 'app');
|
|
48
|
+
if (fs.existsSync(appDir)) {
|
|
49
|
+
const hasPages = fs.existsSync(path.join(appDir, 'pages'));
|
|
50
|
+
const hasLayouts = fs.existsSync(path.join(appDir, 'layouts'));
|
|
51
|
+
if (hasPages || hasLayouts) {
|
|
52
|
+
return current;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
current = path.dirname(current);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Extract props from a .zen file
|
|
63
|
+
* Infers props from usage patterns (Astro/Vue style)
|
|
64
|
+
*/
|
|
65
|
+
function extractPropsFromFile(filePath: string): string[] {
|
|
66
|
+
try {
|
|
67
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
68
|
+
const props: string[] = [];
|
|
69
|
+
|
|
70
|
+
// Look for Props interface/type
|
|
71
|
+
const propsMatch = content.match(/(?:interface|type)\s+Props\s*[={]\s*\{([^}]+)\}/);
|
|
72
|
+
if (propsMatch && propsMatch[1]) {
|
|
73
|
+
const propNames = propsMatch[1].match(/([a-zA-Z_$][a-zA-Z0-9_$?]*)\s*[?:]?\s*:/g);
|
|
74
|
+
if (propNames) {
|
|
75
|
+
for (const p of propNames) {
|
|
76
|
+
const name = p.replace(/[?:\s]/g, '');
|
|
77
|
+
if (name && !props.includes(name)) {
|
|
78
|
+
props.push(name);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Look for common prop patterns in expressions
|
|
85
|
+
const usagePatterns = content.matchAll(/\{(title|lang|className|children|href|src|alt|id|name)\}/g);
|
|
86
|
+
for (const match of usagePatterns) {
|
|
87
|
+
if (match[1] && !props.includes(match[1])) {
|
|
88
|
+
props.push(match[1]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return props;
|
|
93
|
+
} catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Discover all .zen files in a directory
|
|
100
|
+
*/
|
|
101
|
+
function discoverZenFiles(dir: string, type: 'layout' | 'component' | 'page'): Map<string, ComponentInfo> {
|
|
102
|
+
const result = new Map<string, ComponentInfo>();
|
|
103
|
+
|
|
104
|
+
if (!fs.existsSync(dir)) {
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function scanDir(currentDir: string) {
|
|
109
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
110
|
+
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
113
|
+
|
|
114
|
+
if (entry.isDirectory()) {
|
|
115
|
+
scanDir(fullPath);
|
|
116
|
+
} else if (entry.name.endsWith('.zen')) {
|
|
117
|
+
const name = path.basename(entry.name, '.zen');
|
|
118
|
+
const props = extractPropsFromFile(fullPath);
|
|
119
|
+
|
|
120
|
+
result.set(name, {
|
|
121
|
+
name,
|
|
122
|
+
filePath: fullPath,
|
|
123
|
+
type,
|
|
124
|
+
props
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
scanDir(dir);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Build project graph from root directory
|
|
136
|
+
*/
|
|
137
|
+
export function buildProjectGraph(root: string): ProjectGraph {
|
|
138
|
+
const srcDir = fs.existsSync(path.join(root, 'src')) ? path.join(root, 'src') : path.join(root, 'app');
|
|
139
|
+
|
|
140
|
+
const layouts = discoverZenFiles(path.join(srcDir, 'layouts'), 'layout');
|
|
141
|
+
const components = discoverZenFiles(path.join(srcDir, 'components'), 'component');
|
|
142
|
+
const pages = discoverZenFiles(path.join(srcDir, 'pages'), 'page');
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
root,
|
|
146
|
+
layouts,
|
|
147
|
+
components,
|
|
148
|
+
pages
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolve a component/layout by name
|
|
154
|
+
*/
|
|
155
|
+
export function resolveComponent(graph: ProjectGraph, name: string): ComponentInfo | undefined {
|
|
156
|
+
// Check layouts first (common pattern for <DefaultLayout>)
|
|
157
|
+
if (graph.layouts.has(name)) {
|
|
158
|
+
return graph.layouts.get(name);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Then components
|
|
162
|
+
if (graph.components.has(name)) {
|
|
163
|
+
return graph.components.get(name);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
package/src/router.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router Awareness
|
|
3
|
+
*
|
|
4
|
+
* Special support for Zenith Router features.
|
|
5
|
+
* The LSP provides router-aware completions and hovers when zenith/router is imported.
|
|
6
|
+
*
|
|
7
|
+
* Important: No route file system assumptions or runtime navigation simulation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface RouterHookMetadata {
|
|
11
|
+
name: string;
|
|
12
|
+
owner: string;
|
|
13
|
+
description: string;
|
|
14
|
+
restrictions: string;
|
|
15
|
+
returns: string;
|
|
16
|
+
signature: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ZenLinkPropMetadata {
|
|
20
|
+
name: string;
|
|
21
|
+
type: string;
|
|
22
|
+
required: boolean;
|
|
23
|
+
description: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RouteFieldMetadata {
|
|
27
|
+
name: string;
|
|
28
|
+
type: string;
|
|
29
|
+
description: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Router hook definitions
|
|
34
|
+
*/
|
|
35
|
+
export const ROUTER_HOOKS: Record<string, RouterHookMetadata> = {
|
|
36
|
+
useRoute: {
|
|
37
|
+
name: 'useRoute',
|
|
38
|
+
owner: 'Router Hook (zenith/router)',
|
|
39
|
+
description: 'Provides reactive access to the current route state.',
|
|
40
|
+
restrictions: 'Must be called at top-level script scope.',
|
|
41
|
+
returns: '{ path: string; params: Record<string, string>; query: Record<string, string> }',
|
|
42
|
+
signature: 'useRoute(): RouteState'
|
|
43
|
+
},
|
|
44
|
+
useRouter: {
|
|
45
|
+
name: 'useRouter',
|
|
46
|
+
owner: 'Router Hook (zenith/router)',
|
|
47
|
+
description: 'Provides programmatic navigation methods.',
|
|
48
|
+
restrictions: 'Must be called at top-level script scope.',
|
|
49
|
+
returns: '{ navigate, back, forward, go }',
|
|
50
|
+
signature: 'useRouter(): Router'
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* ZenLink component props
|
|
56
|
+
*/
|
|
57
|
+
export const ZENLINK_PROPS: ZenLinkPropMetadata[] = [
|
|
58
|
+
{
|
|
59
|
+
name: 'to',
|
|
60
|
+
type: 'string',
|
|
61
|
+
required: true,
|
|
62
|
+
description: 'The route path to navigate to.'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'preload',
|
|
66
|
+
type: 'boolean',
|
|
67
|
+
required: false,
|
|
68
|
+
description: 'Whether to prefetch the route on hover.'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'replace',
|
|
72
|
+
type: 'boolean',
|
|
73
|
+
required: false,
|
|
74
|
+
description: 'Whether to replace the current history entry instead of pushing a new one.'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'class',
|
|
78
|
+
type: 'string',
|
|
79
|
+
required: false,
|
|
80
|
+
description: 'CSS class to apply to the link.'
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'activeClass',
|
|
84
|
+
type: 'string',
|
|
85
|
+
required: false,
|
|
86
|
+
description: 'CSS class to apply when the link is active.'
|
|
87
|
+
}
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Route state fields available from useRoute()
|
|
92
|
+
*/
|
|
93
|
+
export const ROUTE_FIELDS: RouteFieldMetadata[] = [
|
|
94
|
+
{
|
|
95
|
+
name: 'path',
|
|
96
|
+
type: 'string',
|
|
97
|
+
description: 'The current route path (e.g., "/blog/my-post").'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'params',
|
|
101
|
+
type: 'Record<string, string>',
|
|
102
|
+
description: 'Dynamic route parameters (e.g., { slug: "my-post" }).'
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'query',
|
|
106
|
+
type: 'Record<string, string>',
|
|
107
|
+
description: 'Query string parameters (e.g., { page: "1" }).'
|
|
108
|
+
}
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Router navigation functions
|
|
113
|
+
*/
|
|
114
|
+
export const ROUTER_FUNCTIONS = [
|
|
115
|
+
{
|
|
116
|
+
name: 'navigate',
|
|
117
|
+
description: 'Navigate to a route programmatically.',
|
|
118
|
+
signature: 'navigate(to: string, options?: { replace?: boolean }): void'
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'prefetch',
|
|
122
|
+
description: 'Prefetch a route for faster navigation.',
|
|
123
|
+
signature: 'prefetch(path: string): Promise<void>'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'isActive',
|
|
127
|
+
description: 'Check if a route is currently active.',
|
|
128
|
+
signature: 'isActive(path: string, exact?: boolean): boolean'
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'back',
|
|
132
|
+
description: 'Navigate back in history.',
|
|
133
|
+
signature: 'back(): void'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'forward',
|
|
137
|
+
description: 'Navigate forward in history.',
|
|
138
|
+
signature: 'forward(): void'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'go',
|
|
142
|
+
description: 'Navigate to a specific history entry.',
|
|
143
|
+
signature: 'go(delta: number): void'
|
|
144
|
+
}
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get router hook metadata
|
|
149
|
+
*/
|
|
150
|
+
export function getRouterHook(name: string): RouterHookMetadata | undefined {
|
|
151
|
+
return ROUTER_HOOKS[name];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Check if a name is a router hook
|
|
156
|
+
*/
|
|
157
|
+
export function isRouterHook(name: string): boolean {
|
|
158
|
+
return name in ROUTER_HOOKS;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get ZenLink prop metadata
|
|
163
|
+
*/
|
|
164
|
+
export function getZenLinkProp(name: string): ZenLinkPropMetadata | undefined {
|
|
165
|
+
return ZENLINK_PROPS.find(p => p.name === name);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get all ZenLink prop names
|
|
170
|
+
*/
|
|
171
|
+
export function getZenLinkPropNames(): string[] {
|
|
172
|
+
return ZENLINK_PROPS.map(p => p.name);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get route field metadata
|
|
177
|
+
*/
|
|
178
|
+
export function getRouteField(name: string): RouteFieldMetadata | undefined {
|
|
179
|
+
return ROUTE_FIELDS.find(f => f.name === name);
|
|
180
|
+
}
|