@hai3/framework 0.2.0-alpha.0 → 0.2.0-alpha.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/dist/index.cjs CHANGED
@@ -21,18 +21,29 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  ACCOUNTS_DOMAIN: () => ACCOUNTS_DOMAIN,
24
+ ApiPlugin: () => import_api3.ApiPlugin,
25
+ ApiPluginBase: () => import_api3.ApiPluginBase,
26
+ ApiProtocol: () => import_api3.ApiProtocol,
24
27
  BaseApiService: () => import_api3.BaseApiService,
25
28
  I18nRegistry: () => import_i18n6.I18nRegistryImpl,
26
29
  I18nRegistryImpl: () => import_i18n5.I18nRegistryImpl,
27
30
  LAYOUT_SLICE_NAME: () => LAYOUT_SLICE_NAME,
28
31
  Language: () => import_i18n5.Language,
29
32
  LanguageDisplayMode: () => import_i18n5.LanguageDisplayMode,
30
- LayoutDomain: () => import_screensets7.LayoutDomain,
31
- MockPlugin: () => import_api3.MockPlugin,
33
+ LayoutDomain: () => import_screensets6.LayoutDomain,
34
+ MOCK_PLUGIN: () => import_api3.MOCK_PLUGIN,
35
+ MockEventSource: () => import_api3.MockEventSource,
36
+ MockEvents: () => MockEvents,
37
+ RestMockPlugin: () => import_api3.RestMockPlugin,
38
+ RestPlugin: () => import_api3.RestPlugin,
39
+ RestPluginWithConfig: () => import_api3.RestPluginWithConfig,
32
40
  RestProtocol: () => import_api3.RestProtocol,
33
41
  STATE_PATH_MAPPING: () => STATE_PATH_MAPPING,
34
42
  SUPPORTED_LANGUAGES: () => import_i18n5.SUPPORTED_LANGUAGES,
35
- ScreensetCategory: () => import_screensets7.ScreensetCategory,
43
+ ScreensetCategory: () => import_screensets6.ScreensetCategory,
44
+ SseMockPlugin: () => import_api3.SseMockPlugin,
45
+ SsePlugin: () => import_api3.SsePlugin,
46
+ SsePluginWithConfig: () => import_api3.SsePluginWithConfig,
36
47
  SseProtocol: () => import_api3.SseProtocol,
37
48
  TENANT_SLICE_NAME: () => TENANT_SLICE_NAME,
38
49
  TenantEvents: () => TenantEvents,
@@ -51,39 +62,44 @@ __export(src_exports, {
51
62
  createI18nRegistry: () => import_i18n5.createI18nRegistry,
52
63
  createLegacySelector: () => createLegacySelector,
53
64
  createRouteRegistry: () => createRouteRegistry,
54
- createScreensetRegistry: () => import_screensets6.createScreensetRegistry,
55
- createSlice: () => import_state16.createSlice,
56
- createStore: () => import_state16.createStore,
65
+ createScreensetRegistry: () => import_screensets4.createScreensetRegistry,
66
+ createSlice: () => import_state17.createSlice,
67
+ createStore: () => import_state17.createStore,
57
68
  createThemeRegistry: () => createThemeRegistry,
58
69
  effects: () => effects,
59
- eventBus: () => import_state16.eventBus,
60
- fetchCurrentUser: () => fetchCurrentUser,
70
+ eventBus: () => import_state17.eventBus,
61
71
  footerActions: () => footerActions,
62
72
  footerSlice: () => footerSlice,
63
73
  full: () => full,
64
74
  getLanguageMetadata: () => import_i18n5.getLanguageMetadata,
65
75
  getLayoutDomainState: () => getLayoutDomainState,
66
- getStore: () => import_state16.getStore,
76
+ getStore: () => import_state17.getStore,
67
77
  hasLegacyUicoreState: () => hasLegacyUicoreState,
68
78
  hasNewLayoutState: () => hasNewLayoutState,
69
- hasSlice: () => import_state16.hasSlice,
79
+ hasSlice: () => import_state17.hasSlice,
70
80
  headerActions: () => headerActions,
71
81
  headerSlice: () => headerSlice,
72
82
  headless: () => headless,
73
83
  hideOverlay: () => hideOverlay,
74
84
  i18n: () => i18n,
75
85
  i18nRegistry: () => import_i18n5.i18nRegistry,
86
+ initMockEffects: () => initMockEffects,
76
87
  initTenantEffects: () => initTenantEffects,
77
88
  isDeprecationWarningsEnabled: () => isDeprecationWarningsEnabled,
89
+ isMockPlugin: () => import_api3.isMockPlugin,
90
+ isRestShortCircuit: () => import_api3.isRestShortCircuit,
91
+ isShortCircuit: () => import_api3.isShortCircuit,
92
+ isSseShortCircuit: () => import_api3.isSseShortCircuit,
78
93
  layout: () => layout,
79
94
  layoutDomainReducers: () => layoutDomainReducers,
80
95
  layoutReducer: () => layoutReducer,
81
- legacySelectors: () => legacySelectors,
82
96
  menuActions: () => menuActions,
83
97
  menuSlice: () => menuSlice,
84
98
  minimal: () => minimal,
99
+ mock: () => mock,
100
+ mockActions: () => mockActions,
101
+ mockSlice: () => mockSlice,
85
102
  navigateTo: () => navigateTo,
86
- navigateToScreen: () => navigateToScreen,
87
103
  navigation: () => navigation,
88
104
  openPopup: () => openPopup,
89
105
  overlayActions: () => overlayActions,
@@ -91,12 +107,11 @@ __export(src_exports, {
91
107
  popupActions: () => popupActions,
92
108
  popupSlice: () => popupSlice,
93
109
  presets: () => presets,
94
- registerSlice: () => import_state16.registerSlice,
95
- routeRegistry: () => routeRegistry,
110
+ registerSlice: () => import_state17.registerSlice,
96
111
  routing: () => routing,
97
112
  screenActions: () => screenActions,
98
113
  screenSlice: () => screenSlice,
99
- screensetRegistry: () => import_screensets3.screensetRegistry,
114
+ screensetRegistry: () => import_screensets5.screensetRegistry,
100
115
  screensets: () => screensets,
101
116
  setActiveScreen: () => setActiveScreen,
102
117
  setDeprecationWarnings: () => setDeprecationWarnings,
@@ -107,6 +122,7 @@ __export(src_exports, {
107
122
  setMenuConfig: () => setMenuConfig,
108
123
  setMenuItems: () => setMenuItems,
109
124
  setMenuVisible: () => setMenuVisible,
125
+ setMockEnabled: () => setMockEnabled,
110
126
  setOverlayVisible: () => setOverlayVisible,
111
127
  setScreenLoading: () => setScreenLoading,
112
128
  setSidebarCollapsed: () => setSidebarCollapsed,
@@ -126,9 +142,9 @@ __export(src_exports, {
126
142
  tenantActions: () => tenantActions,
127
143
  tenantReducer: () => tenantSlice_default,
128
144
  tenantSlice: () => tenantSlice,
129
- themeRegistry: () => themeRegistry,
130
145
  themes: () => themes,
131
146
  toggleMenu: () => toggleMenu,
147
+ toggleMockMode: () => toggleMockMode,
132
148
  toggleSidebar: () => toggleSidebar
133
149
  });
134
150
  module.exports = __toCommonJS(src_exports);
@@ -294,8 +310,8 @@ Add the missing plugin: .use(${depName}())`
294
310
  */
295
311
  createStoreWithSlices(slices) {
296
312
  const store = (0, import_state.getStore)();
297
- slices.forEach((slice9) => {
298
- (0, import_state.registerSlice)(slice9);
313
+ slices.forEach((slice10) => {
314
+ (0, import_state.registerSlice)(slice10);
299
315
  });
300
316
  return store;
301
317
  }
@@ -579,6 +595,25 @@ var tenantSlice = slice8;
579
595
  var tenantActions = { setTenant, setTenantLoading, clearTenant };
580
596
  var tenantSlice_default = slice8.reducer;
581
597
 
598
+ // src/slices/mockSlice.ts
599
+ var import_state10 = require("@hai3/state");
600
+ var SLICE_KEY9 = "mock";
601
+ var initialState9 = {
602
+ enabled: false
603
+ };
604
+ var { slice: slice9, setMockEnabled } = (0, import_state10.createSlice)({
605
+ name: SLICE_KEY9,
606
+ initialState: initialState9,
607
+ reducers: {
608
+ setMockEnabled: (state, action) => {
609
+ state.enabled = action.payload;
610
+ }
611
+ }
612
+ });
613
+ var mockSlice = slice9;
614
+ var mockActions = { setMockEnabled };
615
+ var mockSlice_default = slice9.reducer;
616
+
582
617
  // src/slices/index.ts
583
618
  var LAYOUT_SLICE_NAME = "layout";
584
619
  var TENANT_SLICE_NAME = "app/tenant";
@@ -624,39 +659,34 @@ function screensets(config) {
624
659
  // src/plugins/themes.ts
625
660
  var import_state11 = require("@hai3/state");
626
661
 
627
- // src/compat.ts
628
- var import_state10 = require("@hai3/state");
629
- var import_api2 = require("@hai3/api");
630
- var import_screensets2 = require("@hai3/screensets");
631
-
632
662
  // src/registries/themeRegistry.ts
633
- function createThemeRegistry() {
663
+ function createThemeRegistry(config) {
634
664
  const themes2 = /* @__PURE__ */ new Map();
635
- const legacyThemes = /* @__PURE__ */ new Map();
665
+ const uikitThemes = /* @__PURE__ */ new Map();
636
666
  let currentThemeId = null;
637
- let customApplyFn = null;
667
+ const customApplyFn = config?.applyFn ?? null;
638
668
  const subscribers = /* @__PURE__ */ new Set();
639
669
  let version = 0;
640
670
  function notifySubscribers() {
641
671
  version++;
642
672
  subscribers.forEach((callback) => callback());
643
673
  }
644
- function applyCSSVariables(config) {
674
+ function applyCSSVariables(config2) {
645
675
  if (typeof document === "undefined") return;
646
676
  const root = document.documentElement;
647
- Object.entries(config.variables).forEach(([key, value]) => {
677
+ Object.entries(config2.variables).forEach(([key, value]) => {
648
678
  root.style.setProperty(key, value);
649
679
  });
650
680
  }
651
681
  return {
652
682
  /**
653
683
  * Register a theme.
654
- * Supports both new API (config only) and legacy API (id + theme).
684
+ * Supports both config-based API and UIKit theme API.
655
685
  */
656
- register(configOrId, legacyTheme) {
686
+ register(configOrId, uikitTheme) {
657
687
  if (typeof configOrId === "string") {
658
688
  const id = configOrId;
659
- if (!legacyTheme) {
689
+ if (!uikitTheme) {
660
690
  console.warn(`register() called with ID "${id}" but no theme object. Skipping.`);
661
691
  return;
662
692
  }
@@ -664,39 +694,33 @@ function createThemeRegistry() {
664
694
  console.warn(`Theme "${id}" is already registered. Skipping.`);
665
695
  return;
666
696
  }
667
- legacyThemes.set(id, legacyTheme);
697
+ uikitThemes.set(id, uikitTheme);
668
698
  let themeName = id;
669
- if (legacyTheme && typeof legacyTheme === "object" && "name" in legacyTheme) {
670
- const nameValue = legacyTheme.name;
699
+ if (uikitTheme && typeof uikitTheme === "object" && "name" in uikitTheme) {
700
+ const nameValue = uikitTheme.name;
671
701
  if (typeof nameValue === "string") {
672
702
  themeName = nameValue;
673
703
  }
674
704
  }
675
- const config2 = {
705
+ const config3 = {
676
706
  id,
677
707
  name: themeName,
678
708
  variables: {}
679
- // Legacy themes use custom apply function
709
+ // UIKit themes use custom apply function
680
710
  };
681
- themes2.set(id, config2);
711
+ themes2.set(id, config3);
682
712
  return;
683
713
  }
684
- const config = configOrId;
685
- if (themes2.has(config.id)) {
686
- console.warn(`Theme "${config.id}" is already registered. Skipping.`);
714
+ const config2 = configOrId;
715
+ if (themes2.has(config2.id)) {
716
+ console.warn(`Theme "${config2.id}" is already registered. Skipping.`);
687
717
  return;
688
718
  }
689
- themes2.set(config.id, config);
690
- if (config.default && currentThemeId === null) {
691
- this.apply(config.id);
719
+ themes2.set(config2.id, config2);
720
+ if (config2.default && currentThemeId === null) {
721
+ this.apply(config2.id);
692
722
  }
693
723
  },
694
- /**
695
- * Set the apply function (legacy API).
696
- */
697
- setApplyFunction(applyFn) {
698
- customApplyFn = applyFn;
699
- },
700
724
  /**
701
725
  * Get theme by ID.
702
726
  */
@@ -713,16 +737,17 @@ function createThemeRegistry() {
713
737
  * Apply a theme.
714
738
  */
715
739
  apply(id) {
716
- const config = themes2.get(id);
717
- if (!config) {
740
+ const config2 = themes2.get(id);
741
+ if (!config2) {
718
742
  console.warn(`Theme "${id}" not found. Cannot apply.`);
719
743
  return;
720
744
  }
721
- const legacyTheme = legacyThemes.get(id);
722
- if (legacyTheme && customApplyFn) {
723
- customApplyFn(legacyTheme, id);
724
- } else if (config.variables && Object.keys(config.variables).length > 0) {
725
- applyCSSVariables(config);
745
+ if (config2.variables && Object.keys(config2.variables).length > 0) {
746
+ applyCSSVariables(config2);
747
+ }
748
+ const uikitTheme = uikitThemes.get(id);
749
+ if (uikitTheme && customApplyFn) {
750
+ customApplyFn(uikitTheme, id);
726
751
  }
727
752
  currentThemeId = id;
728
753
  notifySubscribers();
@@ -753,117 +778,18 @@ function createThemeRegistry() {
753
778
  };
754
779
  }
755
780
 
756
- // src/registries/routeRegistry.ts
757
- function createRouteRegistry(screensetRegistry3) {
758
- let routes = null;
759
- function buildRoutes() {
760
- if (routes !== null) {
761
- return routes;
762
- }
763
- routes = [];
764
- const screensets2 = screensetRegistry3.getAll();
765
- screensets2.forEach((screenset) => {
766
- screenset.menu.forEach((menuScreenItem) => {
767
- const screenId = menuScreenItem.menuItem.screenId ?? menuScreenItem.menuItem.id;
768
- if (screenId && menuScreenItem.screen) {
769
- routes.push({
770
- screensetId: screenset.id,
771
- screenId,
772
- loader: menuScreenItem.screen
773
- });
774
- }
775
- });
776
- });
777
- return routes;
778
- }
779
- return {
780
- /**
781
- * Check if a screen exists by screenId only (globally unique).
782
- */
783
- hasScreenById(screenId) {
784
- const allRoutes = buildRoutes();
785
- return allRoutes.some((route) => route.screenId === screenId);
786
- },
787
- /**
788
- * Check if a screen exists (legacy, requires both IDs).
789
- */
790
- hasScreen(screensetId, screenId) {
791
- const allRoutes = buildRoutes();
792
- return allRoutes.some(
793
- (route) => route.screensetId === screensetId && route.screenId === screenId
794
- );
795
- },
796
- /**
797
- * Get screenset ID for a given screen ID (reverse lookup).
798
- * Screen IDs are globally unique across all screensets.
799
- */
800
- getScreensetForScreen(screenId) {
801
- const allRoutes = buildRoutes();
802
- const route = allRoutes.find((r) => r.screenId === screenId);
803
- return route?.screensetId;
804
- },
805
- /**
806
- * Get screen loader by screenId only.
807
- */
808
- getScreenById(screenId) {
809
- const allRoutes = buildRoutes();
810
- const route = allRoutes.find((r) => r.screenId === screenId);
811
- return route?.loader;
812
- },
813
- /**
814
- * Get screen loader (legacy, requires both IDs).
815
- */
816
- getScreen(screensetId, screenId) {
817
- const allRoutes = buildRoutes();
818
- const route = allRoutes.find(
819
- (r) => r.screensetId === screensetId && r.screenId === screenId
820
- );
821
- return route?.loader;
822
- },
823
- /**
824
- * Get all routes.
825
- */
826
- getAll() {
827
- const allRoutes = buildRoutes();
828
- return allRoutes.map(({ screensetId, screenId }) => ({
829
- screensetId,
830
- screenId
831
- }));
832
- }
833
- };
834
- }
835
-
836
- // src/compat.ts
837
- var import_screensets3 = require("@hai3/screensets");
838
- var screenActions3 = screenActions;
839
- var ACCOUNTS_DOMAIN = "accounts";
840
- var themeRegistry = createThemeRegistry();
841
- var routeRegistry = createRouteRegistry(import_screensets2.screensetRegistry);
842
- var navigateToScreen = (screenId) => {
843
- (0, import_state10.getStore)().dispatch(screenActions3.setActiveScreen(screenId));
844
- };
845
- var fetchCurrentUser = () => (_dispatch) => {
846
- try {
847
- const accountsService = import_api2.apiRegistry.getService(ACCOUNTS_DOMAIN);
848
- const service = accountsService;
849
- service.getCurrentUser?.();
850
- } catch {
851
- console.warn("fetchCurrentUser: accounts service not registered");
852
- }
853
- };
854
-
855
781
  // src/plugins/themes.ts
856
782
  function changeTheme(payload) {
857
783
  import_state11.eventBus.emit("theme/changed", payload);
858
784
  }
859
- function themes() {
860
- const themeRegistry2 = themeRegistry;
785
+ function themes(config) {
786
+ const themeRegistry = createThemeRegistry(config);
861
787
  return {
862
788
  name: "themes",
863
789
  dependencies: [],
864
790
  provides: {
865
791
  registries: {
866
- themeRegistry: themeRegistry2
792
+ themeRegistry
867
793
  },
868
794
  actions: {
869
795
  changeTheme
@@ -871,11 +797,11 @@ function themes() {
871
797
  },
872
798
  onInit(_app) {
873
799
  import_state11.eventBus.on("theme/changed", (payload) => {
874
- themeRegistry2.apply(payload.themeId);
800
+ themeRegistry.apply(payload.themeId);
875
801
  });
876
- const themes2 = themeRegistry2.getAll();
802
+ const themes2 = themeRegistry.getAll();
877
803
  if (themes2.length > 0) {
878
- themeRegistry2.apply(themes2[0].id);
804
+ themeRegistry.apply(themes2[0].id);
879
805
  }
880
806
  }
881
807
  };
@@ -972,60 +898,124 @@ function layout() {
972
898
  // src/plugins/navigation.ts
973
899
  var import_state13 = require("@hai3/state");
974
900
  var import_i18n = require("@hai3/i18n");
975
- var screenActions4 = screenActions;
976
- var menuActions3 = menuActions;
977
- function buildMenuItems(screenset) {
978
- return screenset.menu.map((item) => ({
979
- id: item.menuItem.screenId ?? item.menuItem.id,
980
- label: item.menuItem.label,
981
- icon: item.menuItem.icon
982
- }));
901
+
902
+ // src/utils/basePath.ts
903
+ function normalizeBase(base) {
904
+ if (!base) {
905
+ return "/";
906
+ }
907
+ let normalized = base.startsWith("/") ? base : `/${base}`;
908
+ if (normalized !== "/" && normalized.endsWith("/")) {
909
+ normalized = normalized.slice(0, -1);
910
+ }
911
+ return normalized;
983
912
  }
984
- function navigateToScreen2(payload) {
913
+ function stripBase(pathname, base) {
914
+ if (base === "/") {
915
+ return pathname;
916
+ }
917
+ if (!pathname.startsWith(base)) {
918
+ return null;
919
+ }
920
+ const nextChar = pathname.charAt(base.length);
921
+ if (nextChar && nextChar !== "/") {
922
+ return null;
923
+ }
924
+ return pathname.slice(base.length) || "/";
925
+ }
926
+ function prependBase(path, base) {
927
+ if (base === "/") {
928
+ return path;
929
+ }
930
+ const cleanPath = path.startsWith("/") ? path : `/${path}`;
931
+ return `${base}${cleanPath}`;
932
+ }
933
+ function resolveBase(pluginConfig, appConfig) {
934
+ const rawBase = pluginConfig?.base ?? appConfig?.base ?? "/";
935
+ return normalizeBase(rawBase);
936
+ }
937
+
938
+ // src/plugins/navigation.ts
939
+ var screenActions3 = screenActions;
940
+ var menuActions3 = menuActions;
941
+ function navigateToScreen(payload) {
985
942
  import_state13.eventBus.emit("navigation/screen/navigated", payload);
986
943
  }
987
944
  function navigateToScreenset(payload) {
988
945
  import_state13.eventBus.emit("navigation/screenset/navigated", payload);
989
946
  }
990
- function navigation() {
947
+ function navigation(config) {
991
948
  return {
992
949
  name: "navigation",
993
950
  dependencies: ["screensets"],
994
951
  provides: {
995
952
  actions: {
996
- navigateToScreen: navigateToScreen2,
953
+ navigateToScreen,
997
954
  navigateToScreenset
998
955
  }
999
956
  },
1000
957
  onInit(app) {
1001
958
  const dispatch = app.store.dispatch;
959
+ const base = resolveBase(config, app.config);
1002
960
  let currentScreensetId = null;
1003
- async function loadScreensetTranslations(screensetId, language) {
1004
- await import_i18n.i18nRegistry.loadScreensetTranslations(screensetId, language);
1005
- }
1006
- function updateMenuForScreenset(screensetId) {
1007
- if (screensetId === currentScreensetId) return;
961
+ const loadScreensetTranslations = async (screensetId, language) => {
962
+ await import_i18n.i18nRegistry.loadScreensetTranslations(
963
+ screensetId,
964
+ language
965
+ );
966
+ };
967
+ const updateScreensetMenu = (screenset) => {
968
+ const menuItems = screenset.menu.map((item) => ({
969
+ id: item.menuItem.screenId ?? item.menuItem.id,
970
+ label: item.menuItem.label,
971
+ icon: item.menuItem.icon
972
+ }));
973
+ dispatch(menuActions3.setMenuItems(menuItems));
974
+ };
975
+ const activateScreenset = (screensetId) => {
976
+ if (screensetId === currentScreensetId) {
977
+ return;
978
+ }
1008
979
  const screenset = app.screensetRegistry.get(screensetId);
1009
- if (!screenset) return;
980
+ if (!screenset) {
981
+ return;
982
+ }
1010
983
  currentScreensetId = screensetId;
1011
984
  loadScreensetTranslations(screensetId).catch((err) => {
1012
985
  console.warn(`[HAI3] Failed to load translations for screenset ${screensetId}:`, err);
1013
986
  });
1014
- const menuItems = buildMenuItems(screenset);
1015
- dispatch(menuActions3.setMenuItems(menuItems));
1016
- }
987
+ updateScreensetMenu(screenset);
988
+ };
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;
996
+ };
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));
1004
+ };
1017
1005
  import_state13.eventBus.on("navigation/screen/navigated", (payload) => {
1018
- if (app.routeRegistry && !app.routeRegistry.hasScreen(payload.screensetId, payload.screenId)) {
1006
+ if (!app.routeRegistry?.hasScreen(payload.screensetId, payload.screenId)) {
1019
1007
  console.warn(
1020
1008
  `Screen "${payload.screenId}" in screenset "${payload.screensetId}" not found.`
1021
1009
  );
1022
1010
  return;
1023
1011
  }
1024
- updateMenuForScreenset(payload.screensetId);
1025
- dispatch(screenActions4.navigateTo(payload.screenId));
1012
+ activateScreenset(payload.screensetId);
1013
+ dispatch(screenActions3.navigateTo(payload.screenId));
1026
1014
  if (typeof window !== "undefined") {
1027
- const url = `/${payload.screenId}`;
1028
- window.history.pushState(null, "", url);
1015
+ const url = prependBase(`/${payload.screenId}`, base);
1016
+ if (window.location.pathname !== url) {
1017
+ window.history.pushState(null, "", url);
1018
+ }
1029
1019
  }
1030
1020
  });
1031
1021
  import_state13.eventBus.on("navigation/screenset/navigated", (payload) => {
@@ -1034,7 +1024,7 @@ function navigation() {
1034
1024
  console.warn(`Screenset "${payload.screensetId}" not found.`);
1035
1025
  return;
1036
1026
  }
1037
- navigateToScreen2({
1027
+ navigateToScreen({
1038
1028
  screensetId: payload.screensetId,
1039
1029
  screenId: screenset.defaultScreen
1040
1030
  });
@@ -1042,15 +1032,18 @@ function navigation() {
1042
1032
  let lastLoadedLanguage = null;
1043
1033
  import_i18n.i18nRegistry.subscribe(() => {
1044
1034
  const currentLanguage = import_i18n.i18nRegistry.getLanguage();
1045
- if (!currentLanguage || currentLanguage === lastLoadedLanguage) return;
1046
- if (!currentScreensetId) return;
1035
+ if (!currentLanguage || currentLanguage === lastLoadedLanguage) {
1036
+ return;
1037
+ }
1038
+ if (!currentScreensetId) {
1039
+ return;
1040
+ }
1047
1041
  const screenset = app.screensetRegistry.get(currentScreensetId);
1048
- if (!screenset) return;
1042
+ if (!screenset) {
1043
+ return;
1044
+ }
1049
1045
  lastLoadedLanguage = currentLanguage;
1050
- loadScreensetTranslations(currentScreensetId, currentLanguage).then(() => {
1051
- const menuItems = buildMenuItems(screenset);
1052
- dispatch(menuActions3.setMenuItems(menuItems));
1053
- }).catch((err) => {
1046
+ loadScreensetTranslations(currentScreensetId, currentLanguage).then(() => updateScreensetMenu(screenset)).catch((err) => {
1054
1047
  console.warn(
1055
1048
  `[HAI3] Failed to reload translations for screenset ${currentScreensetId}:`,
1056
1049
  err
@@ -1059,31 +1052,15 @@ function navigation() {
1059
1052
  });
1060
1053
  if (typeof window !== "undefined") {
1061
1054
  window.addEventListener("popstate", () => {
1062
- const path2 = window.location.pathname;
1063
- const parts2 = path2.split("/").filter(Boolean);
1064
- if (parts2.length >= 1) {
1065
- const screenId = parts2[0];
1066
- const screensetId = app.routeRegistry?.getScreensetForScreen(screenId);
1067
- if (screensetId) {
1068
- updateMenuForScreenset(screensetId);
1069
- dispatch(screenActions4.navigateTo(screenId));
1070
- }
1055
+ const screenId2 = extractScreenId();
1056
+ if (screenId2) {
1057
+ activateScreen(screenId2);
1071
1058
  }
1072
1059
  });
1073
- const path = window.location.pathname;
1074
- const parts = path.split("/").filter(Boolean);
1060
+ const screenId = extractScreenId();
1075
1061
  const autoNavigate = app.config.autoNavigate !== false;
1076
- if (parts.length >= 1) {
1077
- const screenId = parts[0];
1078
- const screensetId = app.routeRegistry?.getScreensetForScreen(screenId);
1079
- if (screensetId) {
1080
- navigateToScreen2({ screensetId, screenId });
1081
- } else if (autoNavigate) {
1082
- const screensets2 = app.screensetRegistry.getAll();
1083
- if (screensets2.length > 0) {
1084
- navigateToScreenset({ screensetId: screensets2[0].id });
1085
- }
1086
- }
1062
+ if (screenId) {
1063
+ activateScreen(screenId);
1087
1064
  } else if (autoNavigate) {
1088
1065
  const screensets2 = app.screensetRegistry.getAll();
1089
1066
  if (screensets2.length > 0) {
@@ -1095,6 +1072,86 @@ function navigation() {
1095
1072
  };
1096
1073
  }
1097
1074
 
1075
+ // src/registries/routeRegistry.ts
1076
+ function createRouteRegistry(screensetRegistry3) {
1077
+ let routes = null;
1078
+ function buildRoutes() {
1079
+ if (routes !== null) {
1080
+ return routes;
1081
+ }
1082
+ routes = [];
1083
+ const screensets2 = screensetRegistry3.getAll();
1084
+ screensets2.forEach((screenset) => {
1085
+ screenset.menu.forEach((menuScreenItem) => {
1086
+ const screenId = menuScreenItem.menuItem.screenId ?? menuScreenItem.menuItem.id;
1087
+ if (screenId && menuScreenItem.screen) {
1088
+ routes.push({
1089
+ screensetId: screenset.id,
1090
+ screenId,
1091
+ loader: menuScreenItem.screen
1092
+ });
1093
+ }
1094
+ });
1095
+ });
1096
+ return routes;
1097
+ }
1098
+ return {
1099
+ /**
1100
+ * Check if a screen exists by screenId only (globally unique).
1101
+ */
1102
+ hasScreenById(screenId) {
1103
+ const allRoutes = buildRoutes();
1104
+ return allRoutes.some((route) => route.screenId === screenId);
1105
+ },
1106
+ /**
1107
+ * Check if a screen exists by both screensetId and screenId (explicit lookup when screenset context is known).
1108
+ */
1109
+ hasScreen(screensetId, screenId) {
1110
+ const allRoutes = buildRoutes();
1111
+ return allRoutes.some(
1112
+ (route) => route.screensetId === screensetId && route.screenId === screenId
1113
+ );
1114
+ },
1115
+ /**
1116
+ * Get screenset ID for a given screen ID (reverse lookup).
1117
+ * Screen IDs are globally unique across all screensets.
1118
+ */
1119
+ getScreensetForScreen(screenId) {
1120
+ const allRoutes = buildRoutes();
1121
+ const route = allRoutes.find((r) => r.screenId === screenId);
1122
+ return route?.screensetId;
1123
+ },
1124
+ /**
1125
+ * Get screen loader by screenId only.
1126
+ */
1127
+ getScreenById(screenId) {
1128
+ const allRoutes = buildRoutes();
1129
+ const route = allRoutes.find((r) => r.screenId === screenId);
1130
+ return route?.loader;
1131
+ },
1132
+ /**
1133
+ * Get screen loader by both screensetId and screenId (explicit lookup when screenset context is known).
1134
+ */
1135
+ getScreen(screensetId, screenId) {
1136
+ const allRoutes = buildRoutes();
1137
+ const route = allRoutes.find(
1138
+ (r) => r.screensetId === screensetId && r.screenId === screenId
1139
+ );
1140
+ return route?.loader;
1141
+ },
1142
+ /**
1143
+ * Get all routes.
1144
+ */
1145
+ getAll() {
1146
+ const allRoutes = buildRoutes();
1147
+ return allRoutes.map(({ screensetId, screenId }) => ({
1148
+ screensetId,
1149
+ screenId
1150
+ }));
1151
+ }
1152
+ };
1153
+ }
1154
+
1098
1155
  // src/plugins/routing.ts
1099
1156
  function routing() {
1100
1157
  return {
@@ -1103,8 +1160,8 @@ function routing() {
1103
1160
  onRegister(_app) {
1104
1161
  },
1105
1162
  onInit(app) {
1106
- const routeRegistry2 = createRouteRegistry(app.screensetRegistry);
1107
- app.routeRegistry = routeRegistry2;
1163
+ const routeRegistry = createRouteRegistry(app.screensetRegistry);
1164
+ app.routeRegistry = routeRegistry;
1108
1165
  }
1109
1166
  };
1110
1167
  }
@@ -1152,16 +1209,101 @@ function effects() {
1152
1209
  };
1153
1210
  }
1154
1211
 
1212
+ // src/effects/mockEffects.ts
1213
+ var import_state15 = require("@hai3/state");
1214
+ var import_api2 = require("@hai3/api");
1215
+ function hasPluginManagement(protocol) {
1216
+ return "plugins" in protocol && typeof protocol.plugins === "object";
1217
+ }
1218
+ var MockEvents = {
1219
+ Toggle: "mock/toggle"
1220
+ };
1221
+ function syncMockPlugins(enabled) {
1222
+ for (const service of import_api2.apiRegistry.getAll()) {
1223
+ const registeredPlugins = service.getPlugins();
1224
+ for (const [protocol, plugins] of registeredPlugins) {
1225
+ if (!hasPluginManagement(protocol)) continue;
1226
+ for (const plugin of plugins) {
1227
+ if ((0, import_api2.isMockPlugin)(plugin)) {
1228
+ if (enabled) {
1229
+ const existingPlugins = protocol.plugins.getAll();
1230
+ if (!existingPlugins.includes(plugin)) {
1231
+ protocol.plugins.add(plugin);
1232
+ }
1233
+ } else {
1234
+ protocol.plugins.remove(plugin);
1235
+ }
1236
+ }
1237
+ }
1238
+ }
1239
+ }
1240
+ }
1241
+ function initMockEffects() {
1242
+ const store = (0, import_state15.getStore)();
1243
+ const unsubscribe = import_state15.eventBus.on(MockEvents.Toggle, (payload) => {
1244
+ store.dispatch(setMockEnabled(payload.enabled));
1245
+ syncMockPlugins(payload.enabled);
1246
+ });
1247
+ const currentState = store.getState();
1248
+ if ("mock" in currentState && currentState.mock && typeof currentState.mock === "object" && "enabled" in currentState.mock) {
1249
+ syncMockPlugins(currentState.mock.enabled);
1250
+ }
1251
+ return () => {
1252
+ unsubscribe.unsubscribe();
1253
+ };
1254
+ }
1255
+ function toggleMockMode(enabled) {
1256
+ import_state15.eventBus.emit(MockEvents.Toggle, { enabled });
1257
+ }
1258
+
1259
+ // src/plugins/mock.ts
1260
+ var cleanup = null;
1261
+ function isDevEnvironment() {
1262
+ if (typeof window === "undefined") return false;
1263
+ const { hostname } = window.location;
1264
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname.endsWith(".local");
1265
+ }
1266
+ function mock(config) {
1267
+ return {
1268
+ name: "mock",
1269
+ dependencies: ["effects"],
1270
+ provides: {
1271
+ slices: [mockSlice],
1272
+ actions: {
1273
+ toggleMockMode
1274
+ }
1275
+ },
1276
+ onInit() {
1277
+ cleanup = initMockEffects();
1278
+ const isDev = isDevEnvironment();
1279
+ const enabledByDefault = config?.enabledByDefault ?? isDev;
1280
+ if (enabledByDefault) {
1281
+ toggleMockMode(true);
1282
+ if (isDev) {
1283
+ console.log("[HAI3] Mock mode enabled by default (dev environment detected)");
1284
+ }
1285
+ }
1286
+ },
1287
+ onDestroy() {
1288
+ if (cleanup) {
1289
+ cleanup();
1290
+ cleanup = null;
1291
+ }
1292
+ }
1293
+ };
1294
+ }
1295
+
1155
1296
  // src/presets/index.ts
1156
- function full() {
1297
+ function full(config) {
1157
1298
  return [
1158
1299
  effects(),
1159
1300
  screensets({ autoDiscover: true }),
1160
- themes(),
1301
+ themes(config?.themes),
1161
1302
  layout(),
1162
1303
  routing(),
1163
1304
  navigation(),
1164
- i18n()
1305
+ i18n(),
1306
+ mock()
1165
1307
  ];
1166
1308
  }
1167
1309
  function minimal() {
@@ -1183,28 +1325,28 @@ var presets = {
1183
1325
 
1184
1326
  // src/createHAI3App.ts
1185
1327
  function createHAI3App(config) {
1186
- return createHAI3(config).useAll(full()).build();
1328
+ return createHAI3(config).useAll(full({ themes: config?.themes })).build();
1187
1329
  }
1188
1330
 
1189
1331
  // src/registries/index.ts
1190
- var import_screensets6 = require("@hai3/screensets");
1332
+ var import_screensets4 = require("@hai3/screensets");
1191
1333
 
1192
1334
  // src/index.ts
1193
- var import_state16 = require("@hai3/state");
1194
- var import_screensets7 = require("@hai3/screensets");
1335
+ var import_state17 = require("@hai3/state");
1336
+ var import_screensets6 = require("@hai3/screensets");
1195
1337
 
1196
1338
  // src/effects/tenantEffects.ts
1197
- var import_state15 = require("@hai3/state");
1339
+ var import_state16 = require("@hai3/state");
1198
1340
  var TenantEvents = {
1199
1341
  Changed: "app/tenant/changed",
1200
1342
  Cleared: "app/tenant/cleared"
1201
1343
  };
1202
1344
  function initTenantEffects() {
1203
- const store = (0, import_state15.getStore)();
1204
- const subChanged = import_state15.eventBus.on(TenantEvents.Changed, (payload) => {
1345
+ const store = (0, import_state16.getStore)();
1346
+ const subChanged = import_state16.eventBus.on(TenantEvents.Changed, (payload) => {
1205
1347
  store.dispatch(setTenant(payload.tenant));
1206
1348
  });
1207
- const subCleared = import_state15.eventBus.on(TenantEvents.Cleared, () => {
1349
+ const subCleared = import_state16.eventBus.on(TenantEvents.Cleared, () => {
1208
1350
  store.dispatch(clearTenant());
1209
1351
  });
1210
1352
  return () => {
@@ -1213,13 +1355,13 @@ function initTenantEffects() {
1213
1355
  };
1214
1356
  }
1215
1357
  function changeTenant(tenant) {
1216
- import_state15.eventBus.emit(TenantEvents.Changed, { tenant });
1358
+ import_state16.eventBus.emit(TenantEvents.Changed, { tenant });
1217
1359
  }
1218
1360
  function clearTenantAction() {
1219
- import_state15.eventBus.emit(TenantEvents.Cleared, {});
1361
+ import_state16.eventBus.emit(TenantEvents.Cleared, {});
1220
1362
  }
1221
1363
  function setTenantLoadingState(loading) {
1222
- (0, import_state15.getStore)().dispatch(setTenantLoading(loading));
1364
+ (0, import_state16.getStore)().dispatch(setTenantLoading(loading));
1223
1365
  }
1224
1366
 
1225
1367
  // src/index.ts
@@ -1227,6 +1369,10 @@ var import_api3 = require("@hai3/api");
1227
1369
  var import_i18n5 = require("@hai3/i18n");
1228
1370
  var import_i18n6 = require("@hai3/i18n");
1229
1371
 
1372
+ // src/compat.ts
1373
+ var import_screensets5 = require("@hai3/screensets");
1374
+ var ACCOUNTS_DOMAIN = "accounts";
1375
+
1230
1376
  // src/migration.ts
1231
1377
  var STATE_PATH_MAPPING = {
1232
1378
  // App state (moved to app slice)
@@ -1280,10 +1426,12 @@ function hasLegacyUicoreState(state) {
1280
1426
  function hasNewLayoutState(state) {
1281
1427
  return typeof state === "object" && state !== null && "layout" in state && typeof state.layout === "object";
1282
1428
  }
1283
- var legacySelectors = {};
1284
1429
  // Annotate the CommonJS export names for ESM import in node:
1285
1430
  0 && (module.exports = {
1286
1431
  ACCOUNTS_DOMAIN,
1432
+ ApiPlugin,
1433
+ ApiPluginBase,
1434
+ ApiProtocol,
1287
1435
  BaseApiService,
1288
1436
  I18nRegistry,
1289
1437
  I18nRegistryImpl,
@@ -1291,11 +1439,19 @@ var legacySelectors = {};
1291
1439
  Language,
1292
1440
  LanguageDisplayMode,
1293
1441
  LayoutDomain,
1294
- MockPlugin,
1442
+ MOCK_PLUGIN,
1443
+ MockEventSource,
1444
+ MockEvents,
1445
+ RestMockPlugin,
1446
+ RestPlugin,
1447
+ RestPluginWithConfig,
1295
1448
  RestProtocol,
1296
1449
  STATE_PATH_MAPPING,
1297
1450
  SUPPORTED_LANGUAGES,
1298
1451
  ScreensetCategory,
1452
+ SseMockPlugin,
1453
+ SsePlugin,
1454
+ SsePluginWithConfig,
1299
1455
  SseProtocol,
1300
1456
  TENANT_SLICE_NAME,
1301
1457
  TenantEvents,
@@ -1320,7 +1476,6 @@ var legacySelectors = {};
1320
1476
  createThemeRegistry,
1321
1477
  effects,
1322
1478
  eventBus,
1323
- fetchCurrentUser,
1324
1479
  footerActions,
1325
1480
  footerSlice,
1326
1481
  full,
@@ -1336,17 +1491,23 @@ var legacySelectors = {};
1336
1491
  hideOverlay,
1337
1492
  i18n,
1338
1493
  i18nRegistry,
1494
+ initMockEffects,
1339
1495
  initTenantEffects,
1340
1496
  isDeprecationWarningsEnabled,
1497
+ isMockPlugin,
1498
+ isRestShortCircuit,
1499
+ isShortCircuit,
1500
+ isSseShortCircuit,
1341
1501
  layout,
1342
1502
  layoutDomainReducers,
1343
1503
  layoutReducer,
1344
- legacySelectors,
1345
1504
  menuActions,
1346
1505
  menuSlice,
1347
1506
  minimal,
1507
+ mock,
1508
+ mockActions,
1509
+ mockSlice,
1348
1510
  navigateTo,
1349
- navigateToScreen,
1350
1511
  navigation,
1351
1512
  openPopup,
1352
1513
  overlayActions,
@@ -1355,7 +1516,6 @@ var legacySelectors = {};
1355
1516
  popupSlice,
1356
1517
  presets,
1357
1518
  registerSlice,
1358
- routeRegistry,
1359
1519
  routing,
1360
1520
  screenActions,
1361
1521
  screenSlice,
@@ -1370,6 +1530,7 @@ var legacySelectors = {};
1370
1530
  setMenuConfig,
1371
1531
  setMenuItems,
1372
1532
  setMenuVisible,
1533
+ setMockEnabled,
1373
1534
  setOverlayVisible,
1374
1535
  setScreenLoading,
1375
1536
  setSidebarCollapsed,
@@ -1389,9 +1550,9 @@ var legacySelectors = {};
1389
1550
  tenantActions,
1390
1551
  tenantReducer,
1391
1552
  tenantSlice,
1392
- themeRegistry,
1393
1553
  themes,
1394
1554
  toggleMenu,
1555
+ toggleMockMode,
1395
1556
  toggleSidebar
1396
1557
  });
1397
1558
  //# sourceMappingURL=index.cjs.map