@sigmela/router 0.2.7 → 0.3.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.
Files changed (39) hide show
  1. package/README.md +41 -0
  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 +241 -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 +22 -5
  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 +10 -9
  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 +247 -106
  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 +6 -4
  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 +1 -0
  37. package/lib/typescript/src/index.d.ts +5 -0
  38. package/lib/typescript/src/types.d.ts +10 -1
  39. package/package.json +15 -4
@@ -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,15 +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
- } else {
18
+ this.provider = maybeOptions?.provider;
19
+ } else if (typeof idOrOptions === 'object') {
18
20
  this.stackId = `stack-${nanoid()}`;
19
21
  this.defaultOptions = idOrOptions;
20
- this.debugEnabled = false;
22
+ this.debugEnabled = idOrOptions.debug ?? false;
23
+ this.provider = idOrOptions?.provider;
24
+ } else {
25
+ this.stackId = `stack-${nanoid()}`;
21
26
  }
22
27
  }
23
28
  log(message, data) {
@@ -84,6 +89,7 @@ export class NavigationStack {
84
89
  }
85
90
  addModal(path, mixedComponent, options) {
86
91
  return this.addScreen(path, mixedComponent, {
92
+ syncWithUrl: false,
87
93
  ...options,
88
94
  stackPresentation: 'modal'
89
95
  });
@@ -132,15 +138,26 @@ export class NavigationStack {
132
138
  return this.children.slice();
133
139
  }
134
140
  getRenderer() {
141
+ if (this._cachedRenderer) {
142
+ return this._cachedRenderer;
143
+ }
144
+
135
145
  // eslint-disable-next-line consistent-this
136
146
  const stackInstance = this;
137
147
  const stackId = stackInstance.getId();
138
- return function NavigationStackRenderer(props) {
139
- return /*#__PURE__*/React.createElement(StackRenderer, {
148
+ const provider = this.provider;
149
+ const renderer = function NavigationStackRenderer(props) {
150
+ const stackElement = /*#__PURE__*/React.createElement(StackRenderer, {
140
151
  stackId: stackId,
141
152
  appearance: props.appearance
142
153
  });
154
+ if (provider) {
155
+ return /*#__PURE__*/React.createElement(provider, null, stackElement);
156
+ }
157
+ return stackElement;
143
158
  };
159
+ this._cachedRenderer = renderer;
160
+ return renderer;
144
161
  }
145
162
  seed() {
146
163
  const first = this.getFirstRoute();