@sigmela/router 0.2.8 → 0.3.1

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.
Files changed (39) hide show
  1. package/README.md +107 -1
  2. package/lib/module/Drawer/Drawer.js +250 -0
  3. package/lib/module/Drawer/DrawerContext.js +4 -0
  4. package/lib/module/Drawer/DrawerIcon.web.js +47 -0
  5. package/lib/module/Drawer/RenderDrawer.native.js +244 -0
  6. package/lib/module/Drawer/RenderDrawer.web.js +197 -0
  7. package/lib/module/Drawer/useDrawer.js +11 -0
  8. package/lib/module/Navigation.js +4 -2
  9. package/lib/module/NavigationStack.js +14 -4
  10. package/lib/module/Router.js +214 -60
  11. package/lib/module/RouterContext.js +1 -1
  12. package/lib/module/ScreenStack/ScreenStack.web.js +78 -12
  13. package/lib/module/ScreenStackItem/ScreenStackItem.js +7 -7
  14. package/lib/module/ScreenStackItem/ScreenStackItem.web.js +65 -6
  15. package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.native.js +4 -5
  16. package/lib/module/SplitView/RenderSplitView.web.js +5 -7
  17. package/lib/module/SplitView/SplitView.js +10 -2
  18. package/lib/module/StackRenderer.js +10 -4
  19. package/lib/module/TabBar/RenderTabBar.native.js +55 -24
  20. package/lib/module/TabBar/RenderTabBar.web.js +25 -2
  21. package/lib/module/TabBar/TabBar.js +8 -1
  22. package/lib/module/TabBar/TabIcon.web.js +12 -7
  23. package/lib/module/index.js +2 -0
  24. package/lib/module/styles.css +255 -91
  25. package/lib/typescript/src/Drawer/Drawer.d.ts +100 -0
  26. package/lib/typescript/src/Drawer/DrawerContext.d.ts +3 -0
  27. package/lib/typescript/src/Drawer/DrawerIcon.web.d.ts +7 -0
  28. package/lib/typescript/src/Drawer/RenderDrawer.native.d.ts +8 -0
  29. package/lib/typescript/src/Drawer/RenderDrawer.web.d.ts +8 -0
  30. package/lib/typescript/src/Drawer/useDrawer.d.ts +2 -0
  31. package/lib/typescript/src/NavigationStack.d.ts +1 -0
  32. package/lib/typescript/src/Router.d.ts +13 -0
  33. package/lib/typescript/src/ScreenStack/ScreenStackContext.d.ts +1 -1
  34. package/lib/typescript/src/ScreenStack/animationHelpers.d.ts +1 -1
  35. package/lib/typescript/src/SplitView/SplitView.d.ts +2 -0
  36. package/lib/typescript/src/TabBar/TabBar.d.ts +10 -1
  37. package/lib/typescript/src/index.d.ts +5 -0
  38. package/lib/typescript/src/types.d.ts +12 -3
  39. package/package.json +28 -12
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+
3
+ import { StackRenderer } from "../StackRenderer.js";
4
+ import { DrawerContext } from "./DrawerContext.js";
5
+ import { useRouter } from "../RouterContext.js";
6
+ import { DrawerIcon } from './DrawerIcon';
7
+ import { useCallback, useSyncExternalStore, memo, useMemo } from 'react';
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ const isImageSource = value => {
10
+ if (value == null) return false;
11
+ const valueType = typeof value;
12
+ if (valueType === 'number') return true;
13
+ if (Array.isArray(value)) return value.length > 0;
14
+ if (valueType === 'object') {
15
+ const v = value;
16
+ if ('uri' in v || 'width' in v || 'height' in v) return true;
17
+ }
18
+ return false;
19
+ };
20
+ const toColorString = c => typeof c === 'string' ? c : undefined;
21
+ const DrawerStackRenderer = /*#__PURE__*/memo(({
22
+ stack,
23
+ appearance
24
+ }) => {
25
+ const router = useRouter();
26
+ const stackId = stack.getId();
27
+ const subscribe = useCallback(cb => router.subscribeStack(stackId, cb), [router, stackId]);
28
+ const get = useCallback(() => router.getStackHistory(stackId), [router, stackId]);
29
+ const history = useSyncExternalStore(subscribe, get, get);
30
+ return /*#__PURE__*/_jsx(StackRenderer, {
31
+ appearance: appearance,
32
+ stackId: stackId,
33
+ history: history
34
+ });
35
+ });
36
+ DrawerStackRenderer.displayName = 'DrawerStackRenderer';
37
+ const DrawerNodeRenderer = /*#__PURE__*/memo(({
38
+ node,
39
+ appearance
40
+ }) => {
41
+ const Renderer = useMemo(() => node.getRenderer(), [node]);
42
+ return /*#__PURE__*/_jsx(Renderer, {
43
+ appearance: appearance
44
+ });
45
+ });
46
+ DrawerNodeRenderer.displayName = 'DrawerNodeRenderer';
47
+ const joinPrefixAndPath = (prefix, path) => {
48
+ const base = (prefix ?? '').trim();
49
+ const child = (path || '/').trim();
50
+ if (!base) return child || '/';
51
+ const baseNorm = base === '/' ? '' : base.endsWith('/') ? base.slice(0, -1) : base;
52
+ if (!child || child === '/') {
53
+ return baseNorm || '/';
54
+ }
55
+ if (child.startsWith('/')) {
56
+ return `${baseNorm}${child}`;
57
+ }
58
+ return `${baseNorm}/${child}`;
59
+ };
60
+ export const RenderDrawer = /*#__PURE__*/memo(({
61
+ drawer,
62
+ appearance
63
+ }) => {
64
+ const router = useRouter();
65
+ const subscribe = useCallback(cb => drawer.subscribe(cb), [drawer]);
66
+ const snapshot = useSyncExternalStore(subscribe, drawer.getState, drawer.getState);
67
+ const {
68
+ tabs,
69
+ index,
70
+ config,
71
+ isOpen
72
+ } = snapshot;
73
+ const focusedTab = tabs[index];
74
+ const stack = focusedTab ? drawer.stacks[focusedTab.tabKey] : undefined;
75
+ const node = focusedTab ? drawer.nodes[focusedTab.tabKey] : undefined;
76
+ const Screen = focusedTab ? drawer.screens[focusedTab.tabKey] : undefined;
77
+ const onItemClick = useCallback(nextIndex => {
78
+ const targetTab = tabs[nextIndex];
79
+ if (!targetTab) return;
80
+ const targetStack = drawer.stacks[targetTab.tabKey];
81
+ const targetNode = drawer.nodes[targetTab.tabKey];
82
+ if (targetStack) {
83
+ if (nextIndex !== index) {
84
+ drawer.onIndexChange(nextIndex);
85
+ }
86
+ const firstRoutePath = targetStack.getFirstRoute()?.path;
87
+ if (firstRoutePath) {
88
+ router.reset(firstRoutePath);
89
+ }
90
+ } else if (targetNode) {
91
+ if (nextIndex !== index) {
92
+ drawer.onIndexChange(nextIndex);
93
+ }
94
+ const seedPath = targetNode.seed?.()?.path;
95
+ const fallbackFirstPath = targetNode.getNodeRoutes()?.[0]?.path;
96
+ const path = seedPath ?? fallbackFirstPath ?? '/';
97
+ const fullPath = joinPrefixAndPath(targetTab.tabPrefix, path);
98
+ router.reset(fullPath);
99
+ } else {
100
+ if (nextIndex !== index) {
101
+ drawer.onIndexChange(nextIndex);
102
+ }
103
+ }
104
+
105
+ // Close drawer on mobile after navigation
106
+ drawer.close();
107
+ }, [router, drawer, tabs, index]);
108
+ const handleItemClick = useCallback(e => {
109
+ const idx = Number(e.currentTarget.dataset.index);
110
+ onItemClick(idx);
111
+ }, [onItemClick]);
112
+ const handleOverlayClick = useCallback(() => {
113
+ drawer.close();
114
+ }, [drawer]);
115
+ const drawerStyle = useMemo(() => {
116
+ const bg = toColorString(appearance?.tabBar?.backgroundColor);
117
+ const style = {};
118
+ if (bg) {
119
+ style['--drawer-bg'] = bg;
120
+ }
121
+ if (drawer.width !== 280) {
122
+ style['--drawer-width'] = `${drawer.width}px`;
123
+ }
124
+ return Object.keys(style).length ? style : undefined;
125
+ }, [appearance?.tabBar?.backgroundColor, drawer.width]);
126
+ const titleBaseStyle = useMemo(() => ({
127
+ fontFamily: appearance?.tabBar?.title?.fontFamily,
128
+ fontSize: appearance?.tabBar?.title?.fontSize,
129
+ fontWeight: appearance?.tabBar?.title?.fontWeight,
130
+ fontStyle: appearance?.tabBar?.title?.fontStyle
131
+ }), [appearance?.tabBar?.title?.fontFamily, appearance?.tabBar?.title?.fontSize, appearance?.tabBar?.title?.fontWeight, appearance?.tabBar?.title?.fontStyle]);
132
+ const CustomDrawer = config.component;
133
+ return /*#__PURE__*/_jsx(DrawerContext.Provider, {
134
+ value: drawer,
135
+ children: /*#__PURE__*/_jsxs("div", {
136
+ className: "drawer-container",
137
+ "data-drawer-open": isOpen ? 'true' : 'false',
138
+ style: drawerStyle,
139
+ children: [CustomDrawer ? /*#__PURE__*/_jsx(CustomDrawer, {
140
+ onItemPress: onItemClick,
141
+ activeIndex: index,
142
+ items: tabs,
143
+ isOpen: isOpen,
144
+ onClose: handleOverlayClick
145
+ }) : /*#__PURE__*/_jsx("div", {
146
+ className: "drawer-sidebar",
147
+ children: /*#__PURE__*/_jsx("div", {
148
+ className: "drawer-sidebar-content",
149
+ children: tabs.map((tab, i) => {
150
+ const isActive = i === index;
151
+ const iconTint = toColorString(isActive ? appearance?.tabBar?.iconColorActive : appearance?.tabBar?.iconColor);
152
+ const title = appearance?.tabBar?.title;
153
+ const labelColor = isActive ? toColorString(title?.activeColor) ?? toColorString(title?.color) : toColorString(title?.color);
154
+ const labelStyle = {
155
+ ...titleBaseStyle,
156
+ color: labelColor
157
+ };
158
+ return /*#__PURE__*/_jsxs("button", {
159
+ type: "button",
160
+ "data-index": i,
161
+ "data-active": isActive ? 'true' : 'false',
162
+ "aria-current": isActive ? 'page' : undefined,
163
+ className: `drawer-item${isActive ? ' active' : ''}`,
164
+ onClick: handleItemClick,
165
+ children: [/*#__PURE__*/_jsx("div", {
166
+ className: "drawer-item-icon",
167
+ children: isImageSource(tab.icon) ? /*#__PURE__*/_jsx(DrawerIcon, {
168
+ source: tab.icon,
169
+ tintColor: iconTint
170
+ }) : null
171
+ }), /*#__PURE__*/_jsx("div", {
172
+ className: "drawer-item-label",
173
+ style: labelStyle,
174
+ children: tab.title
175
+ }), tab.badgeValue ? /*#__PURE__*/_jsx("span", {
176
+ className: "drawer-item-badge",
177
+ children: tab.badgeValue
178
+ }) : null]
179
+ }, tab.tabKey);
180
+ })
181
+ })
182
+ }), /*#__PURE__*/_jsx("div", {
183
+ className: "drawer-overlay",
184
+ onClick: handleOverlayClick
185
+ }), /*#__PURE__*/_jsx("div", {
186
+ className: "drawer-main",
187
+ children: stack ? /*#__PURE__*/_jsx(DrawerStackRenderer, {
188
+ appearance: appearance,
189
+ stack: stack
190
+ }) : node ? /*#__PURE__*/_jsx(DrawerNodeRenderer, {
191
+ appearance: appearance,
192
+ node: node
193
+ }) : Screen ? /*#__PURE__*/_jsx(Screen, {}) : null
194
+ })]
195
+ })
196
+ });
197
+ });
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+
3
+ import { useContext } from 'react';
4
+ import { DrawerContext } from "./DrawerContext.js";
5
+ export const useDrawer = () => {
6
+ const drawer = useContext(DrawerContext);
7
+ if (!drawer) {
8
+ throw new Error('useDrawer must be used within a DrawerProvider');
9
+ }
10
+ return drawer;
11
+ };
@@ -4,7 +4,7 @@ import { ScreenStackItem } from "./ScreenStackItem/index.js";
4
4
  import { RouterContext } from "./RouterContext.js";
5
5
  import { ScreenStack } from "./ScreenStack/index.js";
6
6
  import { StyleSheet } from 'react-native';
7
- import { useSyncExternalStore, memo, useCallback, useEffect, useState } from 'react';
7
+ import { useSyncExternalStore, memo, useCallback, useEffect, useRef, useState } from 'react';
8
8
  import { jsx as _jsx } from "react/jsx-runtime";
9
9
  const EMPTY_HISTORY = [];
10
10
  function useStackHistory(router, stackId) {
@@ -38,7 +38,9 @@ export const Navigation = /*#__PURE__*/memo(({
38
38
  const {
39
39
  rootId
40
40
  } = root;
41
- const rootTransition = router.getRootTransition();
41
+ const rootTransitionRef = useRef(undefined);
42
+ rootTransitionRef.current = router.getRootTransition();
43
+ const rootTransition = rootTransitionRef.current;
42
44
  const rootItems = useStackHistory(router, rootId);
43
45
  return /*#__PURE__*/_jsx(RouterContext.Provider, {
44
46
  value: router,
@@ -9,17 +9,20 @@ export class NavigationStack {
9
9
  routes = [];
10
10
  children = [];
11
11
  debugEnabled = false;
12
+ _cachedRenderer = null;
12
13
  constructor(idOrOptions, maybeOptions, maybeDebug) {
13
14
  if (typeof idOrOptions === 'string') {
14
- this.stackId = idOrOptions ?? `stack-${nanoid()}`;
15
+ this.stackId = idOrOptions;
15
16
  this.defaultOptions = maybeOptions;
16
17
  this.debugEnabled = maybeDebug ?? false;
17
18
  this.provider = maybeOptions?.provider;
18
- } else {
19
+ } else if (typeof idOrOptions === 'object') {
19
20
  this.stackId = `stack-${nanoid()}`;
20
21
  this.defaultOptions = idOrOptions;
21
- this.debugEnabled = false;
22
+ this.debugEnabled = idOrOptions.debug ?? false;
22
23
  this.provider = idOrOptions?.provider;
24
+ } else {
25
+ this.stackId = `stack-${nanoid()}`;
23
26
  }
24
27
  }
25
28
  log(message, data) {
@@ -86,6 +89,7 @@ export class NavigationStack {
86
89
  }
87
90
  addModal(path, mixedComponent, options) {
88
91
  return this.addScreen(path, mixedComponent, {
92
+ syncWithUrl: false,
89
93
  ...options,
90
94
  stackPresentation: 'modal'
91
95
  });
@@ -134,11 +138,15 @@ export class NavigationStack {
134
138
  return this.children.slice();
135
139
  }
136
140
  getRenderer() {
141
+ if (this._cachedRenderer) {
142
+ return this._cachedRenderer;
143
+ }
144
+
137
145
  // eslint-disable-next-line consistent-this
138
146
  const stackInstance = this;
139
147
  const stackId = stackInstance.getId();
140
148
  const provider = this.provider;
141
- return function NavigationStackRenderer(props) {
149
+ const renderer = function NavigationStackRenderer(props) {
142
150
  const stackElement = /*#__PURE__*/React.createElement(StackRenderer, {
143
151
  stackId: stackId,
144
152
  appearance: props.appearance
@@ -148,6 +156,8 @@ export class NavigationStack {
148
156
  }
149
157
  return stackElement;
150
158
  };
159
+ this._cachedRenderer = renderer;
160
+ return renderer;
151
161
  }
152
162
  seed() {
153
163
  const first = this.getFirstRoute();