@hai3/framework 0.2.0-alpha.1 → 0.2.0-alpha.3

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.js CHANGED
@@ -159,8 +159,8 @@ Add the missing plugin: .use(${depName}())`
159
159
  */
160
160
  createStoreWithSlices(slices) {
161
161
  const store = getStore();
162
- slices.forEach((slice9) => {
163
- registerSlice(slice9);
162
+ slices.forEach((slice10) => {
163
+ registerSlice(slice10);
164
164
  });
165
165
  return store;
166
166
  }
@@ -444,6 +444,25 @@ var tenantSlice = slice8;
444
444
  var tenantActions = { setTenant, setTenantLoading, clearTenant };
445
445
  var tenantSlice_default = slice8.reducer;
446
446
 
447
+ // src/slices/mockSlice.ts
448
+ import { createSlice as createSlice9 } from "@hai3/state";
449
+ var SLICE_KEY9 = "mock";
450
+ var initialState9 = {
451
+ enabled: false
452
+ };
453
+ var { slice: slice9, setMockEnabled } = createSlice9({
454
+ name: SLICE_KEY9,
455
+ initialState: initialState9,
456
+ reducers: {
457
+ setMockEnabled: (state, action) => {
458
+ state.enabled = action.payload;
459
+ }
460
+ }
461
+ });
462
+ var mockSlice = slice9;
463
+ var mockActions = { setMockEnabled };
464
+ var mockSlice_default = slice9.reducer;
465
+
447
466
  // src/slices/index.ts
448
467
  var LAYOUT_SLICE_NAME = "layout";
449
468
  var TENANT_SLICE_NAME = "app/tenant";
@@ -489,39 +508,34 @@ function screensets(config) {
489
508
  // src/plugins/themes.ts
490
509
  import { eventBus } from "@hai3/state";
491
510
 
492
- // src/compat.ts
493
- import { getStore as getStore2 } from "@hai3/state";
494
- import { apiRegistry as apiRegistry2 } from "@hai3/api";
495
- import { screensetRegistry as sdkScreensetRegistry2 } from "@hai3/screensets";
496
-
497
511
  // src/registries/themeRegistry.ts
498
- function createThemeRegistry() {
512
+ function createThemeRegistry(config) {
499
513
  const themes2 = /* @__PURE__ */ new Map();
500
- const legacyThemes = /* @__PURE__ */ new Map();
514
+ const uikitThemes = /* @__PURE__ */ new Map();
501
515
  let currentThemeId = null;
502
- let customApplyFn = null;
516
+ const customApplyFn = config?.applyFn ?? null;
503
517
  const subscribers = /* @__PURE__ */ new Set();
504
518
  let version = 0;
505
519
  function notifySubscribers() {
506
520
  version++;
507
521
  subscribers.forEach((callback) => callback());
508
522
  }
509
- function applyCSSVariables(config) {
523
+ function applyCSSVariables(config2) {
510
524
  if (typeof document === "undefined") return;
511
525
  const root = document.documentElement;
512
- Object.entries(config.variables).forEach(([key, value]) => {
526
+ Object.entries(config2.variables).forEach(([key, value]) => {
513
527
  root.style.setProperty(key, value);
514
528
  });
515
529
  }
516
530
  return {
517
531
  /**
518
532
  * Register a theme.
519
- * Supports both new API (config only) and legacy API (id + theme).
533
+ * Supports both config-based API and UIKit theme API.
520
534
  */
521
- register(configOrId, legacyTheme) {
535
+ register(configOrId, uikitTheme) {
522
536
  if (typeof configOrId === "string") {
523
537
  const id = configOrId;
524
- if (!legacyTheme) {
538
+ if (!uikitTheme) {
525
539
  console.warn(`register() called with ID "${id}" but no theme object. Skipping.`);
526
540
  return;
527
541
  }
@@ -529,39 +543,33 @@ function createThemeRegistry() {
529
543
  console.warn(`Theme "${id}" is already registered. Skipping.`);
530
544
  return;
531
545
  }
532
- legacyThemes.set(id, legacyTheme);
546
+ uikitThemes.set(id, uikitTheme);
533
547
  let themeName = id;
534
- if (legacyTheme && typeof legacyTheme === "object" && "name" in legacyTheme) {
535
- const nameValue = legacyTheme.name;
548
+ if (uikitTheme && typeof uikitTheme === "object" && "name" in uikitTheme) {
549
+ const nameValue = uikitTheme.name;
536
550
  if (typeof nameValue === "string") {
537
551
  themeName = nameValue;
538
552
  }
539
553
  }
540
- const config2 = {
554
+ const config3 = {
541
555
  id,
542
556
  name: themeName,
543
557
  variables: {}
544
- // Legacy themes use custom apply function
558
+ // UIKit themes use custom apply function
545
559
  };
546
- themes2.set(id, config2);
560
+ themes2.set(id, config3);
547
561
  return;
548
562
  }
549
- const config = configOrId;
550
- if (themes2.has(config.id)) {
551
- console.warn(`Theme "${config.id}" is already registered. Skipping.`);
563
+ const config2 = configOrId;
564
+ if (themes2.has(config2.id)) {
565
+ console.warn(`Theme "${config2.id}" is already registered. Skipping.`);
552
566
  return;
553
567
  }
554
- themes2.set(config.id, config);
555
- if (config.default && currentThemeId === null) {
556
- this.apply(config.id);
568
+ themes2.set(config2.id, config2);
569
+ if (config2.default && currentThemeId === null) {
570
+ this.apply(config2.id);
557
571
  }
558
572
  },
559
- /**
560
- * Set the apply function (legacy API).
561
- */
562
- setApplyFunction(applyFn) {
563
- customApplyFn = applyFn;
564
- },
565
573
  /**
566
574
  * Get theme by ID.
567
575
  */
@@ -578,16 +586,17 @@ function createThemeRegistry() {
578
586
  * Apply a theme.
579
587
  */
580
588
  apply(id) {
581
- const config = themes2.get(id);
582
- if (!config) {
589
+ const config2 = themes2.get(id);
590
+ if (!config2) {
583
591
  console.warn(`Theme "${id}" not found. Cannot apply.`);
584
592
  return;
585
593
  }
586
- const legacyTheme = legacyThemes.get(id);
587
- if (legacyTheme && customApplyFn) {
588
- customApplyFn(legacyTheme, id);
589
- } else if (config.variables && Object.keys(config.variables).length > 0) {
590
- applyCSSVariables(config);
594
+ if (config2.variables && Object.keys(config2.variables).length > 0) {
595
+ applyCSSVariables(config2);
596
+ }
597
+ const uikitTheme = uikitThemes.get(id);
598
+ if (uikitTheme && customApplyFn) {
599
+ customApplyFn(uikitTheme, id);
591
600
  }
592
601
  currentThemeId = id;
593
602
  notifySubscribers();
@@ -618,117 +627,18 @@ function createThemeRegistry() {
618
627
  };
619
628
  }
620
629
 
621
- // src/registries/routeRegistry.ts
622
- function createRouteRegistry(screensetRegistry3) {
623
- let routes = null;
624
- function buildRoutes() {
625
- if (routes !== null) {
626
- return routes;
627
- }
628
- routes = [];
629
- const screensets2 = screensetRegistry3.getAll();
630
- screensets2.forEach((screenset) => {
631
- screenset.menu.forEach((menuScreenItem) => {
632
- const screenId = menuScreenItem.menuItem.screenId ?? menuScreenItem.menuItem.id;
633
- if (screenId && menuScreenItem.screen) {
634
- routes.push({
635
- screensetId: screenset.id,
636
- screenId,
637
- loader: menuScreenItem.screen
638
- });
639
- }
640
- });
641
- });
642
- return routes;
643
- }
644
- return {
645
- /**
646
- * Check if a screen exists by screenId only (globally unique).
647
- */
648
- hasScreenById(screenId) {
649
- const allRoutes = buildRoutes();
650
- return allRoutes.some((route) => route.screenId === screenId);
651
- },
652
- /**
653
- * Check if a screen exists (legacy, requires both IDs).
654
- */
655
- hasScreen(screensetId, screenId) {
656
- const allRoutes = buildRoutes();
657
- return allRoutes.some(
658
- (route) => route.screensetId === screensetId && route.screenId === screenId
659
- );
660
- },
661
- /**
662
- * Get screenset ID for a given screen ID (reverse lookup).
663
- * Screen IDs are globally unique across all screensets.
664
- */
665
- getScreensetForScreen(screenId) {
666
- const allRoutes = buildRoutes();
667
- const route = allRoutes.find((r) => r.screenId === screenId);
668
- return route?.screensetId;
669
- },
670
- /**
671
- * Get screen loader by screenId only.
672
- */
673
- getScreenById(screenId) {
674
- const allRoutes = buildRoutes();
675
- const route = allRoutes.find((r) => r.screenId === screenId);
676
- return route?.loader;
677
- },
678
- /**
679
- * Get screen loader (legacy, requires both IDs).
680
- */
681
- getScreen(screensetId, screenId) {
682
- const allRoutes = buildRoutes();
683
- const route = allRoutes.find(
684
- (r) => r.screensetId === screensetId && r.screenId === screenId
685
- );
686
- return route?.loader;
687
- },
688
- /**
689
- * Get all routes.
690
- */
691
- getAll() {
692
- const allRoutes = buildRoutes();
693
- return allRoutes.map(({ screensetId, screenId }) => ({
694
- screensetId,
695
- screenId
696
- }));
697
- }
698
- };
699
- }
700
-
701
- // src/compat.ts
702
- import { screensetRegistry } from "@hai3/screensets";
703
- var screenActions3 = screenActions;
704
- var ACCOUNTS_DOMAIN = "accounts";
705
- var themeRegistry = createThemeRegistry();
706
- var routeRegistry = createRouteRegistry(sdkScreensetRegistry2);
707
- var navigateToScreen = (screenId) => {
708
- getStore2().dispatch(screenActions3.setActiveScreen(screenId));
709
- };
710
- var fetchCurrentUser = () => (_dispatch) => {
711
- try {
712
- const accountsService = apiRegistry2.getService(ACCOUNTS_DOMAIN);
713
- const service = accountsService;
714
- service.getCurrentUser?.();
715
- } catch {
716
- console.warn("fetchCurrentUser: accounts service not registered");
717
- }
718
- };
719
-
720
630
  // src/plugins/themes.ts
721
631
  function changeTheme(payload) {
722
632
  eventBus.emit("theme/changed", payload);
723
633
  }
724
- function themes() {
725
- const themeRegistry2 = themeRegistry;
634
+ function themes(config) {
635
+ const themeRegistry = createThemeRegistry(config);
726
636
  return {
727
637
  name: "themes",
728
638
  dependencies: [],
729
639
  provides: {
730
640
  registries: {
731
- themeRegistry: themeRegistry2
641
+ themeRegistry
732
642
  },
733
643
  actions: {
734
644
  changeTheme
@@ -736,11 +646,11 @@ function themes() {
736
646
  },
737
647
  onInit(_app) {
738
648
  eventBus.on("theme/changed", (payload) => {
739
- themeRegistry2.apply(payload.themeId);
649
+ themeRegistry.apply(payload.themeId);
740
650
  });
741
- const themes2 = themeRegistry2.getAll();
651
+ const themes2 = themeRegistry.getAll();
742
652
  if (themes2.length > 0) {
743
- themeRegistry2.apply(themes2[0].id);
653
+ themeRegistry.apply(themes2[0].id);
744
654
  }
745
655
  }
746
656
  };
@@ -874,10 +784,48 @@ function resolveBase(pluginConfig, appConfig) {
874
784
  return normalizeBase(rawBase);
875
785
  }
876
786
 
787
+ // src/utils/routeMatcher.ts
788
+ import { match, compile } from "path-to-regexp";
789
+ function compileRoute(pattern, screensetId, screenId) {
790
+ return {
791
+ pattern,
792
+ screensetId,
793
+ screenId,
794
+ matcher: match(pattern, { decode: decodeURIComponent }),
795
+ toPath: compile(pattern, { encode: encodeURIComponent })
796
+ };
797
+ }
798
+ function matchPath(path, routes) {
799
+ for (const route of routes) {
800
+ const result = route.matcher(path);
801
+ if (result) {
802
+ return {
803
+ screensetId: route.screensetId,
804
+ screenId: route.screenId,
805
+ params: result.params
806
+ };
807
+ }
808
+ }
809
+ return void 0;
810
+ }
811
+ function generatePath(route, params) {
812
+ return route.toPath(params);
813
+ }
814
+ function extractRequiredParams(pattern) {
815
+ const params = [];
816
+ const matches = pattern.match(/:([^/]+)/g);
817
+ if (matches) {
818
+ for (const match2 of matches) {
819
+ params.push(match2.slice(1));
820
+ }
821
+ }
822
+ return params;
823
+ }
824
+
877
825
  // src/plugins/navigation.ts
878
- var screenActions4 = screenActions;
826
+ var screenActions3 = screenActions;
879
827
  var menuActions3 = menuActions;
880
- function navigateToScreen2(payload) {
828
+ function navigateToScreen(payload) {
881
829
  eventBus3.emit("navigation/screen/navigated", payload);
882
830
  }
883
831
  function navigateToScreenset(payload) {
@@ -889,14 +837,46 @@ function navigation(config) {
889
837
  dependencies: ["screensets"],
890
838
  provides: {
891
839
  actions: {
892
- navigateToScreen: navigateToScreen2,
840
+ navigateToScreen,
893
841
  navigateToScreenset
894
842
  }
895
843
  },
896
844
  onInit(app) {
897
845
  const dispatch = app.store.dispatch;
898
846
  const base = resolveBase(config, app.config);
847
+ const routerMode = app.config.routerMode ?? "browser";
899
848
  let currentScreensetId = null;
849
+ const getCurrentPath = () => {
850
+ if (typeof window === "undefined") return "/";
851
+ switch (routerMode) {
852
+ case "hash":
853
+ return window.location.hash.slice(1) || "/";
854
+ case "memory":
855
+ return "/";
856
+ case "browser":
857
+ default:
858
+ return window.location.pathname;
859
+ }
860
+ };
861
+ const updateURL = (path) => {
862
+ if (typeof window === "undefined" || routerMode === "memory") {
863
+ return;
864
+ }
865
+ const url = prependBase(path, base);
866
+ switch (routerMode) {
867
+ case "hash":
868
+ if (window.location.hash !== `#${url}`) {
869
+ window.location.hash = url;
870
+ }
871
+ break;
872
+ case "browser":
873
+ default:
874
+ if (window.location.pathname !== url) {
875
+ window.history.pushState(null, "", url);
876
+ }
877
+ break;
878
+ }
879
+ };
900
880
  const loadScreensetTranslations = async (screensetId, language) => {
901
881
  await i18nRegistry.loadScreensetTranslations(
902
882
  screensetId,
@@ -925,21 +905,17 @@ function navigation(config) {
925
905
  });
926
906
  updateScreensetMenu(screenset);
927
907
  };
928
- const extractScreenId = () => {
929
- const internalPath = stripBase(window.location.pathname, base);
930
- if (!internalPath) {
931
- return null;
932
- }
933
- const parts = internalPath.split("/").filter(Boolean);
934
- return parts[0] || null;
908
+ const extractInternalPath = () => {
909
+ const currentPath = getCurrentPath();
910
+ return stripBase(currentPath, base) || "/";
935
911
  };
936
- const activateScreen = (screenId) => {
937
- const screensetId = app.routeRegistry?.getScreensetForScreen(screenId);
938
- if (!screensetId) {
939
- return;
940
- }
941
- activateScreenset(screensetId);
942
- dispatch(screenActions4.navigateTo(screenId));
912
+ const activateFromRouteMatch = (match2) => {
913
+ activateScreenset(match2.screensetId);
914
+ dispatch(screenActions3.navigateTo(match2.screenId));
915
+ };
916
+ const matchCurrentPath = () => {
917
+ const path = extractInternalPath();
918
+ return app.routeRegistry?.matchRoute(path);
943
919
  };
944
920
  eventBus3.on("navigation/screen/navigated", (payload) => {
945
921
  if (!app.routeRegistry?.hasScreen(payload.screensetId, payload.screenId)) {
@@ -948,14 +924,22 @@ function navigation(config) {
948
924
  );
949
925
  return;
950
926
  }
951
- activateScreenset(payload.screensetId);
952
- dispatch(screenActions4.navigateTo(payload.screenId));
953
- if (typeof window !== "undefined") {
954
- const url = prependBase(`/${payload.screenId}`, base);
955
- if (window.location.pathname !== url) {
956
- window.history.pushState(null, "", url);
927
+ const pattern = app.routeRegistry?.getRoutePattern(payload.screenId);
928
+ if (pattern) {
929
+ const requiredParams = extractRequiredParams(pattern);
930
+ const providedParams = payload.params || {};
931
+ const missingParams = requiredParams.filter((param) => !(param in providedParams));
932
+ if (missingParams.length > 0) {
933
+ console.warn(
934
+ `Screen "${payload.screenId}" requires route params [${requiredParams.join(", ")}] but missing: [${missingParams.join(", ")}]`
935
+ );
936
+ return;
957
937
  }
958
938
  }
939
+ activateScreenset(payload.screensetId);
940
+ dispatch(screenActions3.navigateTo(payload.screenId));
941
+ const path = app.routeRegistry?.generatePath(payload.screenId, payload.params) ?? `/${payload.screenId}`;
942
+ updateURL(path);
959
943
  });
960
944
  eventBus3.on("navigation/screenset/navigated", (payload) => {
961
945
  const screenset = app.screensetRegistry.get(payload.screensetId);
@@ -963,7 +947,7 @@ function navigation(config) {
963
947
  console.warn(`Screenset "${payload.screensetId}" not found.`);
964
948
  return;
965
949
  }
966
- navigateToScreen2({
950
+ navigateToScreen({
967
951
  screensetId: payload.screensetId,
968
952
  screenId: screenset.defaultScreen
969
953
  });
@@ -989,28 +973,161 @@ function navigation(config) {
989
973
  );
990
974
  });
991
975
  });
992
- if (typeof window !== "undefined") {
993
- window.addEventListener("popstate", () => {
994
- const screenId2 = extractScreenId();
995
- if (screenId2) {
996
- activateScreen(screenId2);
976
+ if (typeof window !== "undefined" && routerMode !== "memory") {
977
+ const handleURLChange = () => {
978
+ const match3 = matchCurrentPath();
979
+ if (match3) {
980
+ activateFromRouteMatch(match3);
997
981
  }
998
- });
999
- const screenId = extractScreenId();
982
+ };
983
+ if (routerMode === "hash") {
984
+ window.addEventListener("hashchange", handleURLChange);
985
+ } else {
986
+ window.addEventListener("popstate", handleURLChange);
987
+ }
988
+ const match2 = matchCurrentPath();
1000
989
  const autoNavigate = app.config.autoNavigate !== false;
1001
- if (screenId) {
1002
- activateScreen(screenId);
990
+ if (match2) {
991
+ activateFromRouteMatch(match2);
1003
992
  } else if (autoNavigate) {
1004
993
  const screensets2 = app.screensetRegistry.getAll();
1005
994
  if (screensets2.length > 0) {
1006
995
  navigateToScreenset({ screensetId: screensets2[0].id });
1007
996
  }
1008
997
  }
998
+ } else if (routerMode === "memory") {
999
+ const autoNavigate = app.config.autoNavigate !== false;
1000
+ if (autoNavigate) {
1001
+ const screensets2 = app.screensetRegistry.getAll();
1002
+ if (screensets2.length > 0) {
1003
+ navigateToScreenset({ screensetId: screensets2[0].id });
1004
+ }
1005
+ }
1009
1006
  }
1010
1007
  }
1011
1008
  };
1012
1009
  }
1013
1010
 
1011
+ // src/registries/routeRegistry.ts
1012
+ function createRouteRegistry(screensetRegistry3) {
1013
+ let routes = null;
1014
+ function buildRoutes() {
1015
+ if (routes !== null) {
1016
+ return routes;
1017
+ }
1018
+ routes = [];
1019
+ const screensets2 = screensetRegistry3.getAll();
1020
+ screensets2.forEach((screenset) => {
1021
+ screenset.menu.forEach((menuScreenItem) => {
1022
+ const screenId = menuScreenItem.menuItem.screenId ?? menuScreenItem.menuItem.id;
1023
+ if (screenId && menuScreenItem.screen) {
1024
+ const pattern = menuScreenItem.menuItem.path ?? `/${screenId}`;
1025
+ const compiledRoute = compileRoute(pattern, screenset.id, screenId);
1026
+ routes.push({
1027
+ screensetId: screenset.id,
1028
+ screenId,
1029
+ loader: menuScreenItem.screen,
1030
+ pattern,
1031
+ compiledRoute
1032
+ });
1033
+ }
1034
+ });
1035
+ });
1036
+ routes.sort((a, b) => {
1037
+ const aHasParams = a.pattern.includes(":");
1038
+ const bHasParams = b.pattern.includes(":");
1039
+ if (!aHasParams && bHasParams) return -1;
1040
+ if (aHasParams && !bHasParams) return 1;
1041
+ return 0;
1042
+ });
1043
+ return routes;
1044
+ }
1045
+ return {
1046
+ /**
1047
+ * Check if a screen exists by screenId only (globally unique).
1048
+ */
1049
+ hasScreenById(screenId) {
1050
+ const allRoutes = buildRoutes();
1051
+ return allRoutes.some((route) => route.screenId === screenId);
1052
+ },
1053
+ /**
1054
+ * Check if a screen exists by both screensetId and screenId (explicit lookup when screenset context is known).
1055
+ */
1056
+ hasScreen(screensetId, screenId) {
1057
+ const allRoutes = buildRoutes();
1058
+ return allRoutes.some(
1059
+ (route) => route.screensetId === screensetId && route.screenId === screenId
1060
+ );
1061
+ },
1062
+ /**
1063
+ * Get screenset ID for a given screen ID (reverse lookup).
1064
+ * Screen IDs are globally unique across all screensets.
1065
+ */
1066
+ getScreensetForScreen(screenId) {
1067
+ const allRoutes = buildRoutes();
1068
+ const route = allRoutes.find((r) => r.screenId === screenId);
1069
+ return route?.screensetId;
1070
+ },
1071
+ /**
1072
+ * Get screen loader by screenId only.
1073
+ */
1074
+ getScreenById(screenId) {
1075
+ const allRoutes = buildRoutes();
1076
+ const route = allRoutes.find((r) => r.screenId === screenId);
1077
+ return route?.loader;
1078
+ },
1079
+ /**
1080
+ * Get screen loader by both screensetId and screenId (explicit lookup when screenset context is known).
1081
+ */
1082
+ getScreen(screensetId, screenId) {
1083
+ const allRoutes = buildRoutes();
1084
+ const route = allRoutes.find(
1085
+ (r) => r.screensetId === screensetId && r.screenId === screenId
1086
+ );
1087
+ return route?.loader;
1088
+ },
1089
+ /**
1090
+ * Get all routes.
1091
+ */
1092
+ getAll() {
1093
+ const allRoutes = buildRoutes();
1094
+ return allRoutes.map(({ screensetId, screenId }) => ({
1095
+ screensetId,
1096
+ screenId
1097
+ }));
1098
+ },
1099
+ /**
1100
+ * Match a URL path against registered routes, extracting params.
1101
+ * Returns the first matching route with extracted params, or undefined if no match.
1102
+ */
1103
+ matchRoute(path) {
1104
+ const allRoutes = buildRoutes();
1105
+ const compiledRoutes = allRoutes.map((r) => r.compiledRoute);
1106
+ return matchPath(path, compiledRoutes);
1107
+ },
1108
+ /**
1109
+ * Generate a URL path for a screen with given params.
1110
+ */
1111
+ generatePath(screenId, params = {}) {
1112
+ const allRoutes = buildRoutes();
1113
+ const route = allRoutes.find((r) => r.screenId === screenId);
1114
+ if (!route) {
1115
+ console.warn(`Route not found for screen: ${screenId}`);
1116
+ return `/${screenId}`;
1117
+ }
1118
+ return generatePath(route.compiledRoute, params);
1119
+ },
1120
+ /**
1121
+ * Get the route pattern for a screen.
1122
+ */
1123
+ getRoutePattern(screenId) {
1124
+ const allRoutes = buildRoutes();
1125
+ const route = allRoutes.find((r) => r.screenId === screenId);
1126
+ return route?.pattern;
1127
+ }
1128
+ };
1129
+ }
1130
+
1014
1131
  // src/plugins/routing.ts
1015
1132
  function routing() {
1016
1133
  return {
@@ -1019,8 +1136,8 @@ function routing() {
1019
1136
  onRegister(_app) {
1020
1137
  },
1021
1138
  onInit(app) {
1022
- const routeRegistry2 = createRouteRegistry(app.screensetRegistry);
1023
- app.routeRegistry = routeRegistry2;
1139
+ const routeRegistry = createRouteRegistry(app.screensetRegistry);
1140
+ app.routeRegistry = routeRegistry;
1024
1141
  }
1025
1142
  };
1026
1143
  }
@@ -1068,16 +1185,101 @@ function effects() {
1068
1185
  };
1069
1186
  }
1070
1187
 
1188
+ // src/effects/mockEffects.ts
1189
+ import { eventBus as eventBus5, getStore as getStore2 } from "@hai3/state";
1190
+ import { apiRegistry as apiRegistry2, isMockPlugin } from "@hai3/api";
1191
+ function hasPluginManagement(protocol) {
1192
+ return "plugins" in protocol && typeof protocol.plugins === "object";
1193
+ }
1194
+ var MockEvents = {
1195
+ Toggle: "mock/toggle"
1196
+ };
1197
+ function syncMockPlugins(enabled) {
1198
+ for (const service of apiRegistry2.getAll()) {
1199
+ const registeredPlugins = service.getPlugins();
1200
+ for (const [protocol, plugins] of registeredPlugins) {
1201
+ if (!hasPluginManagement(protocol)) continue;
1202
+ for (const plugin of plugins) {
1203
+ if (isMockPlugin(plugin)) {
1204
+ if (enabled) {
1205
+ const existingPlugins = protocol.plugins.getAll();
1206
+ if (!existingPlugins.includes(plugin)) {
1207
+ protocol.plugins.add(plugin);
1208
+ }
1209
+ } else {
1210
+ protocol.plugins.remove(plugin);
1211
+ }
1212
+ }
1213
+ }
1214
+ }
1215
+ }
1216
+ }
1217
+ function initMockEffects() {
1218
+ const store = getStore2();
1219
+ const unsubscribe = eventBus5.on(MockEvents.Toggle, (payload) => {
1220
+ store.dispatch(setMockEnabled(payload.enabled));
1221
+ syncMockPlugins(payload.enabled);
1222
+ });
1223
+ const currentState = store.getState();
1224
+ if ("mock" in currentState && currentState.mock && typeof currentState.mock === "object" && "enabled" in currentState.mock) {
1225
+ syncMockPlugins(currentState.mock.enabled);
1226
+ }
1227
+ return () => {
1228
+ unsubscribe.unsubscribe();
1229
+ };
1230
+ }
1231
+ function toggleMockMode(enabled) {
1232
+ eventBus5.emit(MockEvents.Toggle, { enabled });
1233
+ }
1234
+
1235
+ // src/plugins/mock.ts
1236
+ var cleanup = null;
1237
+ function isDevEnvironment() {
1238
+ if (typeof window === "undefined") return false;
1239
+ const { hostname } = window.location;
1240
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname.endsWith(".local");
1241
+ }
1242
+ function mock(config) {
1243
+ return {
1244
+ name: "mock",
1245
+ dependencies: ["effects"],
1246
+ provides: {
1247
+ slices: [mockSlice],
1248
+ actions: {
1249
+ toggleMockMode
1250
+ }
1251
+ },
1252
+ onInit() {
1253
+ cleanup = initMockEffects();
1254
+ const isDev = isDevEnvironment();
1255
+ const enabledByDefault = config?.enabledByDefault ?? isDev;
1256
+ if (enabledByDefault) {
1257
+ toggleMockMode(true);
1258
+ if (isDev) {
1259
+ console.log("[HAI3] Mock mode enabled by default (dev environment detected)");
1260
+ }
1261
+ }
1262
+ },
1263
+ onDestroy() {
1264
+ if (cleanup) {
1265
+ cleanup();
1266
+ cleanup = null;
1267
+ }
1268
+ }
1269
+ };
1270
+ }
1271
+
1071
1272
  // src/presets/index.ts
1072
- function full() {
1273
+ function full(config) {
1073
1274
  return [
1074
1275
  effects(),
1075
1276
  screensets({ autoDiscover: true }),
1076
- themes(),
1277
+ themes(config?.themes),
1077
1278
  layout(),
1078
1279
  routing(),
1079
1280
  navigation(),
1080
- i18n()
1281
+ i18n(),
1282
+ mock()
1081
1283
  ];
1082
1284
  }
1083
1285
  function minimal() {
@@ -1099,31 +1301,31 @@ var presets = {
1099
1301
 
1100
1302
  // src/createHAI3App.ts
1101
1303
  function createHAI3App(config) {
1102
- return createHAI3(config).useAll(full()).build();
1304
+ return createHAI3(config).useAll(full({ themes: config?.themes })).build();
1103
1305
  }
1104
1306
 
1105
1307
  // src/registries/index.ts
1106
- import { createScreensetRegistry, screensetRegistry as screensetRegistry2 } from "@hai3/screensets";
1308
+ import { createScreensetRegistry, screensetRegistry } from "@hai3/screensets";
1107
1309
 
1108
1310
  // src/index.ts
1109
- import { eventBus as eventBus6, createStore, getStore as getStore4, registerSlice as registerSlice2, hasSlice, createSlice as createSlice9 } from "@hai3/state";
1311
+ import { eventBus as eventBus7, createStore, getStore as getStore4, registerSlice as registerSlice2, hasSlice, createSlice as createSlice10 } from "@hai3/state";
1110
1312
  import {
1111
1313
  LayoutDomain,
1112
1314
  ScreensetCategory
1113
1315
  } from "@hai3/screensets";
1114
1316
 
1115
1317
  // src/effects/tenantEffects.ts
1116
- import { eventBus as eventBus5, getStore as getStore3 } from "@hai3/state";
1318
+ import { eventBus as eventBus6, getStore as getStore3 } from "@hai3/state";
1117
1319
  var TenantEvents = {
1118
1320
  Changed: "app/tenant/changed",
1119
1321
  Cleared: "app/tenant/cleared"
1120
1322
  };
1121
1323
  function initTenantEffects() {
1122
1324
  const store = getStore3();
1123
- const subChanged = eventBus5.on(TenantEvents.Changed, (payload) => {
1325
+ const subChanged = eventBus6.on(TenantEvents.Changed, (payload) => {
1124
1326
  store.dispatch(setTenant(payload.tenant));
1125
1327
  });
1126
- const subCleared = eventBus5.on(TenantEvents.Cleared, () => {
1328
+ const subCleared = eventBus6.on(TenantEvents.Cleared, () => {
1127
1329
  store.dispatch(clearTenant());
1128
1330
  });
1129
1331
  return () => {
@@ -1132,20 +1334,44 @@ function initTenantEffects() {
1132
1334
  };
1133
1335
  }
1134
1336
  function changeTenant(tenant) {
1135
- eventBus5.emit(TenantEvents.Changed, { tenant });
1337
+ eventBus6.emit(TenantEvents.Changed, { tenant });
1136
1338
  }
1137
1339
  function clearTenantAction() {
1138
- eventBus5.emit(TenantEvents.Cleared, {});
1340
+ eventBus6.emit(TenantEvents.Cleared, {});
1139
1341
  }
1140
1342
  function setTenantLoadingState(loading) {
1141
1343
  getStore3().dispatch(setTenantLoading(loading));
1142
1344
  }
1143
1345
 
1144
1346
  // src/index.ts
1145
- import { apiRegistry as apiRegistry3, BaseApiService, RestProtocol, SseProtocol, MockPlugin } from "@hai3/api";
1347
+ import {
1348
+ apiRegistry as apiRegistry3,
1349
+ BaseApiService,
1350
+ RestProtocol,
1351
+ SseProtocol,
1352
+ RestMockPlugin,
1353
+ SseMockPlugin,
1354
+ MockEventSource,
1355
+ ApiPluginBase,
1356
+ ApiPlugin,
1357
+ ApiProtocol,
1358
+ RestPlugin,
1359
+ RestPluginWithConfig,
1360
+ SsePlugin,
1361
+ SsePluginWithConfig,
1362
+ isShortCircuit,
1363
+ isRestShortCircuit,
1364
+ isSseShortCircuit,
1365
+ MOCK_PLUGIN,
1366
+ isMockPlugin as isMockPlugin2
1367
+ } from "@hai3/api";
1146
1368
  import { i18nRegistry as i18nRegistry2, I18nRegistryImpl, createI18nRegistry, Language as Language2, SUPPORTED_LANGUAGES, getLanguageMetadata, TextDirection, LanguageDisplayMode } from "@hai3/i18n";
1147
1369
  import { I18nRegistryImpl as I18nRegistryImpl2 } from "@hai3/i18n";
1148
1370
 
1371
+ // src/compat.ts
1372
+ import { screensetRegistry as screensetRegistry2 } from "@hai3/screensets";
1373
+ var ACCOUNTS_DOMAIN = "accounts";
1374
+
1149
1375
  // src/migration.ts
1150
1376
  var STATE_PATH_MAPPING = {
1151
1377
  // App state (moved to app slice)
@@ -1199,9 +1425,11 @@ function hasLegacyUicoreState(state) {
1199
1425
  function hasNewLayoutState(state) {
1200
1426
  return typeof state === "object" && state !== null && "layout" in state && typeof state.layout === "object";
1201
1427
  }
1202
- var legacySelectors = {};
1203
1428
  export {
1204
1429
  ACCOUNTS_DOMAIN,
1430
+ ApiPlugin,
1431
+ ApiPluginBase,
1432
+ ApiProtocol,
1205
1433
  BaseApiService,
1206
1434
  I18nRegistryImpl2 as I18nRegistry,
1207
1435
  I18nRegistryImpl,
@@ -1209,11 +1437,19 @@ export {
1209
1437
  Language2 as Language,
1210
1438
  LanguageDisplayMode,
1211
1439
  LayoutDomain,
1212
- MockPlugin,
1440
+ MOCK_PLUGIN,
1441
+ MockEventSource,
1442
+ MockEvents,
1443
+ RestMockPlugin,
1444
+ RestPlugin,
1445
+ RestPluginWithConfig,
1213
1446
  RestProtocol,
1214
1447
  STATE_PATH_MAPPING,
1215
1448
  SUPPORTED_LANGUAGES,
1216
1449
  ScreensetCategory,
1450
+ SseMockPlugin,
1451
+ SsePlugin,
1452
+ SsePluginWithConfig,
1217
1453
  SseProtocol,
1218
1454
  TENANT_SLICE_NAME,
1219
1455
  TenantEvents,
@@ -1233,12 +1469,11 @@ export {
1233
1469
  createLegacySelector,
1234
1470
  createRouteRegistry,
1235
1471
  createScreensetRegistry,
1236
- createSlice9 as createSlice,
1472
+ createSlice10 as createSlice,
1237
1473
  createStore,
1238
1474
  createThemeRegistry,
1239
1475
  effects,
1240
- eventBus6 as eventBus,
1241
- fetchCurrentUser,
1476
+ eventBus7 as eventBus,
1242
1477
  footerActions,
1243
1478
  footerSlice,
1244
1479
  full,
@@ -1254,17 +1489,23 @@ export {
1254
1489
  hideOverlay,
1255
1490
  i18n,
1256
1491
  i18nRegistry2 as i18nRegistry,
1492
+ initMockEffects,
1257
1493
  initTenantEffects,
1258
1494
  isDeprecationWarningsEnabled,
1495
+ isMockPlugin2 as isMockPlugin,
1496
+ isRestShortCircuit,
1497
+ isShortCircuit,
1498
+ isSseShortCircuit,
1259
1499
  layout,
1260
1500
  layoutDomainReducers,
1261
1501
  layoutReducer,
1262
- legacySelectors,
1263
1502
  menuActions,
1264
1503
  menuSlice,
1265
1504
  minimal,
1505
+ mock,
1506
+ mockActions,
1507
+ mockSlice,
1266
1508
  navigateTo,
1267
- navigateToScreen,
1268
1509
  navigation,
1269
1510
  openPopup,
1270
1511
  overlayActions,
@@ -1273,11 +1514,10 @@ export {
1273
1514
  popupSlice,
1274
1515
  presets,
1275
1516
  registerSlice2 as registerSlice,
1276
- routeRegistry,
1277
1517
  routing,
1278
1518
  screenActions,
1279
1519
  screenSlice,
1280
- screensetRegistry,
1520
+ screensetRegistry2 as screensetRegistry,
1281
1521
  screensets,
1282
1522
  setActiveScreen,
1283
1523
  setDeprecationWarnings,
@@ -1288,6 +1528,7 @@ export {
1288
1528
  setMenuConfig,
1289
1529
  setMenuItems,
1290
1530
  setMenuVisible,
1531
+ setMockEnabled,
1291
1532
  setOverlayVisible,
1292
1533
  setScreenLoading,
1293
1534
  setSidebarCollapsed,
@@ -1307,9 +1548,9 @@ export {
1307
1548
  tenantActions,
1308
1549
  tenantSlice_default as tenantReducer,
1309
1550
  tenantSlice,
1310
- themeRegistry,
1311
1551
  themes,
1312
1552
  toggleMenu,
1553
+ toggleMockMode,
1313
1554
  toggleSidebar
1314
1555
  };
1315
1556
  //# sourceMappingURL=index.js.map