mobx-route 2.1.1 → 2.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobx-route",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "keywords": [
5
5
  "mobx",
6
6
  "react",
package/react.cjs CHANGED
@@ -103,7 +103,6 @@ function RouteViewBase(props) {
103
103
  return props.children;
104
104
  }
105
105
  const RouteView = mobxReactLite.observer(RouteViewBase);
106
- const useOtherwiseEffect = typeof window === "undefined" ? react.useEffect : react.useLayoutEffect;
107
106
  const RouteViewGroup = mobxReactLite.observer(
108
107
  ({
109
108
  children,
@@ -143,8 +142,12 @@ const RouteViewGroup = mobxReactLite.observer(
143
142
  const hasActiveChildNode = !!activeChildRouteNode;
144
143
  const otherwiseIsString = typeof otherwiseNavigation === "string";
145
144
  const otherwiseRoute = !otherwiseIsString ? otherwiseNavigation : void 0;
146
- useOtherwiseEffect(() => {
147
- if (!hasActiveChildNode && !hasRoutesInOpening && otherwiseNavigation) {
145
+ react.useEffect(() => {
146
+ let cancelled = false;
147
+ queueMicrotask(() => {
148
+ if (cancelled || hasActiveChildNode || hasRoutesInOpening || !otherwiseNavigation) {
149
+ return;
150
+ }
148
151
  if (otherwiseIsString) {
149
152
  const history = mobxRoute.routeConfig.get().history;
150
153
  const url = `${otherwiseNavigation}${mobxRoute.buildSearchString(navigateParams.query || {})}`;
@@ -156,7 +159,10 @@ const RouteViewGroup = mobxReactLite.observer(
156
159
  } else if (otherwiseRoute && !otherwiseRoute.isOpened) {
157
160
  otherwiseRoute.open(params, navigateParams);
158
161
  }
159
- }
162
+ });
163
+ return () => {
164
+ cancelled = true;
165
+ };
160
166
  }, [
161
167
  hasActiveChildNode,
162
168
  hasRoutesInOpening,
package/react.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.cjs","sources":["../src/react/components/link.tsx","../src/react/components/route-view.tsx","../src/react/components/route-view-group.tsx"],"sourcesContent":["import { observer } from 'mobx-react-lite';\nimport {\n type AnyRoute,\n buildSearchString,\n type InputPathParams,\n parseSearchString,\n type RouteNavigateParams,\n routeConfig,\n} from 'mobx-route';\nimport {\n type AnchorHTMLAttributes,\n cloneElement,\n forwardRef,\n isValidElement,\n type MouseEvent,\n useMemo,\n useRef,\n} from 'react';\nimport { isShallowEqual } from 'yummies/data';\nimport type { IsPartial, Maybe } from 'yummies/types';\n\ninterface LinkAnchorProps\n extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n asChild?: boolean;\n}\n\ntype LinkPathRouteProps<TRoute extends AnyRoute> = {\n to: TRoute;\n} & (IsPartial<InputPathParams<TRoute['pathDeclaration']>> extends true\n ? {\n params?: InputPathParams<TRoute['pathDeclaration']> | null | undefined;\n }\n : { params: InputPathParams<TRoute['pathDeclaration']> });\n\ntype LinkSimpleRouteProps =\n | {\n to: string;\n }\n | {\n href: Maybe<string>;\n };\n\nexport type LinkProps<TRoute extends AnyRoute> = LinkAnchorProps &\n RouteNavigateParams &\n (LinkPathRouteProps<TRoute> | LinkSimpleRouteProps);\n\ntype LinkComponentType = <TRoute extends AnyRoute>(\n props: LinkProps<TRoute>,\n) => React.ReactNode;\n\nexport const Link = observer(\n forwardRef<\n HTMLAnchorElement,\n LinkAnchorProps &\n RouteNavigateParams & {\n params?: any;\n to: string | AnyRoute;\n href: string;\n }\n >((props, ref) => {\n const {\n to,\n href: outerHref,\n mergeQuery,\n asChild,\n children,\n params,\n // route navigate params\n query,\n replace,\n state,\n //\n ...outerAnchorProps\n } = props;\n\n const isExternalNavigation =\n outerAnchorProps.target === '_blank' ||\n outerAnchorProps.target === 'blank';\n const queryDataRef = useRef<RouteNavigateParams['query']>(query);\n\n if (!isShallowEqual(queryDataRef.current, query)) {\n queryDataRef.current = query;\n }\n\n const { href, navigateParams } = useMemo(() => {\n const navigateParams: RouteNavigateParams = {\n mergeQuery,\n query,\n replace,\n state,\n };\n\n const cfg = routeConfig.get();\n\n let href: string = '';\n\n if (outerHref) {\n href = outerHref;\n } else if (to) {\n if (typeof to === 'string') {\n const isNeedToMergeQuery =\n navigateParams.mergeQuery ?? cfg.mergeQuery;\n\n const [path, ...querySegments] = to.split('?');\n\n const existedQuery = parseSearchString(querySegments.join('?'));\n\n const query = {\n ...(isNeedToMergeQuery ? cfg.queryParams.data : {}),\n ...existedQuery,\n ...navigateParams.query,\n };\n\n href = `${path}${buildSearchString(query)}`;\n } else {\n href = to.createUrl(\n params,\n navigateParams.query,\n navigateParams.mergeQuery,\n );\n }\n }\n\n return {\n href: cfg.formatLinkHref?.(href) ?? href,\n navigateParams,\n };\n }, [mergeQuery, replace, state, outerHref, to, queryDataRef.current]);\n\n const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {\n if (\n isExternalNavigation ||\n event.ctrlKey ||\n event.metaKey ||\n event.altKey ||\n event.shiftKey ||\n event.button !== 0\n )\n return;\n\n outerAnchorProps.onClick?.(event);\n\n if (\n !event.defaultPrevented &&\n !href.startsWith('https://') &&\n !href.startsWith('http://')\n ) {\n event.preventDefault();\n\n if (navigateParams.replace) {\n routeConfig.get().history.replace(href, navigateParams.state);\n } else {\n routeConfig.get().history.push(href, navigateParams.state);\n }\n }\n };\n\n const anchorProps = {\n ...outerAnchorProps,\n href,\n onClick: handleClick,\n rel:\n outerAnchorProps.rel ??\n (isExternalNavigation ? 'noopener noreferrer' : undefined),\n };\n\n return asChild && isValidElement(children) ? (\n cloneElement(children, anchorProps)\n ) : (\n <a {...anchorProps} ref={ref}>\n {children}\n </a>\n );\n }),\n) as unknown as LinkComponentType;\n","import { observer } from 'mobx-react-lite';\nimport type {\n AnyAbstractRouteEntity,\n AnyRoute,\n AnyVirtualRoute,\n} from 'mobx-route';\n\nexport type RouteViewComponent<TRoute extends AnyAbstractRouteEntity> =\n React.ComponentType<RouteViewProps<TRoute>>;\n\ninterface RouteViewConfigWithoutRoute {\n children?: React.ReactNode | (() => React.ReactNode);\n}\n\nexport interface RouteViewConfigWithRoute<\n TRoute extends AnyAbstractRouteEntity,\n> {\n route: TRoute;\n view?: RouteViewComponent<TRoute>;\n /**\n * Case when route is not opened\n */\n fallback?: React.ReactNode;\n children?:\n | React.ReactNode\n | ((\n params: RouteViewProps<TRoute>['params'],\n route: TRoute,\n ) => React.ReactNode);\n}\n\nexport type RouteViewConfig<TRoute extends AnyAbstractRouteEntity> =\n | RouteViewConfigWithRoute<TRoute>\n | RouteViewConfigWithoutRoute;\n\nexport type RouteViewProps<TRoute extends AnyAbstractRouteEntity> = {\n children?: React.ReactNode;\n params: TRoute extends AnyRoute\n ? Exclude<TRoute['params'], null | undefined>\n : TRoute extends AnyVirtualRoute\n ? TRoute['params']\n : never;\n};\n\ntype RouteViewBaseComponent = <TRoute extends AnyAbstractRouteEntity>(\n props: RouteViewConfig<TRoute>,\n) => React.ReactNode;\n\nfunction RouteViewBase<TRoute extends AnyAbstractRouteEntity>(\n props: Readonly<RouteViewConfig<TRoute>>,\n): React.ReactNode {\n let Component: React.ComponentType<any> | undefined;\n\n if (!('route' in props)) {\n return typeof props.children === 'function'\n ? props.children()\n : props.children;\n }\n\n const isOpening =\n 'isOpening' in props.route &&\n Boolean((props.route as { isOpening?: boolean }).isOpening);\n\n if (!props.route.isOpened && !isOpening) {\n return props.fallback ?? null;\n }\n\n Component = props.view;\n\n const params: any = 'params' in props.route ? props.route.params : {};\n\n if (Component) {\n return <Component params={params}>{props.children}</Component>;\n }\n\n if (typeof props.children === 'function') {\n return props.children(params, props.route);\n }\n\n return props.children;\n}\n\nexport const RouteView = observer(RouteViewBase) as RouteViewBaseComponent;\n","import { observer } from 'mobx-react-lite';\nimport {\n type AnyRoute,\n type AnyRouteEntity,\n buildSearchString,\n isRouteEntity,\n type RouteNavigateParams,\n type RouteParams,\n routeConfig,\n} from 'mobx-route';\nimport { isValidElement, Suspense, useEffect, useLayoutEffect } from 'react';\nimport type { IsPartial, Maybe } from 'yummies/types';\n\ntype LayoutComponent =\n | React.ComponentType<{ children?: React.ReactNode }>\n | React.ComponentType<{ children: React.ReactNode }>;\n\ninterface BaseProps extends RouteNavigateParams {\n children: React.ReactNode;\n layout?: LayoutComponent;\n useLastOpened?: boolean;\n /** When true, wraps the active route content in `<Suspense fallback={fallback}>`. */\n suspense?: boolean;\n /** Fallback content for the Suspense boundary. Only used when `suspense` is true. */\n fallback?: React.ReactNode;\n}\n\ntype PropsWithDefaultRoute<TRoute extends AnyRouteEntity> = BaseProps & {\n otherwise?: TRoute;\n} & (IsPartial<RouteParams<TRoute>> extends true\n ? {\n params?: Maybe<RouteParams<TRoute>>;\n }\n : {\n params: RouteParams<TRoute>;\n });\n\ntype PropsWithDefaultUrl = BaseProps & {\n otherwise?: string;\n};\n\nexport type RouteViewGroupProps<TRoute extends AnyRouteEntity> =\n | PropsWithDefaultRoute<TRoute>\n | PropsWithDefaultUrl;\n\ntype RouteViewGroupComponent = <TRoute extends AnyRouteEntity>(\n props: RouteViewGroupProps<TRoute>,\n) => React.ReactNode;\n\nconst useOtherwiseEffect =\n typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\nexport const RouteViewGroup = observer(\n <TRoute extends AnyRouteEntity>({\n children,\n layout: Layout,\n otherwise: otherwiseNavigation,\n useLastOpened,\n suspense,\n fallback,\n // @ts-expect-error\n params,\n ...navigateParams\n }: RouteViewGroupProps<TRoute>) => {\n let activeChildRouteNode: React.ReactNode = null;\n let lastInactiveChildNode: React.ReactNode = null;\n let hasRoutesInOpening = false;\n\n const childNodes: React.ReactNode[] = Array.isArray(children)\n ? children\n : [children];\n\n for (const childNode of childNodes) {\n const isRouteChild =\n isValidElement(childNode) &&\n // @ts-expect-error redundant checks better to wrap in this directive\n isRouteEntity(childNode.props?.route);\n\n if (isRouteChild) {\n const route = (childNode.props as any).route as AnyRoute;\n\n // isOpening covers confirmOpening and the Back gap — break early so\n // a later-opening virtual route (e.g. notFound) doesn't steal the slot.\n if (route.isOpened || route.isOpening) {\n activeChildRouteNode = childNode;\n if (route.isOpening) {\n hasRoutesInOpening = true;\n }\n if (!useLastOpened) {\n break;\n }\n } else {\n lastInactiveChildNode = childNode;\n }\n } else {\n lastInactiveChildNode = childNode;\n }\n }\n\n const hasActiveChildNode = !!activeChildRouteNode;\n\n const otherwiseIsString = typeof otherwiseNavigation === 'string';\n const otherwiseRoute = !otherwiseIsString ? otherwiseNavigation : undefined;\n\n useOtherwiseEffect(() => {\n if (!hasActiveChildNode && !hasRoutesInOpening && otherwiseNavigation) {\n if (otherwiseIsString) {\n const history = routeConfig.get().history;\n const url = `${otherwiseNavigation}${buildSearchString(navigateParams.query || {})}`;\n\n if (navigateParams.replace) {\n history.replace(url, navigateParams.state);\n } else {\n history.push(url, navigateParams.state);\n }\n } else if (otherwiseRoute && !otherwiseRoute.isOpened) {\n otherwiseRoute.open(params, navigateParams);\n }\n }\n }, [\n hasActiveChildNode,\n hasRoutesInOpening,\n otherwiseIsString,\n otherwiseRoute,\n ]);\n\n // For string otherwise: return null — effect will navigate and route opens next tick.\n // For route otherwise: don't return null — useLayoutEffect opens it synchronously,\n // returning null would unmount Layout and break withViewModel auto-id VMs.\n const shouldRenderNullOtherwise =\n otherwiseNavigation && !activeChildRouteNode && otherwiseIsString;\n\n if (shouldRenderNullOtherwise) {\n return null;\n }\n\n const resultNodeToRender =\n activeChildRouteNode ?? lastInactiveChildNode ?? null;\n\n const content = suspense ? (\n <Suspense fallback={fallback ?? null}>{resultNodeToRender}</Suspense>\n ) : (\n resultNodeToRender\n );\n\n if (Layout) {\n return <Layout>{content}</Layout>;\n }\n\n return content;\n },\n) as unknown as RouteViewGroupComponent;\n"],"names":["observer","forwardRef","useRef","isShallowEqual","useMemo","navigateParams","routeConfig","href","parseSearchString","query","buildSearchString","isValidElement","cloneElement","jsx","useEffect","useLayoutEffect","isRouteEntity","Suspense"],"mappings":"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"}
1
+ {"version":3,"file":"react.cjs","sources":["../src/react/components/link.tsx","../src/react/components/route-view.tsx","../src/react/components/route-view-group.tsx"],"sourcesContent":["import { observer } from 'mobx-react-lite';\nimport {\n type AnyRoute,\n buildSearchString,\n type InputPathParams,\n parseSearchString,\n type RouteNavigateParams,\n routeConfig,\n} from 'mobx-route';\nimport {\n type AnchorHTMLAttributes,\n cloneElement,\n forwardRef,\n isValidElement,\n type MouseEvent,\n useMemo,\n useRef,\n} from 'react';\nimport { isShallowEqual } from 'yummies/data';\nimport type { IsPartial, Maybe } from 'yummies/types';\n\ninterface LinkAnchorProps\n extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n asChild?: boolean;\n}\n\ntype LinkPathRouteProps<TRoute extends AnyRoute> = {\n to: TRoute;\n} & (IsPartial<InputPathParams<TRoute['pathDeclaration']>> extends true\n ? {\n params?: InputPathParams<TRoute['pathDeclaration']> | null | undefined;\n }\n : { params: InputPathParams<TRoute['pathDeclaration']> });\n\ntype LinkSimpleRouteProps =\n | {\n to: string;\n }\n | {\n href: Maybe<string>;\n };\n\nexport type LinkProps<TRoute extends AnyRoute> = LinkAnchorProps &\n RouteNavigateParams &\n (LinkPathRouteProps<TRoute> | LinkSimpleRouteProps);\n\ntype LinkComponentType = <TRoute extends AnyRoute>(\n props: LinkProps<TRoute>,\n) => React.ReactNode;\n\nexport const Link = observer(\n forwardRef<\n HTMLAnchorElement,\n LinkAnchorProps &\n RouteNavigateParams & {\n params?: any;\n to: string | AnyRoute;\n href: string;\n }\n >((props, ref) => {\n const {\n to,\n href: outerHref,\n mergeQuery,\n asChild,\n children,\n params,\n // route navigate params\n query,\n replace,\n state,\n //\n ...outerAnchorProps\n } = props;\n\n const isExternalNavigation =\n outerAnchorProps.target === '_blank' ||\n outerAnchorProps.target === 'blank';\n const queryDataRef = useRef<RouteNavigateParams['query']>(query);\n\n if (!isShallowEqual(queryDataRef.current, query)) {\n queryDataRef.current = query;\n }\n\n const { href, navigateParams } = useMemo(() => {\n const navigateParams: RouteNavigateParams = {\n mergeQuery,\n query,\n replace,\n state,\n };\n\n const cfg = routeConfig.get();\n\n let href: string = '';\n\n if (outerHref) {\n href = outerHref;\n } else if (to) {\n if (typeof to === 'string') {\n const isNeedToMergeQuery =\n navigateParams.mergeQuery ?? cfg.mergeQuery;\n\n const [path, ...querySegments] = to.split('?');\n\n const existedQuery = parseSearchString(querySegments.join('?'));\n\n const query = {\n ...(isNeedToMergeQuery ? cfg.queryParams.data : {}),\n ...existedQuery,\n ...navigateParams.query,\n };\n\n href = `${path}${buildSearchString(query)}`;\n } else {\n href = to.createUrl(\n params,\n navigateParams.query,\n navigateParams.mergeQuery,\n );\n }\n }\n\n return {\n href: cfg.formatLinkHref?.(href) ?? href,\n navigateParams,\n };\n }, [mergeQuery, replace, state, outerHref, to, queryDataRef.current]);\n\n const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {\n if (\n isExternalNavigation ||\n event.ctrlKey ||\n event.metaKey ||\n event.altKey ||\n event.shiftKey ||\n event.button !== 0\n )\n return;\n\n outerAnchorProps.onClick?.(event);\n\n if (\n !event.defaultPrevented &&\n !href.startsWith('https://') &&\n !href.startsWith('http://')\n ) {\n event.preventDefault();\n\n if (navigateParams.replace) {\n routeConfig.get().history.replace(href, navigateParams.state);\n } else {\n routeConfig.get().history.push(href, navigateParams.state);\n }\n }\n };\n\n const anchorProps = {\n ...outerAnchorProps,\n href,\n onClick: handleClick,\n rel:\n outerAnchorProps.rel ??\n (isExternalNavigation ? 'noopener noreferrer' : undefined),\n };\n\n return asChild && isValidElement(children) ? (\n cloneElement(children, anchorProps)\n ) : (\n <a {...anchorProps} ref={ref}>\n {children}\n </a>\n );\n }),\n) as unknown as LinkComponentType;\n","import { observer } from 'mobx-react-lite';\nimport type {\n AnyAbstractRouteEntity,\n AnyRoute,\n AnyVirtualRoute,\n} from 'mobx-route';\n\nexport type RouteViewComponent<TRoute extends AnyAbstractRouteEntity> =\n React.ComponentType<RouteViewProps<TRoute>>;\n\ninterface RouteViewConfigWithoutRoute {\n children?: React.ReactNode | (() => React.ReactNode);\n}\n\nexport interface RouteViewConfigWithRoute<\n TRoute extends AnyAbstractRouteEntity,\n> {\n route: TRoute;\n view?: RouteViewComponent<TRoute>;\n /**\n * Case when route is not opened\n */\n fallback?: React.ReactNode;\n children?:\n | React.ReactNode\n | ((\n params: RouteViewProps<TRoute>['params'],\n route: TRoute,\n ) => React.ReactNode);\n}\n\nexport type RouteViewConfig<TRoute extends AnyAbstractRouteEntity> =\n | RouteViewConfigWithRoute<TRoute>\n | RouteViewConfigWithoutRoute;\n\nexport type RouteViewProps<TRoute extends AnyAbstractRouteEntity> = {\n children?: React.ReactNode;\n params: TRoute extends AnyRoute\n ? Exclude<TRoute['params'], null | undefined>\n : TRoute extends AnyVirtualRoute\n ? TRoute['params']\n : never;\n};\n\ntype RouteViewBaseComponent = <TRoute extends AnyAbstractRouteEntity>(\n props: RouteViewConfig<TRoute>,\n) => React.ReactNode;\n\nfunction RouteViewBase<TRoute extends AnyAbstractRouteEntity>(\n props: Readonly<RouteViewConfig<TRoute>>,\n): React.ReactNode {\n let Component: React.ComponentType<any> | undefined;\n\n if (!('route' in props)) {\n return typeof props.children === 'function'\n ? props.children()\n : props.children;\n }\n\n const isOpening =\n 'isOpening' in props.route &&\n Boolean((props.route as { isOpening?: boolean }).isOpening);\n\n if (!props.route.isOpened && !isOpening) {\n return props.fallback ?? null;\n }\n\n Component = props.view;\n\n const params: any = 'params' in props.route ? props.route.params : {};\n\n if (Component) {\n return <Component params={params}>{props.children}</Component>;\n }\n\n if (typeof props.children === 'function') {\n return props.children(params, props.route);\n }\n\n return props.children;\n}\n\nexport const RouteView = observer(RouteViewBase) as RouteViewBaseComponent;\n","import { observer } from 'mobx-react-lite';\nimport {\n type AnyRoute,\n type AnyRouteEntity,\n buildSearchString,\n isRouteEntity,\n type RouteNavigateParams,\n type RouteParams,\n routeConfig,\n} from 'mobx-route';\nimport { isValidElement, Suspense, useEffect } from 'react';\nimport type { IsPartial, Maybe } from 'yummies/types';\n\ntype LayoutComponent =\n | React.ComponentType<{ children?: React.ReactNode }>\n | React.ComponentType<{ children: React.ReactNode }>;\n\ninterface BaseProps extends RouteNavigateParams {\n children: React.ReactNode;\n layout?: LayoutComponent;\n useLastOpened?: boolean;\n /** When true, wraps the active route content in `<Suspense fallback={fallback}>`. */\n suspense?: boolean;\n /** Fallback content for the Suspense boundary. Only used when `suspense` is true. */\n fallback?: React.ReactNode;\n}\n\ntype PropsWithDefaultRoute<TRoute extends AnyRouteEntity> = BaseProps & {\n otherwise?: TRoute;\n} & (IsPartial<RouteParams<TRoute>> extends true\n ? {\n params?: Maybe<RouteParams<TRoute>>;\n }\n : {\n params: RouteParams<TRoute>;\n });\n\ntype PropsWithDefaultUrl = BaseProps & {\n otherwise?: string;\n};\n\nexport type RouteViewGroupProps<TRoute extends AnyRouteEntity> =\n | PropsWithDefaultRoute<TRoute>\n | PropsWithDefaultUrl;\n\ntype RouteViewGroupComponent = <TRoute extends AnyRouteEntity>(\n props: RouteViewGroupProps<TRoute>,\n) => React.ReactNode;\n\nexport const RouteViewGroup = observer(\n <TRoute extends AnyRouteEntity>({\n children,\n layout: Layout,\n otherwise: otherwiseNavigation,\n useLastOpened,\n suspense,\n fallback,\n // @ts-expect-error\n params,\n ...navigateParams\n }: RouteViewGroupProps<TRoute>) => {\n let activeChildRouteNode: React.ReactNode = null;\n let lastInactiveChildNode: React.ReactNode = null;\n let hasRoutesInOpening = false;\n\n const childNodes: React.ReactNode[] = Array.isArray(children)\n ? children\n : [children];\n\n for (const childNode of childNodes) {\n const isRouteChild =\n isValidElement(childNode) &&\n // @ts-expect-error redundant checks better to wrap in this directive\n isRouteEntity(childNode.props?.route);\n\n if (isRouteChild) {\n const route = (childNode.props as any).route as AnyRoute;\n\n // isOpening covers confirmOpening and the Back gap — break early so\n // a later-opening virtual route (e.g. notFound) doesn't steal the slot.\n if (route.isOpened || route.isOpening) {\n activeChildRouteNode = childNode;\n if (route.isOpening) {\n hasRoutesInOpening = true;\n }\n if (!useLastOpened) {\n break;\n }\n } else {\n lastInactiveChildNode = childNode;\n }\n } else {\n lastInactiveChildNode = childNode;\n }\n }\n\n const hasActiveChildNode = !!activeChildRouteNode;\n\n const otherwiseIsString = typeof otherwiseNavigation === 'string';\n const otherwiseRoute = !otherwiseIsString ? otherwiseNavigation : undefined;\n\n useEffect(() => {\n let cancelled = false;\n\n queueMicrotask(() => {\n if (\n cancelled ||\n hasActiveChildNode ||\n hasRoutesInOpening ||\n !otherwiseNavigation\n ) {\n return;\n }\n\n if (otherwiseIsString) {\n const history = routeConfig.get().history;\n const url = `${otherwiseNavigation}${buildSearchString(navigateParams.query || {})}`;\n\n if (navigateParams.replace) {\n history.replace(url, navigateParams.state);\n } else {\n history.push(url, navigateParams.state);\n }\n } else if (otherwiseRoute && !otherwiseRoute.isOpened) {\n otherwiseRoute.open(params, navigateParams);\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [\n hasActiveChildNode,\n hasRoutesInOpening,\n otherwiseIsString,\n otherwiseRoute,\n ]);\n\n // For string otherwise: return null — effect will navigate and route opens next tick.\n // For route otherwise: don't return null — useLayoutEffect opens it synchronously,\n // returning null would unmount Layout and break withViewModel auto-id VMs.\n const shouldRenderNullOtherwise =\n otherwiseNavigation && !activeChildRouteNode && otherwiseIsString;\n\n if (shouldRenderNullOtherwise) {\n return null;\n }\n\n const resultNodeToRender =\n activeChildRouteNode ?? lastInactiveChildNode ?? null;\n\n const content = suspense ? (\n <Suspense fallback={fallback ?? null}>{resultNodeToRender}</Suspense>\n ) : (\n resultNodeToRender\n );\n\n if (Layout) {\n return <Layout>{content}</Layout>;\n }\n\n return content;\n },\n) as unknown as RouteViewGroupComponent;\n"],"names":["observer","forwardRef","useRef","isShallowEqual","useMemo","navigateParams","routeConfig","href","parseSearchString","query","buildSearchString","isValidElement","cloneElement","jsx","isRouteEntity","useEffect","Suspense"],"mappings":"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"}
package/react.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { observer } from "mobx-react-lite";
3
3
  import { routeConfig, parseSearchString, buildSearchString, isRouteEntity } from "mobx-route";
4
- import { forwardRef, useRef, useMemo, isValidElement, cloneElement, useEffect, useLayoutEffect, Suspense } from "react";
4
+ import { forwardRef, useRef, useMemo, isValidElement, cloneElement, useEffect, Suspense } from "react";
5
5
  import { isShallowEqual } from "yummies/data";
6
6
  const Link = observer(
7
7
  forwardRef((props, ref) => {
@@ -101,7 +101,6 @@ function RouteViewBase(props) {
101
101
  return props.children;
102
102
  }
103
103
  const RouteView = observer(RouteViewBase);
104
- const useOtherwiseEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
105
104
  const RouteViewGroup = observer(
106
105
  ({
107
106
  children,
@@ -141,8 +140,12 @@ const RouteViewGroup = observer(
141
140
  const hasActiveChildNode = !!activeChildRouteNode;
142
141
  const otherwiseIsString = typeof otherwiseNavigation === "string";
143
142
  const otherwiseRoute = !otherwiseIsString ? otherwiseNavigation : void 0;
144
- useOtherwiseEffect(() => {
145
- if (!hasActiveChildNode && !hasRoutesInOpening && otherwiseNavigation) {
143
+ useEffect(() => {
144
+ let cancelled = false;
145
+ queueMicrotask(() => {
146
+ if (cancelled || hasActiveChildNode || hasRoutesInOpening || !otherwiseNavigation) {
147
+ return;
148
+ }
146
149
  if (otherwiseIsString) {
147
150
  const history = routeConfig.get().history;
148
151
  const url = `${otherwiseNavigation}${buildSearchString(navigateParams.query || {})}`;
@@ -154,7 +157,10 @@ const RouteViewGroup = observer(
154
157
  } else if (otherwiseRoute && !otherwiseRoute.isOpened) {
155
158
  otherwiseRoute.open(params, navigateParams);
156
159
  }
157
- }
160
+ });
161
+ return () => {
162
+ cancelled = true;
163
+ };
158
164
  }, [
159
165
  hasActiveChildNode,
160
166
  hasRoutesInOpening,
package/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","sources":["../src/react/components/link.tsx","../src/react/components/route-view.tsx","../src/react/components/route-view-group.tsx"],"sourcesContent":["import { observer } from 'mobx-react-lite';\nimport {\n type AnyRoute,\n buildSearchString,\n type InputPathParams,\n parseSearchString,\n type RouteNavigateParams,\n routeConfig,\n} from 'mobx-route';\nimport {\n type AnchorHTMLAttributes,\n cloneElement,\n forwardRef,\n isValidElement,\n type MouseEvent,\n useMemo,\n useRef,\n} from 'react';\nimport { isShallowEqual } from 'yummies/data';\nimport type { IsPartial, Maybe } from 'yummies/types';\n\ninterface LinkAnchorProps\n extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n asChild?: boolean;\n}\n\ntype LinkPathRouteProps<TRoute extends AnyRoute> = {\n to: TRoute;\n} & (IsPartial<InputPathParams<TRoute['pathDeclaration']>> extends true\n ? {\n params?: InputPathParams<TRoute['pathDeclaration']> | null | undefined;\n }\n : { params: InputPathParams<TRoute['pathDeclaration']> });\n\ntype LinkSimpleRouteProps =\n | {\n to: string;\n }\n | {\n href: Maybe<string>;\n };\n\nexport type LinkProps<TRoute extends AnyRoute> = LinkAnchorProps &\n RouteNavigateParams &\n (LinkPathRouteProps<TRoute> | LinkSimpleRouteProps);\n\ntype LinkComponentType = <TRoute extends AnyRoute>(\n props: LinkProps<TRoute>,\n) => React.ReactNode;\n\nexport const Link = observer(\n forwardRef<\n HTMLAnchorElement,\n LinkAnchorProps &\n RouteNavigateParams & {\n params?: any;\n to: string | AnyRoute;\n href: string;\n }\n >((props, ref) => {\n const {\n to,\n href: outerHref,\n mergeQuery,\n asChild,\n children,\n params,\n // route navigate params\n query,\n replace,\n state,\n //\n ...outerAnchorProps\n } = props;\n\n const isExternalNavigation =\n outerAnchorProps.target === '_blank' ||\n outerAnchorProps.target === 'blank';\n const queryDataRef = useRef<RouteNavigateParams['query']>(query);\n\n if (!isShallowEqual(queryDataRef.current, query)) {\n queryDataRef.current = query;\n }\n\n const { href, navigateParams } = useMemo(() => {\n const navigateParams: RouteNavigateParams = {\n mergeQuery,\n query,\n replace,\n state,\n };\n\n const cfg = routeConfig.get();\n\n let href: string = '';\n\n if (outerHref) {\n href = outerHref;\n } else if (to) {\n if (typeof to === 'string') {\n const isNeedToMergeQuery =\n navigateParams.mergeQuery ?? cfg.mergeQuery;\n\n const [path, ...querySegments] = to.split('?');\n\n const existedQuery = parseSearchString(querySegments.join('?'));\n\n const query = {\n ...(isNeedToMergeQuery ? cfg.queryParams.data : {}),\n ...existedQuery,\n ...navigateParams.query,\n };\n\n href = `${path}${buildSearchString(query)}`;\n } else {\n href = to.createUrl(\n params,\n navigateParams.query,\n navigateParams.mergeQuery,\n );\n }\n }\n\n return {\n href: cfg.formatLinkHref?.(href) ?? href,\n navigateParams,\n };\n }, [mergeQuery, replace, state, outerHref, to, queryDataRef.current]);\n\n const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {\n if (\n isExternalNavigation ||\n event.ctrlKey ||\n event.metaKey ||\n event.altKey ||\n event.shiftKey ||\n event.button !== 0\n )\n return;\n\n outerAnchorProps.onClick?.(event);\n\n if (\n !event.defaultPrevented &&\n !href.startsWith('https://') &&\n !href.startsWith('http://')\n ) {\n event.preventDefault();\n\n if (navigateParams.replace) {\n routeConfig.get().history.replace(href, navigateParams.state);\n } else {\n routeConfig.get().history.push(href, navigateParams.state);\n }\n }\n };\n\n const anchorProps = {\n ...outerAnchorProps,\n href,\n onClick: handleClick,\n rel:\n outerAnchorProps.rel ??\n (isExternalNavigation ? 'noopener noreferrer' : undefined),\n };\n\n return asChild && isValidElement(children) ? (\n cloneElement(children, anchorProps)\n ) : (\n <a {...anchorProps} ref={ref}>\n {children}\n </a>\n );\n }),\n) as unknown as LinkComponentType;\n","import { observer } from 'mobx-react-lite';\nimport type {\n AnyAbstractRouteEntity,\n AnyRoute,\n AnyVirtualRoute,\n} from 'mobx-route';\n\nexport type RouteViewComponent<TRoute extends AnyAbstractRouteEntity> =\n React.ComponentType<RouteViewProps<TRoute>>;\n\ninterface RouteViewConfigWithoutRoute {\n children?: React.ReactNode | (() => React.ReactNode);\n}\n\nexport interface RouteViewConfigWithRoute<\n TRoute extends AnyAbstractRouteEntity,\n> {\n route: TRoute;\n view?: RouteViewComponent<TRoute>;\n /**\n * Case when route is not opened\n */\n fallback?: React.ReactNode;\n children?:\n | React.ReactNode\n | ((\n params: RouteViewProps<TRoute>['params'],\n route: TRoute,\n ) => React.ReactNode);\n}\n\nexport type RouteViewConfig<TRoute extends AnyAbstractRouteEntity> =\n | RouteViewConfigWithRoute<TRoute>\n | RouteViewConfigWithoutRoute;\n\nexport type RouteViewProps<TRoute extends AnyAbstractRouteEntity> = {\n children?: React.ReactNode;\n params: TRoute extends AnyRoute\n ? Exclude<TRoute['params'], null | undefined>\n : TRoute extends AnyVirtualRoute\n ? TRoute['params']\n : never;\n};\n\ntype RouteViewBaseComponent = <TRoute extends AnyAbstractRouteEntity>(\n props: RouteViewConfig<TRoute>,\n) => React.ReactNode;\n\nfunction RouteViewBase<TRoute extends AnyAbstractRouteEntity>(\n props: Readonly<RouteViewConfig<TRoute>>,\n): React.ReactNode {\n let Component: React.ComponentType<any> | undefined;\n\n if (!('route' in props)) {\n return typeof props.children === 'function'\n ? props.children()\n : props.children;\n }\n\n const isOpening =\n 'isOpening' in props.route &&\n Boolean((props.route as { isOpening?: boolean }).isOpening);\n\n if (!props.route.isOpened && !isOpening) {\n return props.fallback ?? null;\n }\n\n Component = props.view;\n\n const params: any = 'params' in props.route ? props.route.params : {};\n\n if (Component) {\n return <Component params={params}>{props.children}</Component>;\n }\n\n if (typeof props.children === 'function') {\n return props.children(params, props.route);\n }\n\n return props.children;\n}\n\nexport const RouteView = observer(RouteViewBase) as RouteViewBaseComponent;\n","import { observer } from 'mobx-react-lite';\nimport {\n type AnyRoute,\n type AnyRouteEntity,\n buildSearchString,\n isRouteEntity,\n type RouteNavigateParams,\n type RouteParams,\n routeConfig,\n} from 'mobx-route';\nimport { isValidElement, Suspense, useEffect, useLayoutEffect } from 'react';\nimport type { IsPartial, Maybe } from 'yummies/types';\n\ntype LayoutComponent =\n | React.ComponentType<{ children?: React.ReactNode }>\n | React.ComponentType<{ children: React.ReactNode }>;\n\ninterface BaseProps extends RouteNavigateParams {\n children: React.ReactNode;\n layout?: LayoutComponent;\n useLastOpened?: boolean;\n /** When true, wraps the active route content in `<Suspense fallback={fallback}>`. */\n suspense?: boolean;\n /** Fallback content for the Suspense boundary. Only used when `suspense` is true. */\n fallback?: React.ReactNode;\n}\n\ntype PropsWithDefaultRoute<TRoute extends AnyRouteEntity> = BaseProps & {\n otherwise?: TRoute;\n} & (IsPartial<RouteParams<TRoute>> extends true\n ? {\n params?: Maybe<RouteParams<TRoute>>;\n }\n : {\n params: RouteParams<TRoute>;\n });\n\ntype PropsWithDefaultUrl = BaseProps & {\n otherwise?: string;\n};\n\nexport type RouteViewGroupProps<TRoute extends AnyRouteEntity> =\n | PropsWithDefaultRoute<TRoute>\n | PropsWithDefaultUrl;\n\ntype RouteViewGroupComponent = <TRoute extends AnyRouteEntity>(\n props: RouteViewGroupProps<TRoute>,\n) => React.ReactNode;\n\nconst useOtherwiseEffect =\n typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\nexport const RouteViewGroup = observer(\n <TRoute extends AnyRouteEntity>({\n children,\n layout: Layout,\n otherwise: otherwiseNavigation,\n useLastOpened,\n suspense,\n fallback,\n // @ts-expect-error\n params,\n ...navigateParams\n }: RouteViewGroupProps<TRoute>) => {\n let activeChildRouteNode: React.ReactNode = null;\n let lastInactiveChildNode: React.ReactNode = null;\n let hasRoutesInOpening = false;\n\n const childNodes: React.ReactNode[] = Array.isArray(children)\n ? children\n : [children];\n\n for (const childNode of childNodes) {\n const isRouteChild =\n isValidElement(childNode) &&\n // @ts-expect-error redundant checks better to wrap in this directive\n isRouteEntity(childNode.props?.route);\n\n if (isRouteChild) {\n const route = (childNode.props as any).route as AnyRoute;\n\n // isOpening covers confirmOpening and the Back gap — break early so\n // a later-opening virtual route (e.g. notFound) doesn't steal the slot.\n if (route.isOpened || route.isOpening) {\n activeChildRouteNode = childNode;\n if (route.isOpening) {\n hasRoutesInOpening = true;\n }\n if (!useLastOpened) {\n break;\n }\n } else {\n lastInactiveChildNode = childNode;\n }\n } else {\n lastInactiveChildNode = childNode;\n }\n }\n\n const hasActiveChildNode = !!activeChildRouteNode;\n\n const otherwiseIsString = typeof otherwiseNavigation === 'string';\n const otherwiseRoute = !otherwiseIsString ? otherwiseNavigation : undefined;\n\n useOtherwiseEffect(() => {\n if (!hasActiveChildNode && !hasRoutesInOpening && otherwiseNavigation) {\n if (otherwiseIsString) {\n const history = routeConfig.get().history;\n const url = `${otherwiseNavigation}${buildSearchString(navigateParams.query || {})}`;\n\n if (navigateParams.replace) {\n history.replace(url, navigateParams.state);\n } else {\n history.push(url, navigateParams.state);\n }\n } else if (otherwiseRoute && !otherwiseRoute.isOpened) {\n otherwiseRoute.open(params, navigateParams);\n }\n }\n }, [\n hasActiveChildNode,\n hasRoutesInOpening,\n otherwiseIsString,\n otherwiseRoute,\n ]);\n\n // For string otherwise: return null — effect will navigate and route opens next tick.\n // For route otherwise: don't return null — useLayoutEffect opens it synchronously,\n // returning null would unmount Layout and break withViewModel auto-id VMs.\n const shouldRenderNullOtherwise =\n otherwiseNavigation && !activeChildRouteNode && otherwiseIsString;\n\n if (shouldRenderNullOtherwise) {\n return null;\n }\n\n const resultNodeToRender =\n activeChildRouteNode ?? lastInactiveChildNode ?? null;\n\n const content = suspense ? (\n <Suspense fallback={fallback ?? null}>{resultNodeToRender}</Suspense>\n ) : (\n resultNodeToRender\n );\n\n if (Layout) {\n return <Layout>{content}</Layout>;\n }\n\n return content;\n },\n) as unknown as RouteViewGroupComponent;\n"],"names":["navigateParams","href","query"],"mappings":"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"}
1
+ {"version":3,"file":"react.js","sources":["../src/react/components/link.tsx","../src/react/components/route-view.tsx","../src/react/components/route-view-group.tsx"],"sourcesContent":["import { observer } from 'mobx-react-lite';\nimport {\n type AnyRoute,\n buildSearchString,\n type InputPathParams,\n parseSearchString,\n type RouteNavigateParams,\n routeConfig,\n} from 'mobx-route';\nimport {\n type AnchorHTMLAttributes,\n cloneElement,\n forwardRef,\n isValidElement,\n type MouseEvent,\n useMemo,\n useRef,\n} from 'react';\nimport { isShallowEqual } from 'yummies/data';\nimport type { IsPartial, Maybe } from 'yummies/types';\n\ninterface LinkAnchorProps\n extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n asChild?: boolean;\n}\n\ntype LinkPathRouteProps<TRoute extends AnyRoute> = {\n to: TRoute;\n} & (IsPartial<InputPathParams<TRoute['pathDeclaration']>> extends true\n ? {\n params?: InputPathParams<TRoute['pathDeclaration']> | null | undefined;\n }\n : { params: InputPathParams<TRoute['pathDeclaration']> });\n\ntype LinkSimpleRouteProps =\n | {\n to: string;\n }\n | {\n href: Maybe<string>;\n };\n\nexport type LinkProps<TRoute extends AnyRoute> = LinkAnchorProps &\n RouteNavigateParams &\n (LinkPathRouteProps<TRoute> | LinkSimpleRouteProps);\n\ntype LinkComponentType = <TRoute extends AnyRoute>(\n props: LinkProps<TRoute>,\n) => React.ReactNode;\n\nexport const Link = observer(\n forwardRef<\n HTMLAnchorElement,\n LinkAnchorProps &\n RouteNavigateParams & {\n params?: any;\n to: string | AnyRoute;\n href: string;\n }\n >((props, ref) => {\n const {\n to,\n href: outerHref,\n mergeQuery,\n asChild,\n children,\n params,\n // route navigate params\n query,\n replace,\n state,\n //\n ...outerAnchorProps\n } = props;\n\n const isExternalNavigation =\n outerAnchorProps.target === '_blank' ||\n outerAnchorProps.target === 'blank';\n const queryDataRef = useRef<RouteNavigateParams['query']>(query);\n\n if (!isShallowEqual(queryDataRef.current, query)) {\n queryDataRef.current = query;\n }\n\n const { href, navigateParams } = useMemo(() => {\n const navigateParams: RouteNavigateParams = {\n mergeQuery,\n query,\n replace,\n state,\n };\n\n const cfg = routeConfig.get();\n\n let href: string = '';\n\n if (outerHref) {\n href = outerHref;\n } else if (to) {\n if (typeof to === 'string') {\n const isNeedToMergeQuery =\n navigateParams.mergeQuery ?? cfg.mergeQuery;\n\n const [path, ...querySegments] = to.split('?');\n\n const existedQuery = parseSearchString(querySegments.join('?'));\n\n const query = {\n ...(isNeedToMergeQuery ? cfg.queryParams.data : {}),\n ...existedQuery,\n ...navigateParams.query,\n };\n\n href = `${path}${buildSearchString(query)}`;\n } else {\n href = to.createUrl(\n params,\n navigateParams.query,\n navigateParams.mergeQuery,\n );\n }\n }\n\n return {\n href: cfg.formatLinkHref?.(href) ?? href,\n navigateParams,\n };\n }, [mergeQuery, replace, state, outerHref, to, queryDataRef.current]);\n\n const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {\n if (\n isExternalNavigation ||\n event.ctrlKey ||\n event.metaKey ||\n event.altKey ||\n event.shiftKey ||\n event.button !== 0\n )\n return;\n\n outerAnchorProps.onClick?.(event);\n\n if (\n !event.defaultPrevented &&\n !href.startsWith('https://') &&\n !href.startsWith('http://')\n ) {\n event.preventDefault();\n\n if (navigateParams.replace) {\n routeConfig.get().history.replace(href, navigateParams.state);\n } else {\n routeConfig.get().history.push(href, navigateParams.state);\n }\n }\n };\n\n const anchorProps = {\n ...outerAnchorProps,\n href,\n onClick: handleClick,\n rel:\n outerAnchorProps.rel ??\n (isExternalNavigation ? 'noopener noreferrer' : undefined),\n };\n\n return asChild && isValidElement(children) ? (\n cloneElement(children, anchorProps)\n ) : (\n <a {...anchorProps} ref={ref}>\n {children}\n </a>\n );\n }),\n) as unknown as LinkComponentType;\n","import { observer } from 'mobx-react-lite';\nimport type {\n AnyAbstractRouteEntity,\n AnyRoute,\n AnyVirtualRoute,\n} from 'mobx-route';\n\nexport type RouteViewComponent<TRoute extends AnyAbstractRouteEntity> =\n React.ComponentType<RouteViewProps<TRoute>>;\n\ninterface RouteViewConfigWithoutRoute {\n children?: React.ReactNode | (() => React.ReactNode);\n}\n\nexport interface RouteViewConfigWithRoute<\n TRoute extends AnyAbstractRouteEntity,\n> {\n route: TRoute;\n view?: RouteViewComponent<TRoute>;\n /**\n * Case when route is not opened\n */\n fallback?: React.ReactNode;\n children?:\n | React.ReactNode\n | ((\n params: RouteViewProps<TRoute>['params'],\n route: TRoute,\n ) => React.ReactNode);\n}\n\nexport type RouteViewConfig<TRoute extends AnyAbstractRouteEntity> =\n | RouteViewConfigWithRoute<TRoute>\n | RouteViewConfigWithoutRoute;\n\nexport type RouteViewProps<TRoute extends AnyAbstractRouteEntity> = {\n children?: React.ReactNode;\n params: TRoute extends AnyRoute\n ? Exclude<TRoute['params'], null | undefined>\n : TRoute extends AnyVirtualRoute\n ? TRoute['params']\n : never;\n};\n\ntype RouteViewBaseComponent = <TRoute extends AnyAbstractRouteEntity>(\n props: RouteViewConfig<TRoute>,\n) => React.ReactNode;\n\nfunction RouteViewBase<TRoute extends AnyAbstractRouteEntity>(\n props: Readonly<RouteViewConfig<TRoute>>,\n): React.ReactNode {\n let Component: React.ComponentType<any> | undefined;\n\n if (!('route' in props)) {\n return typeof props.children === 'function'\n ? props.children()\n : props.children;\n }\n\n const isOpening =\n 'isOpening' in props.route &&\n Boolean((props.route as { isOpening?: boolean }).isOpening);\n\n if (!props.route.isOpened && !isOpening) {\n return props.fallback ?? null;\n }\n\n Component = props.view;\n\n const params: any = 'params' in props.route ? props.route.params : {};\n\n if (Component) {\n return <Component params={params}>{props.children}</Component>;\n }\n\n if (typeof props.children === 'function') {\n return props.children(params, props.route);\n }\n\n return props.children;\n}\n\nexport const RouteView = observer(RouteViewBase) as RouteViewBaseComponent;\n","import { observer } from 'mobx-react-lite';\nimport {\n type AnyRoute,\n type AnyRouteEntity,\n buildSearchString,\n isRouteEntity,\n type RouteNavigateParams,\n type RouteParams,\n routeConfig,\n} from 'mobx-route';\nimport { isValidElement, Suspense, useEffect } from 'react';\nimport type { IsPartial, Maybe } from 'yummies/types';\n\ntype LayoutComponent =\n | React.ComponentType<{ children?: React.ReactNode }>\n | React.ComponentType<{ children: React.ReactNode }>;\n\ninterface BaseProps extends RouteNavigateParams {\n children: React.ReactNode;\n layout?: LayoutComponent;\n useLastOpened?: boolean;\n /** When true, wraps the active route content in `<Suspense fallback={fallback}>`. */\n suspense?: boolean;\n /** Fallback content for the Suspense boundary. Only used when `suspense` is true. */\n fallback?: React.ReactNode;\n}\n\ntype PropsWithDefaultRoute<TRoute extends AnyRouteEntity> = BaseProps & {\n otherwise?: TRoute;\n} & (IsPartial<RouteParams<TRoute>> extends true\n ? {\n params?: Maybe<RouteParams<TRoute>>;\n }\n : {\n params: RouteParams<TRoute>;\n });\n\ntype PropsWithDefaultUrl = BaseProps & {\n otherwise?: string;\n};\n\nexport type RouteViewGroupProps<TRoute extends AnyRouteEntity> =\n | PropsWithDefaultRoute<TRoute>\n | PropsWithDefaultUrl;\n\ntype RouteViewGroupComponent = <TRoute extends AnyRouteEntity>(\n props: RouteViewGroupProps<TRoute>,\n) => React.ReactNode;\n\nexport const RouteViewGroup = observer(\n <TRoute extends AnyRouteEntity>({\n children,\n layout: Layout,\n otherwise: otherwiseNavigation,\n useLastOpened,\n suspense,\n fallback,\n // @ts-expect-error\n params,\n ...navigateParams\n }: RouteViewGroupProps<TRoute>) => {\n let activeChildRouteNode: React.ReactNode = null;\n let lastInactiveChildNode: React.ReactNode = null;\n let hasRoutesInOpening = false;\n\n const childNodes: React.ReactNode[] = Array.isArray(children)\n ? children\n : [children];\n\n for (const childNode of childNodes) {\n const isRouteChild =\n isValidElement(childNode) &&\n // @ts-expect-error redundant checks better to wrap in this directive\n isRouteEntity(childNode.props?.route);\n\n if (isRouteChild) {\n const route = (childNode.props as any).route as AnyRoute;\n\n // isOpening covers confirmOpening and the Back gap — break early so\n // a later-opening virtual route (e.g. notFound) doesn't steal the slot.\n if (route.isOpened || route.isOpening) {\n activeChildRouteNode = childNode;\n if (route.isOpening) {\n hasRoutesInOpening = true;\n }\n if (!useLastOpened) {\n break;\n }\n } else {\n lastInactiveChildNode = childNode;\n }\n } else {\n lastInactiveChildNode = childNode;\n }\n }\n\n const hasActiveChildNode = !!activeChildRouteNode;\n\n const otherwiseIsString = typeof otherwiseNavigation === 'string';\n const otherwiseRoute = !otherwiseIsString ? otherwiseNavigation : undefined;\n\n useEffect(() => {\n let cancelled = false;\n\n queueMicrotask(() => {\n if (\n cancelled ||\n hasActiveChildNode ||\n hasRoutesInOpening ||\n !otherwiseNavigation\n ) {\n return;\n }\n\n if (otherwiseIsString) {\n const history = routeConfig.get().history;\n const url = `${otherwiseNavigation}${buildSearchString(navigateParams.query || {})}`;\n\n if (navigateParams.replace) {\n history.replace(url, navigateParams.state);\n } else {\n history.push(url, navigateParams.state);\n }\n } else if (otherwiseRoute && !otherwiseRoute.isOpened) {\n otherwiseRoute.open(params, navigateParams);\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [\n hasActiveChildNode,\n hasRoutesInOpening,\n otherwiseIsString,\n otherwiseRoute,\n ]);\n\n // For string otherwise: return null — effect will navigate and route opens next tick.\n // For route otherwise: don't return null — useLayoutEffect opens it synchronously,\n // returning null would unmount Layout and break withViewModel auto-id VMs.\n const shouldRenderNullOtherwise =\n otherwiseNavigation && !activeChildRouteNode && otherwiseIsString;\n\n if (shouldRenderNullOtherwise) {\n return null;\n }\n\n const resultNodeToRender =\n activeChildRouteNode ?? lastInactiveChildNode ?? null;\n\n const content = suspense ? (\n <Suspense fallback={fallback ?? null}>{resultNodeToRender}</Suspense>\n ) : (\n resultNodeToRender\n );\n\n if (Layout) {\n return <Layout>{content}</Layout>;\n }\n\n return content;\n },\n) as unknown as RouteViewGroupComponent;\n"],"names":["navigateParams","href","query"],"mappings":"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"}