@hai3/framework 0.2.0-alpha.2 → 0.2.0-alpha.4

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.cjs CHANGED
@@ -935,6 +935,44 @@ function resolveBase(pluginConfig, appConfig) {
935
935
  return normalizeBase(rawBase);
936
936
  }
937
937
 
938
+ // src/utils/routeMatcher.ts
939
+ var import_path_to_regexp = require("path-to-regexp");
940
+ function compileRoute(pattern, screensetId, screenId) {
941
+ return {
942
+ pattern,
943
+ screensetId,
944
+ screenId,
945
+ matcher: (0, import_path_to_regexp.match)(pattern, { decode: decodeURIComponent }),
946
+ toPath: (0, import_path_to_regexp.compile)(pattern, { encode: encodeURIComponent })
947
+ };
948
+ }
949
+ function matchPath(path, routes) {
950
+ for (const route of routes) {
951
+ const result = route.matcher(path);
952
+ if (result) {
953
+ return {
954
+ screensetId: route.screensetId,
955
+ screenId: route.screenId,
956
+ params: result.params
957
+ };
958
+ }
959
+ }
960
+ return void 0;
961
+ }
962
+ function generatePath(route, params) {
963
+ return route.toPath(params);
964
+ }
965
+ function extractRequiredParams(pattern) {
966
+ const params = [];
967
+ const matches = pattern.match(/:([^/]+)/g);
968
+ if (matches) {
969
+ for (const match2 of matches) {
970
+ params.push(match2.slice(1));
971
+ }
972
+ }
973
+ return params;
974
+ }
975
+
938
976
  // src/plugins/navigation.ts
939
977
  var screenActions3 = screenActions;
940
978
  var menuActions3 = menuActions;
@@ -957,7 +995,39 @@ function navigation(config) {
957
995
  onInit(app) {
958
996
  const dispatch = app.store.dispatch;
959
997
  const base = resolveBase(config, app.config);
998
+ const routerMode = app.config.routerMode ?? "browser";
960
999
  let currentScreensetId = null;
1000
+ const getCurrentPath = () => {
1001
+ if (typeof window === "undefined") return "/";
1002
+ switch (routerMode) {
1003
+ case "hash":
1004
+ return window.location.hash.slice(1) || "/";
1005
+ case "memory":
1006
+ return "/";
1007
+ case "browser":
1008
+ default:
1009
+ return window.location.pathname;
1010
+ }
1011
+ };
1012
+ const updateURL = (path) => {
1013
+ if (typeof window === "undefined" || routerMode === "memory") {
1014
+ return;
1015
+ }
1016
+ const url = prependBase(path, base);
1017
+ switch (routerMode) {
1018
+ case "hash":
1019
+ if (window.location.hash !== `#${url}`) {
1020
+ window.location.hash = url;
1021
+ }
1022
+ break;
1023
+ case "browser":
1024
+ default:
1025
+ if (window.location.pathname !== url) {
1026
+ window.history.pushState(null, "", url);
1027
+ }
1028
+ break;
1029
+ }
1030
+ };
961
1031
  const loadScreensetTranslations = async (screensetId, language) => {
962
1032
  await import_i18n.i18nRegistry.loadScreensetTranslations(
963
1033
  screensetId,
@@ -986,21 +1056,17 @@ function navigation(config) {
986
1056
  });
987
1057
  updateScreensetMenu(screenset);
988
1058
  };
989
- const extractScreenId = () => {
990
- const internalPath = stripBase(window.location.pathname, base);
991
- if (!internalPath) {
992
- return null;
993
- }
994
- const parts = internalPath.split("/").filter(Boolean);
995
- return parts[0] || null;
1059
+ const extractInternalPath = () => {
1060
+ const currentPath = getCurrentPath();
1061
+ return stripBase(currentPath, base) || "/";
996
1062
  };
997
- const activateScreen = (screenId) => {
998
- const screensetId = app.routeRegistry?.getScreensetForScreen(screenId);
999
- if (!screensetId) {
1000
- return;
1001
- }
1002
- activateScreenset(screensetId);
1003
- dispatch(screenActions3.navigateTo(screenId));
1063
+ const activateFromRouteMatch = (match2) => {
1064
+ activateScreenset(match2.screensetId);
1065
+ dispatch(screenActions3.navigateTo(match2.screenId));
1066
+ };
1067
+ const matchCurrentPath = () => {
1068
+ const path = extractInternalPath();
1069
+ return app.routeRegistry?.matchRoute(path);
1004
1070
  };
1005
1071
  import_state13.eventBus.on("navigation/screen/navigated", (payload) => {
1006
1072
  if (!app.routeRegistry?.hasScreen(payload.screensetId, payload.screenId)) {
@@ -1009,14 +1075,22 @@ function navigation(config) {
1009
1075
  );
1010
1076
  return;
1011
1077
  }
1012
- activateScreenset(payload.screensetId);
1013
- dispatch(screenActions3.navigateTo(payload.screenId));
1014
- if (typeof window !== "undefined") {
1015
- const url = prependBase(`/${payload.screenId}`, base);
1016
- if (window.location.pathname !== url) {
1017
- window.history.pushState(null, "", url);
1078
+ const pattern = app.routeRegistry?.getRoutePattern(payload.screenId);
1079
+ if (pattern) {
1080
+ const requiredParams = extractRequiredParams(pattern);
1081
+ const providedParams = payload.params || {};
1082
+ const missingParams = requiredParams.filter((param) => !(param in providedParams));
1083
+ if (missingParams.length > 0) {
1084
+ console.warn(
1085
+ `Screen "${payload.screenId}" requires route params [${requiredParams.join(", ")}] but missing: [${missingParams.join(", ")}]`
1086
+ );
1087
+ return;
1018
1088
  }
1019
1089
  }
1090
+ activateScreenset(payload.screensetId);
1091
+ dispatch(screenActions3.navigateTo(payload.screenId));
1092
+ const path = app.routeRegistry?.generatePath(payload.screenId, payload.params) ?? `/${payload.screenId}`;
1093
+ updateURL(path);
1020
1094
  });
1021
1095
  import_state13.eventBus.on("navigation/screenset/navigated", (payload) => {
1022
1096
  const screenset = app.screensetRegistry.get(payload.screensetId);
@@ -1050,23 +1124,36 @@ function navigation(config) {
1050
1124
  );
1051
1125
  });
1052
1126
  });
1053
- if (typeof window !== "undefined") {
1054
- window.addEventListener("popstate", () => {
1055
- const screenId2 = extractScreenId();
1056
- if (screenId2) {
1057
- activateScreen(screenId2);
1127
+ if (typeof window !== "undefined" && routerMode !== "memory") {
1128
+ const handleURLChange = () => {
1129
+ const match3 = matchCurrentPath();
1130
+ if (match3) {
1131
+ activateFromRouteMatch(match3);
1058
1132
  }
1059
- });
1060
- const screenId = extractScreenId();
1133
+ };
1134
+ if (routerMode === "hash") {
1135
+ window.addEventListener("hashchange", handleURLChange);
1136
+ } else {
1137
+ window.addEventListener("popstate", handleURLChange);
1138
+ }
1139
+ const match2 = matchCurrentPath();
1061
1140
  const autoNavigate = app.config.autoNavigate !== false;
1062
- if (screenId) {
1063
- activateScreen(screenId);
1141
+ if (match2) {
1142
+ activateFromRouteMatch(match2);
1064
1143
  } else if (autoNavigate) {
1065
1144
  const screensets2 = app.screensetRegistry.getAll();
1066
1145
  if (screensets2.length > 0) {
1067
1146
  navigateToScreenset({ screensetId: screensets2[0].id });
1068
1147
  }
1069
1148
  }
1149
+ } else if (routerMode === "memory") {
1150
+ const autoNavigate = app.config.autoNavigate !== false;
1151
+ if (autoNavigate) {
1152
+ const screensets2 = app.screensetRegistry.getAll();
1153
+ if (screensets2.length > 0) {
1154
+ navigateToScreenset({ screensetId: screensets2[0].id });
1155
+ }
1156
+ }
1070
1157
  }
1071
1158
  }
1072
1159
  };
@@ -1085,14 +1172,25 @@ function createRouteRegistry(screensetRegistry3) {
1085
1172
  screenset.menu.forEach((menuScreenItem) => {
1086
1173
  const screenId = menuScreenItem.menuItem.screenId ?? menuScreenItem.menuItem.id;
1087
1174
  if (screenId && menuScreenItem.screen) {
1175
+ const pattern = menuScreenItem.menuItem.path ?? `/${screenId}`;
1176
+ const compiledRoute = compileRoute(pattern, screenset.id, screenId);
1088
1177
  routes.push({
1089
1178
  screensetId: screenset.id,
1090
1179
  screenId,
1091
- loader: menuScreenItem.screen
1180
+ loader: menuScreenItem.screen,
1181
+ pattern,
1182
+ compiledRoute
1092
1183
  });
1093
1184
  }
1094
1185
  });
1095
1186
  });
1187
+ routes.sort((a, b) => {
1188
+ const aHasParams = a.pattern.includes(":");
1189
+ const bHasParams = b.pattern.includes(":");
1190
+ if (!aHasParams && bHasParams) return -1;
1191
+ if (aHasParams && !bHasParams) return 1;
1192
+ return 0;
1193
+ });
1096
1194
  return routes;
1097
1195
  }
1098
1196
  return {
@@ -1148,6 +1246,35 @@ function createRouteRegistry(screensetRegistry3) {
1148
1246
  screensetId,
1149
1247
  screenId
1150
1248
  }));
1249
+ },
1250
+ /**
1251
+ * Match a URL path against registered routes, extracting params.
1252
+ * Returns the first matching route with extracted params, or undefined if no match.
1253
+ */
1254
+ matchRoute(path) {
1255
+ const allRoutes = buildRoutes();
1256
+ const compiledRoutes = allRoutes.map((r) => r.compiledRoute);
1257
+ return matchPath(path, compiledRoutes);
1258
+ },
1259
+ /**
1260
+ * Generate a URL path for a screen with given params.
1261
+ */
1262
+ generatePath(screenId, params = {}) {
1263
+ const allRoutes = buildRoutes();
1264
+ const route = allRoutes.find((r) => r.screenId === screenId);
1265
+ if (!route) {
1266
+ console.warn(`Route not found for screen: ${screenId}`);
1267
+ return `/${screenId}`;
1268
+ }
1269
+ return generatePath(route.compiledRoute, params);
1270
+ },
1271
+ /**
1272
+ * Get the route pattern for a screen.
1273
+ */
1274
+ getRoutePattern(screenId) {
1275
+ const allRoutes = buildRoutes();
1276
+ const route = allRoutes.find((r) => r.screenId === screenId);
1277
+ return route?.pattern;
1151
1278
  }
1152
1279
  };
1153
1280
  }