@sigmela/router 0.1.3 → 0.2.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 (52) hide show
  1. package/README.md +177 -833
  2. package/lib/module/Navigation.js +1 -10
  3. package/lib/module/NavigationStack.js +168 -19
  4. package/lib/module/Router.js +1523 -501
  5. package/lib/module/RouterContext.js +1 -1
  6. package/lib/module/ScreenStack/ScreenStack.web.js +388 -117
  7. package/lib/module/ScreenStack/ScreenStackContext.js +21 -0
  8. package/lib/module/ScreenStack/animationHelpers.js +72 -0
  9. package/lib/module/ScreenStackItem/ScreenStackItem.js +2 -1
  10. package/lib/module/ScreenStackItem/ScreenStackItem.web.js +76 -16
  11. package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.native.js +2 -1
  12. package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.web.js +1 -1
  13. package/lib/module/SplitView/RenderSplitView.native.js +85 -0
  14. package/lib/module/SplitView/RenderSplitView.web.js +109 -0
  15. package/lib/module/SplitView/SplitView.js +89 -0
  16. package/lib/module/SplitView/SplitViewContext.js +4 -0
  17. package/lib/module/SplitView/index.js +5 -0
  18. package/lib/module/SplitView/useSplitView.js +11 -0
  19. package/lib/module/StackRenderer.js +4 -2
  20. package/lib/module/TabBar/RenderTabBar.native.js +118 -33
  21. package/lib/module/TabBar/RenderTabBar.web.js +52 -47
  22. package/lib/module/TabBar/TabBar.js +116 -3
  23. package/lib/module/TabBar/index.js +4 -1
  24. package/lib/module/TabBar/useTabBarHeight.js +22 -0
  25. package/lib/module/index.js +3 -4
  26. package/lib/module/navigationNode.js +3 -0
  27. package/lib/module/styles.css +693 -28
  28. package/lib/typescript/src/NavigationStack.d.ts +25 -13
  29. package/lib/typescript/src/Router.d.ts +147 -34
  30. package/lib/typescript/src/RouterContext.d.ts +1 -1
  31. package/lib/typescript/src/ScreenStack/ScreenStack.web.d.ts +0 -2
  32. package/lib/typescript/src/ScreenStack/ScreenStackContext.d.ts +31 -0
  33. package/lib/typescript/src/ScreenStack/animationHelpers.d.ts +6 -0
  34. package/lib/typescript/src/ScreenStackItem/ScreenStackItem.types.d.ts +5 -1
  35. package/lib/typescript/src/ScreenStackItem/ScreenStackItem.web.d.ts +1 -1
  36. package/lib/typescript/src/SplitView/RenderSplitView.native.d.ts +8 -0
  37. package/lib/typescript/src/SplitView/RenderSplitView.web.d.ts +8 -0
  38. package/lib/typescript/src/SplitView/SplitView.d.ts +31 -0
  39. package/lib/typescript/src/SplitView/SplitViewContext.d.ts +3 -0
  40. package/lib/typescript/src/SplitView/index.d.ts +5 -0
  41. package/lib/typescript/src/SplitView/useSplitView.d.ts +2 -0
  42. package/lib/typescript/src/StackRenderer.d.ts +2 -1
  43. package/lib/typescript/src/TabBar/TabBar.d.ts +27 -3
  44. package/lib/typescript/src/TabBar/index.d.ts +3 -0
  45. package/lib/typescript/src/TabBar/useTabBarHeight.d.ts +18 -0
  46. package/lib/typescript/src/createController.d.ts +1 -0
  47. package/lib/typescript/src/index.d.ts +4 -3
  48. package/lib/typescript/src/navigationNode.d.ts +41 -0
  49. package/lib/typescript/src/types.d.ts +29 -32
  50. package/package.json +6 -5
  51. package/lib/module/web/TransitionStack.js +0 -227
  52. package/lib/typescript/src/web/TransitionStack.d.ts +0 -21
@@ -6,7 +6,6 @@ export type NavigationState<Route extends TabItem> = {
6
6
  index: number;
7
7
  routes: Route[];
8
8
  };
9
- export type Scope = 'global' | 'tab' | 'root';
10
9
  export type TabBarIcon = {
11
10
  sfSymbolName?: string;
12
11
  } | string;
@@ -14,70 +13,68 @@ export type ScreenOptions = Partial<Omit<RNSScreenProps, 'stackPresentation'>> &
14
13
  header?: ScreenStackHeaderConfigProps;
15
14
  stackPresentation?: StackPresentationTypes;
16
15
  convertModalToSheetForAndroid?: boolean;
16
+ syncWithUrl?: boolean;
17
+ tabBarIcon?: TabBarIcon;
18
+ animated?: boolean;
17
19
  /**
18
- * Tab bar icon source for this route (used on web renderer, optional on native).
20
+ * Allows pushing multiple instances of the same screen (same routeId) onto a stack.
21
+ *
22
+ * By default, Router.navigate() will behave like "replace" when targeting the currently
23
+ * active routeId in the same stack (i.e. treat it as "same screen, new data").
24
+ * Set this to true to force "push" even when navigating to the active routeId.
19
25
  */
20
- tabBarIcon?: TabBarIcon;
26
+ allowMultipleInstances?: boolean;
27
+ /**
28
+ * Allows Router.goBack() to pop the last (root) screen of a stack.
29
+ * Useful for secondary stacks in split-view / overlays.
30
+ */
31
+ allowRootPop?: boolean;
21
32
  };
22
33
  export type HistoryItem = {
23
34
  key: string;
24
- scope: Scope;
25
35
  routeId: string;
26
36
  component: React.ComponentType<any>;
27
37
  options?: ScreenOptions;
28
38
  params?: Record<string, unknown>;
29
39
  query?: Record<string, unknown>;
30
40
  passProps?: any;
31
- tabIndex?: number;
32
41
  stackId?: string;
33
42
  pattern?: string;
34
43
  path?: string;
35
44
  };
36
- export type VisibleRoute = {
45
+ export type ActiveRoute = {
37
46
  routeId: string;
38
47
  stackId?: string;
39
48
  tabIndex?: number;
40
- scope: Scope;
41
49
  path?: string;
42
50
  params?: Record<string, unknown>;
43
51
  query?: Record<string, unknown>;
44
52
  } | null;
53
+ export type QueryToken = {
54
+ type: 'const';
55
+ value: string;
56
+ } | {
57
+ type: 'param';
58
+ name: string;
59
+ };
60
+ export type QueryPattern = Record<string, QueryToken>;
45
61
  export type CompiledRoute = {
46
62
  routeId: string;
47
- scope: Scope;
48
63
  path: string;
49
- match: (path: string) => false | {
64
+ pathnamePattern: string;
65
+ isWildcardPath: boolean;
66
+ queryPattern: QueryPattern | null;
67
+ baseSpecificity: number;
68
+ matchPath: (path: string) => false | {
50
69
  params: Record<string, any>;
51
70
  };
52
71
  component: React.ComponentType<any>;
53
72
  controller?: import('./createController').Controller<any, any>;
54
73
  options?: ScreenOptions;
55
- tabIndex?: number;
56
74
  stackId?: string;
75
+ childNode?: import('./navigationNode').NavigationNode;
57
76
  };
58
77
  export type TabBarConfig = {
59
- /**
60
- * @summary Specifies the minimize behavior for the tab bar.
61
- *
62
- * Available starting from iOS 26.
63
- *
64
- * The following values are currently supported:
65
- *
66
- * - `automatic` - resolves to the system default minimize behavior
67
- * - `never` - the tab bar does not minimize
68
- * - `onScrollDown` - the tab bar minimizes when scrolling down and
69
- * expands when scrolling back up
70
- * - `onScrollUp` - the tab bar minimizes when scrolling up and expands
71
- * when scrolling back down
72
- *
73
- * The supported values correspond to the official UIKit documentation:
74
- * @see {@link https://developer.apple.com/documentation/uikit/uitabbarcontroller/minimizebehavior|UITabBarController.MinimizeBehavior}
75
- *
76
- * @default Defaults to `automatic`.
77
- *
78
- * @platform ios
79
- * @supported iOS 26 or higher
80
- */
81
78
  tabBarMinimizeBehavior?: TabBarMinimizeBehavior;
82
79
  };
83
80
  export type SheetAppearance = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigmela/router",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "React Native Router",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -88,7 +88,7 @@
88
88
  "react": "19.1.0",
89
89
  "react-native": "0.81.4",
90
90
  "react-native-builder-bob": "^0.40.13",
91
- "react-native-screens": "^4.16.0",
91
+ "react-native-screens": "^4.18.0",
92
92
  "release-it": "^19.0.4",
93
93
  "typescript": "^5.9.2"
94
94
  },
@@ -96,7 +96,7 @@
96
96
  "@sigmela/native-sheet": ">=0.0.1",
97
97
  "react": "*",
98
98
  "react-native": "*",
99
- "react-native-screens": ">=4.16.0"
99
+ "react-native-screens": ">=4.18.0"
100
100
  },
101
101
  "workspaces": [
102
102
  "example"
@@ -109,7 +109,7 @@
109
109
  "<rootDir>/lib/"
110
110
  ],
111
111
  "transformIgnorePatterns": [
112
- "node_modules/(?!((jest-)?react-native|@react-native|react-native-screens|nanoid|query-string|decode-uri-component|split-on-first|filter-obj)/)"
112
+ "node_modules/(?!((jest-)?react-native|@react-native|react-native-screens|@sigmela/native-sheet|nanoid|query-string|decode-uri-component|split-on-first|filter-obj)/)"
113
113
  ]
114
114
  },
115
115
  "commitlint": {
@@ -170,6 +170,7 @@
170
170
  "dependencies": {
171
171
  "nanoid": "^5.1.6",
172
172
  "path-to-regexp": "^8.3.0",
173
- "query-string": "^9.3.1"
173
+ "query-string": "^9.3.1",
174
+ "react-transition-state": "^2.3.1"
174
175
  }
175
176
  }
@@ -1,227 +0,0 @@
1
- "use strict";
2
-
3
- function whichChild(elem, countNonElements) {
4
- if (!elem?.parentNode) {
5
- return -1;
6
- }
7
- if (countNonElements) {
8
- return Array.from(elem.parentNode.childNodes).indexOf(elem);
9
- }
10
- let i = 0;
11
- let current = elem;
12
- while ((current = current.previousElementSibling) !== null) ++i;
13
- return i;
14
- }
15
- function makeTranslate(x, y) {
16
- return `translate3d(${x}px, ${y}px, 0)`;
17
- }
18
- function makeTransitionFunction(options) {
19
- return options;
20
- }
21
- const slideModal = makeTransitionFunction({
22
- callback: (tabContent, prevTabContent, toRight) => {
23
- const height = prevTabContent.getBoundingClientRect().height;
24
- const elements = [tabContent, prevTabContent];
25
- if (toRight) elements.reverse();
26
- elements[0].style.filter = `brightness(80%)`;
27
- elements[1].style.transform = makeTranslate(0, height);
28
- tabContent.classList.add('active');
29
- // eslint-disable-next-line no-void
30
- void tabContent.offsetHeight; // reflow
31
-
32
- tabContent.style.transform = '';
33
- tabContent.style.filter = '';
34
- return () => {
35
- prevTabContent.style.transform = prevTabContent.style.filter = '';
36
- };
37
- },
38
- animateFirst: false
39
- });
40
- const slideNavigation = makeTransitionFunction({
41
- callback: (tabContent, prevTabContent, toRight) => {
42
- const width = prevTabContent.getBoundingClientRect().width;
43
- const elements = [tabContent, prevTabContent];
44
- if (toRight) elements.reverse();
45
- elements[0].style.filter = `brightness(80%)`;
46
- elements[0].style.transform = makeTranslate(-width * 0.25, 0);
47
- elements[1].style.transform = makeTranslate(width, 0);
48
- tabContent.classList.add('active');
49
- // eslint-disable-next-line no-void
50
- void tabContent.offsetWidth; // reflow
51
-
52
- tabContent.style.transform = '';
53
- tabContent.style.filter = '';
54
- return () => {
55
- prevTabContent.style.transform = prevTabContent.style.filter = '';
56
- };
57
- },
58
- animateFirst: false
59
- });
60
- const transitions = {
61
- navigation: slideNavigation,
62
- modal: slideModal
63
- };
64
- const TransitionStack = options => {
65
- let {
66
- animateFirst = false
67
- } = options;
68
- const {
69
- type: t,
70
- content,
71
- withAnimationListener = true,
72
- transitionTime,
73
- onTransitionEnd,
74
- onTransitionStart,
75
- onTransitionStartAfter,
76
- once = false
77
- } = options;
78
- const type = t;
79
-
80
- // Set initial animation type on container; will be overridden per transition based on target's data-presentation
81
- content.dataset.animation = type;
82
- const onTransitionEndCallbacks = new Map();
83
- let from = null;
84
- if (withAnimationListener) {
85
- const listenerName = 'transitionend';
86
- const onEndEvent = e => {
87
- // @ts-expect-error - e is TransitionEvent | AnimationEvent
88
- e = e.originalEvent || e;
89
- try {
90
- if (e.stopPropagation) e.stopPropagation();
91
- if (e.preventDefault) e.preventDefault();
92
- e.returnValue = false;
93
- e.cancelBubble = true;
94
- } catch {
95
- /* empty */
96
- }
97
- if (e.target.parentElement !== content) {
98
- return;
99
- }
100
- const callback = onTransitionEndCallbacks.get(e.target);
101
- callback?.();
102
- if (e.target !== from) {
103
- return;
104
- }
105
- onTransitionEnd?.(transition.prevId());
106
- content.classList.remove('animating', 'backwards', 'disable-hover');
107
- if (once) {
108
- content.removeEventListener(listenerName, onEndEvent);
109
- from = undefined;
110
- onTransitionEndCallbacks.clear();
111
- }
112
- };
113
- content.addEventListener(listenerName, onEndEvent);
114
- }
115
- function transition(id, animate = true, overrideFrom) {
116
- if (overrideFrom) {
117
- from = overrideFrom;
118
- }
119
- const targetIndex = id instanceof HTMLElement ? whichChild(id) : id;
120
- const prevId = transition.prevId();
121
- if (targetIndex === prevId) return;
122
- onTransitionStart?.(targetIndex);
123
- const to = content.children[targetIndex];
124
-
125
- // Determine direction and pick animation source
126
- const goingBack = prevId > targetIndex;
127
- const sourcePresentation = from?.dataset.presentation || '';
128
- const targetPresentation = to?.dataset.presentation || '';
129
- const chosenPresentation = goingBack ? sourcePresentation : targetPresentation;
130
- const effectiveType = chosenPresentation === 'modal' ? 'modal' : 'navigation';
131
-
132
- // Update container dataset to drive CSS timing variants
133
- content.dataset.animation = effectiveType;
134
- const transitionSpec = transitions[effectiveType];
135
- const animationFunction = transitionSpec?.callback;
136
- const animateFirstLocal = (transitionSpec?.animateFirst !== undefined ? transitionSpec?.animateFirst : animateFirst) ?? false;
137
- if (prevId === -1 && !animateFirstLocal) {
138
- animate = false;
139
- }
140
- if (!withAnimationListener) {
141
- const timeout = content.dataset.timeout;
142
- if (timeout !== undefined) {
143
- clearTimeout(+timeout);
144
- }
145
- delete content.dataset.timeout;
146
- }
147
- if (!animate) {
148
- if (from) from.classList.remove('active', 'to', 'from');else if (to) {
149
- const callback = onTransitionEndCallbacks.get(to);
150
- callback?.();
151
- }
152
- if (to) {
153
- to.classList.remove('to', 'from');
154
- to.classList.add('active');
155
- }
156
- content.classList.remove('animating', 'backwards', 'disable-hover');
157
- from = to;
158
- onTransitionEnd?.(targetIndex);
159
- return;
160
- }
161
- if (!withAnimationListener) {
162
- content.dataset.timeout = '' + window.setTimeout(() => {
163
- to?.classList.remove('to');
164
- from?.classList.remove('from');
165
- content.classList.remove('animating', 'backwards', 'disable-hover');
166
- delete content.dataset.timeout;
167
- }, transitionTime);
168
- }
169
- if (from) {
170
- from.classList.remove('to');
171
- from.classList.add('from');
172
- }
173
- content.classList.add('animating');
174
- const toRight = prevId < targetIndex;
175
- content.classList.toggle('backwards', !toRight);
176
- let onTransitionEndCallback;
177
- if (!to) {
178
- // prevTabContent.classList.remove('active');
179
- } else {
180
- if (animationFunction) {
181
- onTransitionEndCallback = animationFunction(to, from, toRight);
182
- } else {
183
- to.classList.add('active');
184
- }
185
- onTransitionStartAfter?.(targetIndex);
186
- to.classList.remove('from');
187
- to.classList.add('to');
188
- }
189
- if (to) {
190
- const transitionTimeout = to.dataset.transitionTimeout;
191
- if (transitionTimeout) {
192
- clearTimeout(+transitionTimeout);
193
- }
194
- onTransitionEndCallbacks.set(to, () => {
195
- to.classList.remove('to');
196
- onTransitionEndCallbacks.delete(to);
197
- });
198
- }
199
- if (from) {
200
- let timeout;
201
- const _from = from;
202
- const callback = () => {
203
- clearTimeout(timeout);
204
- _from.classList.remove('active', 'from');
205
- onTransitionEndCallback?.();
206
- onTransitionEndCallbacks.delete(_from);
207
- };
208
- if (to) {
209
- timeout = window.setTimeout(callback, transitionTime + 100);
210
- onTransitionEndCallbacks.set(_from, callback);
211
- } else {
212
- timeout = window.setTimeout(callback, transitionTime + 100);
213
- onTransitionEndCallbacks.set(_from, () => {
214
- clearTimeout(timeout);
215
- onTransitionEndCallbacks.delete(_from);
216
- });
217
- }
218
- _from.dataset.transitionTimeout = '' + timeout;
219
- }
220
- from = to;
221
- }
222
- transition.prevId = () => from ? whichChild(from) : -1;
223
- transition.getFrom = () => from;
224
- transition.setFrom = _from => from = _from;
225
- return transition;
226
- };
227
- export default TransitionStack;
@@ -1,21 +0,0 @@
1
- export type TransitionStackType = 'modal' | 'navigation';
2
- export type TransitionStackOptions = {
3
- content: HTMLElement;
4
- type: TransitionStackType;
5
- transitionTime: number;
6
- onTransitionStart?: (id: number) => void;
7
- onTransitionStartAfter?: (id: number) => void;
8
- onTransitionEnd?: (id: number) => void;
9
- isHeavy?: boolean;
10
- once?: boolean;
11
- withAnimationListener?: boolean;
12
- animateFirst?: boolean;
13
- };
14
- declare const TransitionStack: (options: TransitionStackOptions) => {
15
- (id: number | HTMLElement, animate?: boolean, overrideFrom?: HTMLElement | null | undefined): void;
16
- prevId(): number;
17
- getFrom(): HTMLElement | null | undefined;
18
- setFrom(_from: HTMLElement): HTMLElement;
19
- };
20
- export default TransitionStack;
21
- //# sourceMappingURL=TransitionStack.d.ts.map