@route-forge/vue 2.2.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as vue from 'vue';
2
- import { InjectionKey, Ref, Plugin, ComputedRef, VNode, SlotsType } from 'vue';
2
+ import { InjectionKey, Plugin, Ref, ComputedRef, VNode, SlotsType } from 'vue';
3
3
  import { RouteForge, RouteForgeOptions, BoundForge, UseForgeApiBoundReturn, UseForgeApiReturn } from '@route-forge/core';
4
4
  export { UseForgeApiBoundCall, UseForgeApiCall } from '@route-forge/core';
5
5
 
@@ -8,28 +8,29 @@ export { UseForgeApiBoundCall, UseForgeApiCall } from '@route-forge/core';
8
8
  * @see .docs/SPEC.md §4.1.7
9
9
  *
10
10
  * 提供:
11
- * - 全局属性 $forge(route() 工具)
12
- * - inject symbol 注入 RouteForge 实例供 composable 使用
13
- * - useForge() / useForge(level) / useForge(level, prefix) 返回统一方法的 forge 实例
14
- * • 不传 level:forge(level, name, params?) 直接调用
15
- * • 传 level:forge(name, params?) 直接调用,自动绑定层级
16
- * • 传 level + prefix:forge(suffix, params?) 自动拼接 prefix
11
+ * - createRouteForgePlugin:装配插件,install 时经 inject symbol 注入 RouteForge 实例
12
+ * - 返回对象上的 interceptors(转发实例拦截器):创建后即可同步注册,无需等待 ready()
17
13
  *
18
- * 类型推断:
19
- * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,
20
- * level / name / params 均自动推断,IDE 提供补全提示。
14
+ * useForge / useForgeApi / useForgeRoute 等消费 composable 见 composables/ 下各文件。
15
+ *
16
+ * 注:v3.0.0 起不再注入 `$forge` 全局属性(残缺 facade,仅 route() 且未 ready 时抛错断渲染);
17
+ * 模板内生成链接用 useForgeRoute / ForgeLink / ForgeRoute,命令式场景用 useForge()。
21
18
  */
22
19
 
23
20
  declare const FORGE_INJECTION_KEY: InjectionKey<RouteForge>;
24
21
  interface RouteForgePluginOptions extends RouteForgeOptions {
25
22
  }
26
- /** Vue 特化类型:levelLoaded Ref<boolean> */
27
- type VueBoundForge = BoundForge<Ref<boolean>>;
28
- declare function createRouteForgePlugin(options?: RouteForgePluginOptions): Plugin<[]> & {
23
+ declare function createRouteForgePlugin(forgeOrOptions?: RouteForge | RouteForgePluginOptions): Plugin<[]> & {
29
24
  ready: RouteForge['ready'];
25
+ /** 转发实例上的拦截器管理器:创建后即可同步注册,无需等待 ready()(请求/响应拦截链仅影响后续 api() 调用,eager 元信息预加载走 requestRaw 不受影响) */
26
+ interceptors: RouteForge['interceptors'];
30
27
  };
28
+
31
29
  /**
32
- * 获取 forge 实例。内部委托 core 的 forge.use(),仅将 levelLoaded 替换为 Vue 响应式 Ref
30
+ * useForge — 获取 forge 实例(Vue 特化:levelLoaded Ref<boolean>)
31
+ * @see .docs/SPEC.md §4.1.7
32
+ *
33
+ * 内部委托 core 的 forge.use(),仅将 levelLoaded 从 Promise<void> 替换为 Vue 响应式 Ref。
33
34
  *
34
35
  * @example
35
36
  * // 不绑定层级
@@ -51,6 +52,9 @@ declare function createRouteForgePlugin(options?: RouteForgePluginOptions): Plug
51
52
  * forge('show', { user: 1 }) // → forge.api('admin', 'users.show', ...)
52
53
  * forge.route('show', { user: 1 }) // → forge.route('admin', 'users.show', ...)
53
54
  */
55
+
56
+ /** Vue 特化类型:levelLoaded 为 Ref<boolean> */
57
+ type VueBoundForge = BoundForge<Ref<boolean>>;
54
58
  declare function useForge<L extends string>(level: L, prefix: string): VueBoundForge;
55
59
  declare function useForge<L extends string>(level: L): VueBoundForge;
56
60
  declare function useForge(): RouteForge;
@@ -115,6 +119,11 @@ declare const ForgeRoute: vue.DefineComponent<vue.ExtractPropTypes<{
115
119
  readonly required: false;
116
120
  readonly default: undefined;
117
121
  };
122
+ readonly as: {
123
+ readonly type: vue.PropType<object>;
124
+ readonly required: false;
125
+ readonly default: undefined;
126
+ };
118
127
  }>, () => VNode<vue.RendererNode, vue.RendererElement, {
119
128
  [key: string]: any;
120
129
  }>[] | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
@@ -131,14 +140,24 @@ declare const ForgeRoute: vue.DefineComponent<vue.ExtractPropTypes<{
131
140
  readonly required: false;
132
141
  readonly default: undefined;
133
142
  };
143
+ readonly as: {
144
+ readonly type: vue.PropType<object>;
145
+ readonly required: false;
146
+ readonly default: undefined;
147
+ };
134
148
  }>> & Readonly<{}>, {
135
149
  readonly params: Record<string, unknown> | (() => Record<string, unknown>);
150
+ readonly as: object;
136
151
  }, SlotsType<{
137
152
  default?: (props: {
138
153
  href: string;
139
154
  loaded: boolean;
140
155
  }) => VNode[];
141
156
  loading?: () => VNode[];
157
+ /** 解析失败(路由名不存在等):props 携带错误对象;未传回落 loading 插槽 */
158
+ error?: (props: {
159
+ error: unknown;
160
+ }) => VNode[];
142
161
  }>, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
143
162
  type ForgeRouteProps = InstanceType<typeof ForgeRoute>['$props'];
144
163
 
@@ -156,6 +175,11 @@ declare const ForgeLink: vue.DefineComponent<vue.ExtractPropTypes<{
156
175
  readonly required: false;
157
176
  readonly default: undefined;
158
177
  };
178
+ readonly as: {
179
+ readonly type: vue.PropType<object>;
180
+ readonly required: false;
181
+ readonly default: undefined;
182
+ };
159
183
  }>, () => VNode<vue.RendererNode, vue.RendererElement, {
160
184
  [key: string]: any;
161
185
  }> | VNode<vue.RendererNode, vue.RendererElement, {
@@ -174,14 +198,34 @@ declare const ForgeLink: vue.DefineComponent<vue.ExtractPropTypes<{
174
198
  readonly required: false;
175
199
  readonly default: undefined;
176
200
  };
201
+ readonly as: {
202
+ readonly type: vue.PropType<object>;
203
+ readonly required: false;
204
+ readonly default: undefined;
205
+ };
177
206
  }>> & Readonly<{}>, {
178
207
  readonly params: Record<string, unknown> | (() => Record<string, unknown>);
208
+ readonly as: object;
179
209
  }, SlotsType<{
180
210
  /** 链接文本/内容 */
181
211
  default?: () => VNode[];
182
212
  /** 未加载占位 */
183
213
  loading?: () => VNode[];
214
+ /** 解析失败(路由名不存在等):props 携带错误对象;未传回落 loading 插槽 */
215
+ error?: (props: {
216
+ error: unknown;
217
+ }) => VNode[];
184
218
  }>, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
185
219
  type ForgeLinkProps = InstanceType<typeof ForgeLink>['$props'];
186
220
 
187
- export { FORGE_INJECTION_KEY, ForgeLink, type ForgeLinkProps, ForgeRoute, type ForgeRouteDegradeHooks, type ForgeRouteProps, type RouteForgePluginOptions, type UseForgeApiBoundReturnVue, type UseForgeApiReturnVue, type VueBoundForge, createRouteForgePlugin, useForge, useForgeApi, useForgeRoute };
221
+ declare const ForgeReady: vue.DefineComponent<{}, () => VNode<vue.RendererNode, vue.RendererElement, {
222
+ [key: string]: any;
223
+ }>[] | null | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<{}> & Readonly<{}>, {}, SlotsType<{
224
+ /** ready 后渲染的内容 */
225
+ default?: () => VNode[];
226
+ /** 门闩期间的占位 UI(缺省不渲染任何内容) */
227
+ fallback?: () => VNode[];
228
+ }>, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
229
+ type ForgeReadyProps = InstanceType<typeof ForgeReady>['$props'];
230
+
231
+ export { FORGE_INJECTION_KEY, ForgeLink, type ForgeLinkProps, ForgeReady, type ForgeReadyProps, ForgeRoute, type ForgeRouteDegradeHooks, type ForgeRouteProps, type RouteForgePluginOptions, type UseForgeApiBoundReturnVue, type UseForgeApiReturnVue, type VueBoundForge, createRouteForgePlugin, useForge, useForgeApi, useForgeRoute };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as vue from 'vue';
2
- import { InjectionKey, Ref, Plugin, ComputedRef, VNode, SlotsType } from 'vue';
2
+ import { InjectionKey, Plugin, Ref, ComputedRef, VNode, SlotsType } from 'vue';
3
3
  import { RouteForge, RouteForgeOptions, BoundForge, UseForgeApiBoundReturn, UseForgeApiReturn } from '@route-forge/core';
4
4
  export { UseForgeApiBoundCall, UseForgeApiCall } from '@route-forge/core';
5
5
 
@@ -8,28 +8,29 @@ export { UseForgeApiBoundCall, UseForgeApiCall } from '@route-forge/core';
8
8
  * @see .docs/SPEC.md §4.1.7
9
9
  *
10
10
  * 提供:
11
- * - 全局属性 $forge(route() 工具)
12
- * - inject symbol 注入 RouteForge 实例供 composable 使用
13
- * - useForge() / useForge(level) / useForge(level, prefix) 返回统一方法的 forge 实例
14
- * • 不传 level:forge(level, name, params?) 直接调用
15
- * • 传 level:forge(name, params?) 直接调用,自动绑定层级
16
- * • 传 level + prefix:forge(suffix, params?) 自动拼接 prefix
11
+ * - createRouteForgePlugin:装配插件,install 时经 inject symbol 注入 RouteForge 实例
12
+ * - 返回对象上的 interceptors(转发实例拦截器):创建后即可同步注册,无需等待 ready()
17
13
  *
18
- * 类型推断:
19
- * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,
20
- * level / name / params 均自动推断,IDE 提供补全提示。
14
+ * useForge / useForgeApi / useForgeRoute 等消费 composable 见 composables/ 下各文件。
15
+ *
16
+ * 注:v3.0.0 起不再注入 `$forge` 全局属性(残缺 facade,仅 route() 且未 ready 时抛错断渲染);
17
+ * 模板内生成链接用 useForgeRoute / ForgeLink / ForgeRoute,命令式场景用 useForge()。
21
18
  */
22
19
 
23
20
  declare const FORGE_INJECTION_KEY: InjectionKey<RouteForge>;
24
21
  interface RouteForgePluginOptions extends RouteForgeOptions {
25
22
  }
26
- /** Vue 特化类型:levelLoaded Ref<boolean> */
27
- type VueBoundForge = BoundForge<Ref<boolean>>;
28
- declare function createRouteForgePlugin(options?: RouteForgePluginOptions): Plugin<[]> & {
23
+ declare function createRouteForgePlugin(forgeOrOptions?: RouteForge | RouteForgePluginOptions): Plugin<[]> & {
29
24
  ready: RouteForge['ready'];
25
+ /** 转发实例上的拦截器管理器:创建后即可同步注册,无需等待 ready()(请求/响应拦截链仅影响后续 api() 调用,eager 元信息预加载走 requestRaw 不受影响) */
26
+ interceptors: RouteForge['interceptors'];
30
27
  };
28
+
31
29
  /**
32
- * 获取 forge 实例。内部委托 core 的 forge.use(),仅将 levelLoaded 替换为 Vue 响应式 Ref
30
+ * useForge — 获取 forge 实例(Vue 特化:levelLoaded Ref<boolean>)
31
+ * @see .docs/SPEC.md §4.1.7
32
+ *
33
+ * 内部委托 core 的 forge.use(),仅将 levelLoaded 从 Promise<void> 替换为 Vue 响应式 Ref。
33
34
  *
34
35
  * @example
35
36
  * // 不绑定层级
@@ -51,6 +52,9 @@ declare function createRouteForgePlugin(options?: RouteForgePluginOptions): Plug
51
52
  * forge('show', { user: 1 }) // → forge.api('admin', 'users.show', ...)
52
53
  * forge.route('show', { user: 1 }) // → forge.route('admin', 'users.show', ...)
53
54
  */
55
+
56
+ /** Vue 特化类型:levelLoaded 为 Ref<boolean> */
57
+ type VueBoundForge = BoundForge<Ref<boolean>>;
54
58
  declare function useForge<L extends string>(level: L, prefix: string): VueBoundForge;
55
59
  declare function useForge<L extends string>(level: L): VueBoundForge;
56
60
  declare function useForge(): RouteForge;
@@ -115,6 +119,11 @@ declare const ForgeRoute: vue.DefineComponent<vue.ExtractPropTypes<{
115
119
  readonly required: false;
116
120
  readonly default: undefined;
117
121
  };
122
+ readonly as: {
123
+ readonly type: vue.PropType<object>;
124
+ readonly required: false;
125
+ readonly default: undefined;
126
+ };
118
127
  }>, () => VNode<vue.RendererNode, vue.RendererElement, {
119
128
  [key: string]: any;
120
129
  }>[] | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
@@ -131,14 +140,24 @@ declare const ForgeRoute: vue.DefineComponent<vue.ExtractPropTypes<{
131
140
  readonly required: false;
132
141
  readonly default: undefined;
133
142
  };
143
+ readonly as: {
144
+ readonly type: vue.PropType<object>;
145
+ readonly required: false;
146
+ readonly default: undefined;
147
+ };
134
148
  }>> & Readonly<{}>, {
135
149
  readonly params: Record<string, unknown> | (() => Record<string, unknown>);
150
+ readonly as: object;
136
151
  }, SlotsType<{
137
152
  default?: (props: {
138
153
  href: string;
139
154
  loaded: boolean;
140
155
  }) => VNode[];
141
156
  loading?: () => VNode[];
157
+ /** 解析失败(路由名不存在等):props 携带错误对象;未传回落 loading 插槽 */
158
+ error?: (props: {
159
+ error: unknown;
160
+ }) => VNode[];
142
161
  }>, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
143
162
  type ForgeRouteProps = InstanceType<typeof ForgeRoute>['$props'];
144
163
 
@@ -156,6 +175,11 @@ declare const ForgeLink: vue.DefineComponent<vue.ExtractPropTypes<{
156
175
  readonly required: false;
157
176
  readonly default: undefined;
158
177
  };
178
+ readonly as: {
179
+ readonly type: vue.PropType<object>;
180
+ readonly required: false;
181
+ readonly default: undefined;
182
+ };
159
183
  }>, () => VNode<vue.RendererNode, vue.RendererElement, {
160
184
  [key: string]: any;
161
185
  }> | VNode<vue.RendererNode, vue.RendererElement, {
@@ -174,14 +198,34 @@ declare const ForgeLink: vue.DefineComponent<vue.ExtractPropTypes<{
174
198
  readonly required: false;
175
199
  readonly default: undefined;
176
200
  };
201
+ readonly as: {
202
+ readonly type: vue.PropType<object>;
203
+ readonly required: false;
204
+ readonly default: undefined;
205
+ };
177
206
  }>> & Readonly<{}>, {
178
207
  readonly params: Record<string, unknown> | (() => Record<string, unknown>);
208
+ readonly as: object;
179
209
  }, SlotsType<{
180
210
  /** 链接文本/内容 */
181
211
  default?: () => VNode[];
182
212
  /** 未加载占位 */
183
213
  loading?: () => VNode[];
214
+ /** 解析失败(路由名不存在等):props 携带错误对象;未传回落 loading 插槽 */
215
+ error?: (props: {
216
+ error: unknown;
217
+ }) => VNode[];
184
218
  }>, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
185
219
  type ForgeLinkProps = InstanceType<typeof ForgeLink>['$props'];
186
220
 
187
- export { FORGE_INJECTION_KEY, ForgeLink, type ForgeLinkProps, ForgeRoute, type ForgeRouteDegradeHooks, type ForgeRouteProps, type RouteForgePluginOptions, type UseForgeApiBoundReturnVue, type UseForgeApiReturnVue, type VueBoundForge, createRouteForgePlugin, useForge, useForgeApi, useForgeRoute };
221
+ declare const ForgeReady: vue.DefineComponent<{}, () => VNode<vue.RendererNode, vue.RendererElement, {
222
+ [key: string]: any;
223
+ }>[] | null | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<{}> & Readonly<{}>, {}, SlotsType<{
224
+ /** ready 后渲染的内容 */
225
+ default?: () => VNode[];
226
+ /** 门闩期间的占位 UI(缺省不渲染任何内容) */
227
+ fallback?: () => VNode[];
228
+ }>, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
229
+ type ForgeReadyProps = InstanceType<typeof ForgeReady>['$props'];
230
+
231
+ export { FORGE_INJECTION_KEY, ForgeLink, type ForgeLinkProps, ForgeReady, type ForgeReadyProps, ForgeRoute, type ForgeRouteDegradeHooks, type ForgeRouteProps, type RouteForgePluginOptions, type UseForgeApiBoundReturnVue, type UseForgeApiReturnVue, type VueBoundForge, createRouteForgePlugin, useForge, useForgeApi, useForgeRoute };
package/dist/index.js CHANGED
@@ -1,20 +1,21 @@
1
- import { defineComponent, inject, h, ref, onMounted, computed, getCurrentInstance } from 'vue';
2
1
  import { createRouteForge } from '@route-forge/core';
2
+ import { defineComponent, h, ref, inject, onMounted, getCurrentScope, onScopeDispose, computed, watch, getCurrentInstance } from 'vue';
3
3
 
4
4
  // src/plugin.ts
5
5
  var FORGE_INJECTION_KEY = /* @__PURE__ */ Symbol("route-forge");
6
- function createRouteForgePlugin(options = {}) {
7
- const forge = createRouteForge(options);
6
+ function createRouteForgePlugin(forgeOrOptions = {}) {
7
+ const forge = isRouteForge(forgeOrOptions) ? forgeOrOptions : createRouteForge(forgeOrOptions);
8
8
  return {
9
9
  ready: forge.ready.bind(forge),
10
+ interceptors: forge.interceptors,
10
11
  install(app) {
11
12
  app.provide(FORGE_INJECTION_KEY, forge);
12
- app.config.globalProperties.$forge = {
13
- route: (level, name, params) => forge.route(level, name, params)
14
- };
15
13
  }
16
14
  };
17
15
  }
16
+ function isRouteForge(x) {
17
+ return typeof x === "object" && x !== null && typeof x.api === "function" && typeof x.ready === "function";
18
+ }
18
19
  function useForge(level, prefix) {
19
20
  const forge = inject(FORGE_INJECTION_KEY);
20
21
  if (!forge) {
@@ -62,7 +63,40 @@ function useForgeApi(level, prefix) {
62
63
  }
63
64
  return { pending, error, call };
64
65
  }
65
- function warnRenderError(error) {
66
+ function useInjectedForge(apiName) {
67
+ const forge = inject(FORGE_INJECTION_KEY);
68
+ if (!forge) {
69
+ throw new Error(
70
+ `[route-forge/vue] ${apiName} must be used inside an app with createRouteForgePlugin() installed`
71
+ );
72
+ }
73
+ return forge;
74
+ }
75
+
76
+ // src/degrade.ts
77
+ var reportedKeys = /* @__PURE__ */ new Set();
78
+ var renderWarnKeys = /* @__PURE__ */ new Set();
79
+ function dedupeKey(component, error) {
80
+ const message = error instanceof Error ? error.message : String(error);
81
+ return `${component}:${message}`;
82
+ }
83
+ function reportDegrade(component, error, enabled = true) {
84
+ if (!enabled) return;
85
+ const key = dedupeKey(component, error);
86
+ if (reportedKeys.has(key)) return;
87
+ reportedKeys.add(key);
88
+ console.error(
89
+ `%c[route-forge]%c ${component} \u8DEF\u7531\u89E3\u6790\u5931\u8D25\uFF08\u5DF2\u964D\u7EA7\u4E3A\u7A7A\u5B57\u7B26\u4E32\uFF0C\u6E32\u67D3\u672A\u4E2D\u65AD\uFF09`,
90
+ "color:#c0392b;font-weight:bold",
91
+ "color:inherit",
92
+ error
93
+ );
94
+ }
95
+ function reportRenderWarn(error, enabled) {
96
+ if (!enabled) return;
97
+ const key = `useForgeRoute:${error instanceof Error ? error.message : String(error)}`;
98
+ if (renderWarnKeys.has(key)) return;
99
+ renderWarnKeys.add(key);
66
100
  console.warn(
67
101
  "%c[route-forge]%c useForgeRoute \u6E32\u67D3\u671F\u9519\u8BEF\uFF08\u5DF2\u964D\u7EA7\u4E3A\u7A7A\u5B57\u7B26\u4E32\uFF0C\u6E32\u67D3\u672A\u4E2D\u65AD\uFF09",
68
102
  "color:#e67e22;font-weight:bold",
@@ -70,13 +104,18 @@ function warnRenderError(error) {
70
104
  error
71
105
  );
72
106
  }
107
+
108
+ // src/composables/useForgeRoute.ts
73
109
  function useForgeRoute(level, name, params, hooks) {
110
+ return useForgeRouteState(level, name, params, hooks).href;
111
+ }
112
+ function useForgeRouteState(level, name, params, hooks) {
74
113
  if (typeof level !== "string") {
75
114
  throw new TypeError(
76
115
  "[route-forge/vue] useForgeRoute(): level must be a static string \u2014 getter form is not supported (levels are deterministic declarations; create another useForgeRoute call for another level)"
77
116
  );
78
117
  }
79
- const forge = inject(FORGE_INJECTION_KEY);
118
+ const forge = useInjectedForge("useForgeRoute()");
80
119
  const lvl = level;
81
120
  const levelLoaded = ref(forge.isLoaded(lvl));
82
121
  if (!levelLoaded.value) {
@@ -87,17 +126,36 @@ function useForgeRoute(level, name, params, hooks) {
87
126
  });
88
127
  });
89
128
  }
90
- return computed(() => {
91
- if (!levelLoaded.value) return "";
129
+ const dataVersion = ref(0);
130
+ const unsubscribe = forge.onRoutesChange((changed) => {
131
+ if (changed === lvl) dataVersion.value++;
132
+ });
133
+ if (getCurrentScope()) onScopeDispose(unsubscribe);
134
+ const state = computed(() => {
135
+ if (!levelLoaded.value) return { href: "", error: null };
136
+ void dataVersion.value;
92
137
  const n = typeof name === "function" ? name() : name;
93
138
  const p = params ? params() : void 0;
94
139
  try {
95
- return forge.route(lvl, n, p);
140
+ return { href: forge.route(lvl, n, p), error: null };
96
141
  } catch (e) {
97
- (hooks?.onDegrade ?? warnRenderError)(e);
98
- return "";
142
+ return { href: "", error: e };
99
143
  }
100
144
  });
145
+ watch(
146
+ () => state.value.error,
147
+ (err) => {
148
+ if (err == null) return;
149
+ if (hooks?.onDegrade) hooks.onDegrade(err);
150
+ else reportRenderWarn(err, forge.warnings);
151
+ },
152
+ { immediate: true }
153
+ );
154
+ return {
155
+ href: computed(() => state.value.href),
156
+ error: computed(() => state.value.error),
157
+ isLevelLoaded: computed(() => levelLoaded.value)
158
+ };
101
159
  }
102
160
  var forgeLinkProps = {
103
161
  /** 层级名:静态快照,setup 求值一次后固定,不支持中途切换 */
@@ -112,16 +170,18 @@ var forgeLinkProps = {
112
170
  type: [Object, Function],
113
171
  required: false,
114
172
  default: void 0
173
+ },
174
+ /**
175
+ * 手动注入链接组件(组件对象或标签名字符串),注入后同时收到 `href` 与 `to` 两个 prop
176
+ * (对齐 @route-forge/react 的 ForgeLink `as` 契约)。不传时按默认链路:
177
+ * 探测全局 RouterLink → 原生 `<a>`。用于 RouterLink 局部注册 / 非 vue-router 链接组件场景。
178
+ */
179
+ as: {
180
+ type: [Object, String, Function],
181
+ required: false,
182
+ default: void 0
115
183
  }
116
184
  };
117
- function reportDegrade(component, error) {
118
- console.error(
119
- `%c[route-forge]%c ${component} \u8DEF\u7531\u89E3\u6790\u5931\u8D25\uFF08\u5DF2\u964D\u7EA7\u4E3A\u7A7A\u5B57\u7B26\u4E32\uFF0C\u6E32\u67D3\u672A\u4E2D\u65AD\uFF09`,
120
- "color:#c0392b;font-weight:bold",
121
- "color:inherit",
122
- error
123
- );
124
- }
125
185
  function warnUnloadedOnce(component, level, warned) {
126
186
  if (warned.value) return;
127
187
  warned.value = true;
@@ -143,20 +203,23 @@ var ForgeRoute = defineComponent({
143
203
  props: forgeLinkProps,
144
204
  slots: Object,
145
205
  setup(props, { slots }) {
146
- const forge = inject(FORGE_INJECTION_KEY);
206
+ const forge = useInjectedForge("ForgeRoute");
147
207
  const nameGetter = () => typeof props.name === "function" ? props.name() : props.name;
148
208
  const paramsGetter = () => typeof props.params === "function" ? props.params() : props.params;
149
- const href = useForgeRoute(props.level, nameGetter, paramsGetter, {
150
- onDegrade: (e) => reportDegrade("ForgeRoute", e)
209
+ const state = useForgeRouteState(props.level, nameGetter, paramsGetter, {
210
+ onDegrade: (e) => reportDegrade("ForgeRoute", e, forge.warnings)
151
211
  });
152
212
  const unloadWarned = { value: false };
153
213
  return () => {
154
- const url = href.value;
214
+ const url = state.href.value;
155
215
  const loaded = url !== "";
156
216
  if (!loaded && !forge.isLoaded(props.level)) {
157
217
  warnUnloadedOnce("ForgeRoute", props.level, unloadWarned);
158
218
  }
159
- if (!loaded) return slots.loading ? slots.loading() : null;
219
+ if (!state.isLevelLoaded.value) return slots.loading ? slots.loading() : null;
220
+ if (state.error.value != null) {
221
+ return slots.error ? slots.error({ error: state.error.value }) : slots.loading ? slots.loading() : null;
222
+ }
160
223
  return slots.default ? slots.default({ href: url, loaded }) : null;
161
224
  };
162
225
  }
@@ -168,22 +231,32 @@ var ForgeLink = defineComponent({
168
231
  props: forgeLinkProps,
169
232
  slots: Object,
170
233
  setup(props, { slots, attrs }) {
171
- const forge = inject(FORGE_INJECTION_KEY);
234
+ const forge = useInjectedForge("ForgeLink");
172
235
  const nameGetter = () => typeof props.name === "function" ? props.name() : props.name;
173
236
  const paramsGetter = () => typeof props.params === "function" ? props.params() : props.params;
174
- const href = useForgeRoute(props.level, nameGetter, paramsGetter, {
175
- onDegrade: (e) => reportDegrade("ForgeLink", e)
237
+ const state = useForgeRouteState(props.level, nameGetter, paramsGetter, {
238
+ onDegrade: (e) => reportDegrade("ForgeLink", e, forge.warnings)
176
239
  });
177
240
  const unloadWarned = { value: false };
178
241
  const routerLink = resolveRouterLink();
179
242
  return () => {
180
- const url = href.value;
243
+ const url = state.href.value;
181
244
  const loaded = url !== "";
182
245
  if (!loaded && !forge.isLoaded(props.level)) {
183
246
  warnUnloadedOnce("ForgeLink", props.level, unloadWarned);
184
247
  }
185
- if (!loaded) return slots.loading ? slots.loading() : null;
248
+ if (!state.isLevelLoaded.value) return slots.loading ? slots.loading() : null;
249
+ if (state.error.value != null) {
250
+ return slots.error ? slots.error({ error: state.error.value }) : slots.loading ? slots.loading() : null;
251
+ }
186
252
  const children = slots.default ? slots.default() : void 0;
253
+ if (props.as) {
254
+ return h(
255
+ props.as,
256
+ { ...attrs, to: url, href: url },
257
+ { default: () => children }
258
+ );
259
+ }
187
260
  if (routerLink) {
188
261
  return h(
189
262
  routerLink,
@@ -195,7 +268,29 @@ var ForgeLink = defineComponent({
195
268
  };
196
269
  }
197
270
  });
271
+ var ForgeReady = defineComponent({
272
+ name: "ForgeReady",
273
+ slots: Object,
274
+ setup(_, { slots }) {
275
+ const forge = useInjectedForge("ForgeReady");
276
+ const open = ref(forge.isReady());
277
+ if (!open.value) {
278
+ forge.ready().then(
279
+ () => {
280
+ open.value = true;
281
+ },
282
+ (err) => {
283
+ console.error(
284
+ "[route-forge] ForgeReady: ready() rejected \u2014 content stays gated (same as an unhandled ready().catch)",
285
+ err
286
+ );
287
+ }
288
+ );
289
+ }
290
+ return () => open.value ? slots.default?.() : slots.fallback ? slots.fallback() : null;
291
+ }
292
+ });
198
293
 
199
- export { FORGE_INJECTION_KEY, ForgeLink, ForgeRoute, createRouteForgePlugin, useForge, useForgeApi, useForgeRoute };
294
+ export { FORGE_INJECTION_KEY, ForgeLink, ForgeReady, ForgeRoute, createRouteForgePlugin, useForge, useForgeApi, useForgeRoute };
200
295
  //# sourceMappingURL=index.js.map
201
296
  //# sourceMappingURL=index.js.map