mobx-route 2.1.1 → 2.1.3

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.3",
4
4
  "keywords": [
5
5
  "mobx",
6
6
  "react",
@@ -29,7 +29,7 @@
29
29
  "peerDependencies": {
30
30
  "mobx": "^6.12.4",
31
31
  "mobx-react-lite": "^4.0.7",
32
- "mobx-view-model": "^8.10.0 || ^9.0.0 || ^10.0.0",
32
+ "mobx-view-model": "^8.10.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
33
33
  "react": "^18.0.0 || ^19.0.0",
34
34
  "react-dom": "^18.0.0 || ^19.0.0"
35
35
  },
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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"}
package/view-model.cjs CHANGED
@@ -22,7 +22,11 @@ function withRoute(route) {
22
22
  _lastPayload = {};
23
23
  constructor(...args) {
24
24
  super(...args);
25
- mobxViewModel.applyObservable(this, annotations, this.vmConfig.observable.viewModels);
25
+ const observableConfig = this.vmConfig?.observable?.viewModels ?? {
26
+ ...mobxViewModel.viewModelsConfig.observable.viewModels,
27
+ ...args[0]?.vmConfig?.observable?.viewModels
28
+ };
29
+ mobxViewModel.applyObservable(this, annotations, observableConfig);
26
30
  }
27
31
  /**
28
32
  * Current route params with fallback to the last cached value.
@@ -1 +1 @@
1
- {"version":3,"file":"view-model.cjs","sources":["../src/view-model/route-view-model.ts"],"sourcesContent":["import { computed } from 'mobx';\nimport type { IQueryParams } from 'mobx-location-history';\nimport {\n applyObservable,\n type ViewModel,\n ViewModelBase,\n} from 'mobx-view-model';\nimport type { ObservableAnnotationsArray } from 'yummies/mobx';\nimport type { Class } from 'yummies/types';\nimport {\n type AnyAbstractRouteEntity,\n type RouteParams,\n routeConfig,\n} from '../core/index.js';\n\nconst annotations: ObservableAnnotationsArray<any> = [\n [computed.struct, 'pathParams'],\n [computed, 'query'],\n];\n\n/**\n * Public interface added by `withRouteViewModel` mixin.\n */\ntype RouteViewModelMixinPublic<TRoute extends AnyAbstractRouteEntity> = {\n readonly route: TRoute;\n _lastPayload: RouteParams<TRoute>;\n payload: RouteParams<TRoute>;\n query: IQueryParams;\n pathParams: RouteParams<TRoute>;\n isMounted: boolean;\n};\n\n/**\n * Mixin function that enhances any `ViewModelBase` subclass with route-related properties.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel)\n *\n * @param route - Route entity to bind to the view model.\n * @returns A class enhancer that takes a base class and returns a new class with route properties.\n *\n * @example\n * ```ts\n * // With ViewModelBase directly\n * class MyVM extends withRoute(someRoute)(ViewModelBase) {}\n *\n * // With a custom base class\n * class ProductPageVM extends withRoute(productRoute)(AppVM) {}\n * ```\n */\nexport function withRoute<TRoute extends AnyAbstractRouteEntity>(\n route: TRoute,\n) {\n return <TBase extends Class<ViewModel<any, any>>>(\n Base: TBase,\n ): TBase & Class<InstanceType<TBase> & RouteViewModelMixinPublic<TRoute>> => {\n class RouteViewModelMixin extends Base {\n /**\n * Route entity bound to this view model.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel#route)\n */\n readonly route: TRoute = route;\n\n /**\n * Caches the latest known route params.\n */\n _lastPayload: RouteParams<TRoute> = {} as RouteParams<TRoute>;\n\n constructor(...args: any[]) {\n super(...args);\n\n applyObservable(this, annotations, this.vmConfig.observable.viewModels);\n }\n\n /**\n * Current route params with fallback to the last cached value.\n */\n override get payload(): RouteParams<TRoute> {\n if ('params' in this.route && this.route.params != null) {\n this._lastPayload = this.route.params as RouteParams<TRoute>;\n }\n\n return this._lastPayload;\n }\n\n /**\n * Current query params from route or global route config.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel#query)\n */\n get query(): IQueryParams {\n if ('query' in this.route) {\n return this.route.query as IQueryParams;\n }\n\n return routeConfig.get().queryParams;\n }\n\n /**\n * Alias for `payload`.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel#pathparams)\n */\n get pathParams() {\n return this.payload;\n }\n\n /**\n * Mounted state including the route opened status.\n */\n override get isMounted() {\n return super.isMounted && this.route.isOpened;\n }\n }\n\n return RouteViewModelMixin as any;\n };\n}\n\n/**\n * Base class created by the mixin with a dummy route.\n * Used internally by `RouteViewModel` to avoid duplicating the mixin logic.\n */\nconst _RouteViewModelBase = withRoute({} as any)(ViewModelBase);\n\n/**\n * Abstract route-aware view model that extends `ViewModelBase<EmptyObject>`.\n *\n * Provides `route`, `payload`, `pathParams`, `query`, and `isMounted` properties.\n * Subclasses must declare `route = someRoute`.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel)\n *\n * @example\n * ```ts\n * class MyVM extends RouteViewModel<typeof myRoute> {\n * route = myRoute;\n * }\n * ```\n */\nexport abstract class RouteViewModel<\n TRoute extends AnyAbstractRouteEntity = AnyAbstractRouteEntity,\n> extends _RouteViewModelBase {\n abstract override readonly route: TRoute;\n}\n"],"names":["computed","applyObservable","routeConfig","ViewModelBase"],"mappings":";;;;;AAeA,MAAM,cAA+C;AAAA,EACnD,CAACA,KAAAA,SAAS,QAAQ,YAAY;AAAA,EAC9B,CAACA,KAAAA,UAAU,OAAO;AACpB;AA+BO,SAAS,UACd,OACA;AACA,SAAO,CACL,SAC2E;AAAA,IAC3E,MAAM,4BAA4B,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAM5B,QAAgB;AAAA;AAAA;AAAA;AAAA,MAKzB,eAAoC,CAAA;AAAA,MAEpC,eAAe,MAAa;AAC1B,cAAM,GAAG,IAAI;AAEbC,sBAAAA,gBAAgB,MAAM,aAAa,KAAK,SAAS,WAAW,UAAU;AAAA,MACxE;AAAA;AAAA;AAAA;AAAA,MAKA,IAAa,UAA+B;AAC1C,YAAI,YAAY,KAAK,SAAS,KAAK,MAAM,UAAU,MAAM;AACvD,eAAK,eAAe,KAAK,MAAM;AAAA,QACjC;AAEA,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,IAAI,QAAsB;AACxB,YAAI,WAAW,KAAK,OAAO;AACzB,iBAAO,KAAK,MAAM;AAAA,QACpB;AAEA,eAAOC,aAAAA,YAAY,MAAM;AAAA,MAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,IAAI,aAAa;AACf,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA,MAKA,IAAa,YAAY;AACvB,eAAO,MAAM,aAAa,KAAK,MAAM;AAAA,MACvC;AAAA,IAAA;AAGF,WAAO;AAAA,EACT;AACF;AAMA,MAAM,sBAAsB,UAAU,EAAS,EAAEC,2BAAa;AAiBvD,MAAe,uBAEZ,oBAAoB;AAE9B;;;"}
1
+ {"version":3,"file":"view-model.cjs","sources":["../src/view-model/route-view-model.ts"],"sourcesContent":["import { computed } from 'mobx';\nimport type { IQueryParams } from 'mobx-location-history';\nimport {\n applyObservable,\n type ViewModel,\n ViewModelBase,\n viewModelsConfig,\n} from 'mobx-view-model';\nimport type { ObservableAnnotationsArray } from 'yummies/mobx';\nimport type { Class } from 'yummies/types';\nimport {\n type AnyAbstractRouteEntity,\n type RouteParams,\n routeConfig,\n} from '../core/index.js';\n\nconst annotations: ObservableAnnotationsArray<any> = [\n [computed.struct, 'pathParams'],\n [computed, 'query'],\n];\n\n/**\n * Public interface added by `withRouteViewModel` mixin.\n */\ntype RouteViewModelMixinPublic<TRoute extends AnyAbstractRouteEntity> = {\n readonly route: TRoute;\n _lastPayload: RouteParams<TRoute>;\n payload: RouteParams<TRoute>;\n query: IQueryParams;\n pathParams: RouteParams<TRoute>;\n isMounted: boolean;\n};\n\n/**\n * Mixin function that enhances any `ViewModelBase` subclass with route-related properties.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel)\n *\n * @param route - Route entity to bind to the view model.\n * @returns A class enhancer that takes a base class and returns a new class with route properties.\n *\n * @example\n * ```ts\n * // With ViewModelBase directly\n * class MyVM extends withRoute(someRoute)(ViewModelBase) {}\n *\n * // With a custom base class\n * class ProductPageVM extends withRoute(productRoute)(AppVM) {}\n * ```\n */\nexport function withRoute<TRoute extends AnyAbstractRouteEntity>(\n route: TRoute,\n) {\n return <TBase extends Class<ViewModel<any, any>>>(\n Base: TBase,\n ): TBase & Class<InstanceType<TBase> & RouteViewModelMixinPublic<TRoute>> => {\n class RouteViewModelMixin extends Base {\n /**\n * Route entity bound to this view model.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel#route)\n */\n readonly route: TRoute = route;\n\n /**\n * Caches the latest known route params.\n */\n _lastPayload: RouteParams<TRoute> = {} as RouteParams<TRoute>;\n\n constructor(...args: any[]) {\n super(...args);\n\n // v11 keeps vmConfig internal; the exported config supplies its defaults.\n const observableConfig = (this as any).vmConfig?.observable\n ?.viewModels ?? {\n ...viewModelsConfig.observable.viewModels,\n ...args[0]?.vmConfig?.observable?.viewModels,\n };\n\n applyObservable(this, annotations, observableConfig);\n }\n\n /**\n * Current route params with fallback to the last cached value.\n */\n override get payload(): RouteParams<TRoute> {\n if ('params' in this.route && this.route.params != null) {\n this._lastPayload = this.route.params as RouteParams<TRoute>;\n }\n\n return this._lastPayload;\n }\n\n /**\n * Current query params from route or global route config.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel#query)\n */\n get query(): IQueryParams {\n if ('query' in this.route) {\n return this.route.query as IQueryParams;\n }\n\n return routeConfig.get().queryParams;\n }\n\n /**\n * Alias for `payload`.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel#pathparams)\n */\n get pathParams() {\n return this.payload;\n }\n\n /**\n * Mounted state including the route opened status.\n */\n override get isMounted() {\n return super.isMounted && this.route.isOpened;\n }\n }\n\n return RouteViewModelMixin as any;\n };\n}\n\n/**\n * Base class created by the mixin with a dummy route.\n * Used internally by `RouteViewModel` to avoid duplicating the mixin logic.\n */\nconst _RouteViewModelBase = withRoute({} as any)(ViewModelBase);\n\n/**\n * Abstract route-aware view model that extends `ViewModelBase<EmptyObject>`.\n *\n * Provides `route`, `payload`, `pathParams`, `query`, and `isMounted` properties.\n * Subclasses must declare `route = someRoute`.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel)\n *\n * @example\n * ```ts\n * class MyVM extends RouteViewModel<typeof myRoute> {\n * route = myRoute;\n * }\n * ```\n */\nexport abstract class RouteViewModel<\n TRoute extends AnyAbstractRouteEntity = AnyAbstractRouteEntity,\n> extends _RouteViewModelBase {\n abstract override readonly route: TRoute;\n}\n"],"names":["computed","viewModelsConfig","applyObservable","routeConfig","ViewModelBase"],"mappings":";;;;;AAgBA,MAAM,cAA+C;AAAA,EACnD,CAACA,KAAAA,SAAS,QAAQ,YAAY;AAAA,EAC9B,CAACA,KAAAA,UAAU,OAAO;AACpB;AA+BO,SAAS,UACd,OACA;AACA,SAAO,CACL,SAC2E;AAAA,IAC3E,MAAM,4BAA4B,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAM5B,QAAgB;AAAA;AAAA;AAAA;AAAA,MAKzB,eAAoC,CAAA;AAAA,MAEpC,eAAe,MAAa;AAC1B,cAAM,GAAG,IAAI;AAGb,cAAM,mBAAoB,KAAa,UAAU,YAC7C,cAAc;AAAA,UAChB,GAAGC,cAAAA,iBAAiB,WAAW;AAAA,UAC/B,GAAG,KAAK,CAAC,GAAG,UAAU,YAAY;AAAA,QAAA;AAGpCC,sCAAgB,MAAM,aAAa,gBAAgB;AAAA,MACrD;AAAA;AAAA;AAAA;AAAA,MAKA,IAAa,UAA+B;AAC1C,YAAI,YAAY,KAAK,SAAS,KAAK,MAAM,UAAU,MAAM;AACvD,eAAK,eAAe,KAAK,MAAM;AAAA,QACjC;AAEA,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,IAAI,QAAsB;AACxB,YAAI,WAAW,KAAK,OAAO;AACzB,iBAAO,KAAK,MAAM;AAAA,QACpB;AAEA,eAAOC,aAAAA,YAAY,MAAM;AAAA,MAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,IAAI,aAAa;AACf,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA,MAKA,IAAa,YAAY;AACvB,eAAO,MAAM,aAAa,KAAK,MAAM;AAAA,MACvC;AAAA,IAAA;AAGF,WAAO;AAAA,EACT;AACF;AAMA,MAAM,sBAAsB,UAAU,EAAS,EAAEC,2BAAa;AAiBvD,MAAe,uBAEZ,oBAAoB;AAE9B;;;"}
package/view-model.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { computed } from "mobx";
2
- import { ViewModelBase, applyObservable } from "mobx-view-model";
2
+ import { ViewModelBase, viewModelsConfig, applyObservable } from "mobx-view-model";
3
3
  import { r as routeConfig } from "./virtual-route-C3nG6mQb.js";
4
4
  const annotations = [
5
5
  [computed.struct, "pathParams"],
@@ -20,7 +20,11 @@ function withRoute(route) {
20
20
  _lastPayload = {};
21
21
  constructor(...args) {
22
22
  super(...args);
23
- applyObservable(this, annotations, this.vmConfig.observable.viewModels);
23
+ const observableConfig = this.vmConfig?.observable?.viewModels ?? {
24
+ ...viewModelsConfig.observable.viewModels,
25
+ ...args[0]?.vmConfig?.observable?.viewModels
26
+ };
27
+ applyObservable(this, annotations, observableConfig);
24
28
  }
25
29
  /**
26
30
  * Current route params with fallback to the last cached value.
package/view-model.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"view-model.js","sources":["../src/view-model/route-view-model.ts"],"sourcesContent":["import { computed } from 'mobx';\nimport type { IQueryParams } from 'mobx-location-history';\nimport {\n applyObservable,\n type ViewModel,\n ViewModelBase,\n} from 'mobx-view-model';\nimport type { ObservableAnnotationsArray } from 'yummies/mobx';\nimport type { Class } from 'yummies/types';\nimport {\n type AnyAbstractRouteEntity,\n type RouteParams,\n routeConfig,\n} from '../core/index.js';\n\nconst annotations: ObservableAnnotationsArray<any> = [\n [computed.struct, 'pathParams'],\n [computed, 'query'],\n];\n\n/**\n * Public interface added by `withRouteViewModel` mixin.\n */\ntype RouteViewModelMixinPublic<TRoute extends AnyAbstractRouteEntity> = {\n readonly route: TRoute;\n _lastPayload: RouteParams<TRoute>;\n payload: RouteParams<TRoute>;\n query: IQueryParams;\n pathParams: RouteParams<TRoute>;\n isMounted: boolean;\n};\n\n/**\n * Mixin function that enhances any `ViewModelBase` subclass with route-related properties.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel)\n *\n * @param route - Route entity to bind to the view model.\n * @returns A class enhancer that takes a base class and returns a new class with route properties.\n *\n * @example\n * ```ts\n * // With ViewModelBase directly\n * class MyVM extends withRoute(someRoute)(ViewModelBase) {}\n *\n * // With a custom base class\n * class ProductPageVM extends withRoute(productRoute)(AppVM) {}\n * ```\n */\nexport function withRoute<TRoute extends AnyAbstractRouteEntity>(\n route: TRoute,\n) {\n return <TBase extends Class<ViewModel<any, any>>>(\n Base: TBase,\n ): TBase & Class<InstanceType<TBase> & RouteViewModelMixinPublic<TRoute>> => {\n class RouteViewModelMixin extends Base {\n /**\n * Route entity bound to this view model.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel#route)\n */\n readonly route: TRoute = route;\n\n /**\n * Caches the latest known route params.\n */\n _lastPayload: RouteParams<TRoute> = {} as RouteParams<TRoute>;\n\n constructor(...args: any[]) {\n super(...args);\n\n applyObservable(this, annotations, this.vmConfig.observable.viewModels);\n }\n\n /**\n * Current route params with fallback to the last cached value.\n */\n override get payload(): RouteParams<TRoute> {\n if ('params' in this.route && this.route.params != null) {\n this._lastPayload = this.route.params as RouteParams<TRoute>;\n }\n\n return this._lastPayload;\n }\n\n /**\n * Current query params from route or global route config.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel#query)\n */\n get query(): IQueryParams {\n if ('query' in this.route) {\n return this.route.query as IQueryParams;\n }\n\n return routeConfig.get().queryParams;\n }\n\n /**\n * Alias for `payload`.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel#pathparams)\n */\n get pathParams() {\n return this.payload;\n }\n\n /**\n * Mounted state including the route opened status.\n */\n override get isMounted() {\n return super.isMounted && this.route.isOpened;\n }\n }\n\n return RouteViewModelMixin as any;\n };\n}\n\n/**\n * Base class created by the mixin with a dummy route.\n * Used internally by `RouteViewModel` to avoid duplicating the mixin logic.\n */\nconst _RouteViewModelBase = withRoute({} as any)(ViewModelBase);\n\n/**\n * Abstract route-aware view model that extends `ViewModelBase<EmptyObject>`.\n *\n * Provides `route`, `payload`, `pathParams`, `query`, and `isMounted` properties.\n * Subclasses must declare `route = someRoute`.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel)\n *\n * @example\n * ```ts\n * class MyVM extends RouteViewModel<typeof myRoute> {\n * route = myRoute;\n * }\n * ```\n */\nexport abstract class RouteViewModel<\n TRoute extends AnyAbstractRouteEntity = AnyAbstractRouteEntity,\n> extends _RouteViewModelBase {\n abstract override readonly route: TRoute;\n}\n"],"names":[],"mappings":";;;AAeA,MAAM,cAA+C;AAAA,EACnD,CAAC,SAAS,QAAQ,YAAY;AAAA,EAC9B,CAAC,UAAU,OAAO;AACpB;AA+BO,SAAS,UACd,OACA;AACA,SAAO,CACL,SAC2E;AAAA,IAC3E,MAAM,4BAA4B,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAM5B,QAAgB;AAAA;AAAA;AAAA;AAAA,MAKzB,eAAoC,CAAA;AAAA,MAEpC,eAAe,MAAa;AAC1B,cAAM,GAAG,IAAI;AAEb,wBAAgB,MAAM,aAAa,KAAK,SAAS,WAAW,UAAU;AAAA,MACxE;AAAA;AAAA;AAAA;AAAA,MAKA,IAAa,UAA+B;AAC1C,YAAI,YAAY,KAAK,SAAS,KAAK,MAAM,UAAU,MAAM;AACvD,eAAK,eAAe,KAAK,MAAM;AAAA,QACjC;AAEA,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,IAAI,QAAsB;AACxB,YAAI,WAAW,KAAK,OAAO;AACzB,iBAAO,KAAK,MAAM;AAAA,QACpB;AAEA,eAAO,YAAY,MAAM;AAAA,MAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,IAAI,aAAa;AACf,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA,MAKA,IAAa,YAAY;AACvB,eAAO,MAAM,aAAa,KAAK,MAAM;AAAA,MACvC;AAAA,IAAA;AAGF,WAAO;AAAA,EACT;AACF;AAMA,MAAM,sBAAsB,UAAU,EAAS,EAAE,aAAa;AAiBvD,MAAe,uBAEZ,oBAAoB;AAE9B;"}
1
+ {"version":3,"file":"view-model.js","sources":["../src/view-model/route-view-model.ts"],"sourcesContent":["import { computed } from 'mobx';\nimport type { IQueryParams } from 'mobx-location-history';\nimport {\n applyObservable,\n type ViewModel,\n ViewModelBase,\n viewModelsConfig,\n} from 'mobx-view-model';\nimport type { ObservableAnnotationsArray } from 'yummies/mobx';\nimport type { Class } from 'yummies/types';\nimport {\n type AnyAbstractRouteEntity,\n type RouteParams,\n routeConfig,\n} from '../core/index.js';\n\nconst annotations: ObservableAnnotationsArray<any> = [\n [computed.struct, 'pathParams'],\n [computed, 'query'],\n];\n\n/**\n * Public interface added by `withRouteViewModel` mixin.\n */\ntype RouteViewModelMixinPublic<TRoute extends AnyAbstractRouteEntity> = {\n readonly route: TRoute;\n _lastPayload: RouteParams<TRoute>;\n payload: RouteParams<TRoute>;\n query: IQueryParams;\n pathParams: RouteParams<TRoute>;\n isMounted: boolean;\n};\n\n/**\n * Mixin function that enhances any `ViewModelBase` subclass with route-related properties.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel)\n *\n * @param route - Route entity to bind to the view model.\n * @returns A class enhancer that takes a base class and returns a new class with route properties.\n *\n * @example\n * ```ts\n * // With ViewModelBase directly\n * class MyVM extends withRoute(someRoute)(ViewModelBase) {}\n *\n * // With a custom base class\n * class ProductPageVM extends withRoute(productRoute)(AppVM) {}\n * ```\n */\nexport function withRoute<TRoute extends AnyAbstractRouteEntity>(\n route: TRoute,\n) {\n return <TBase extends Class<ViewModel<any, any>>>(\n Base: TBase,\n ): TBase & Class<InstanceType<TBase> & RouteViewModelMixinPublic<TRoute>> => {\n class RouteViewModelMixin extends Base {\n /**\n * Route entity bound to this view model.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel#route)\n */\n readonly route: TRoute = route;\n\n /**\n * Caches the latest known route params.\n */\n _lastPayload: RouteParams<TRoute> = {} as RouteParams<TRoute>;\n\n constructor(...args: any[]) {\n super(...args);\n\n // v11 keeps vmConfig internal; the exported config supplies its defaults.\n const observableConfig = (this as any).vmConfig?.observable\n ?.viewModels ?? {\n ...viewModelsConfig.observable.viewModels,\n ...args[0]?.vmConfig?.observable?.viewModels,\n };\n\n applyObservable(this, annotations, observableConfig);\n }\n\n /**\n * Current route params with fallback to the last cached value.\n */\n override get payload(): RouteParams<TRoute> {\n if ('params' in this.route && this.route.params != null) {\n this._lastPayload = this.route.params as RouteParams<TRoute>;\n }\n\n return this._lastPayload;\n }\n\n /**\n * Current query params from route or global route config.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel#query)\n */\n get query(): IQueryParams {\n if ('query' in this.route) {\n return this.route.query as IQueryParams;\n }\n\n return routeConfig.get().queryParams;\n }\n\n /**\n * Alias for `payload`.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel#pathparams)\n */\n get pathParams() {\n return this.payload;\n }\n\n /**\n * Mounted state including the route opened status.\n */\n override get isMounted() {\n return super.isMounted && this.route.isOpened;\n }\n }\n\n return RouteViewModelMixin as any;\n };\n}\n\n/**\n * Base class created by the mixin with a dummy route.\n * Used internally by `RouteViewModel` to avoid duplicating the mixin logic.\n */\nconst _RouteViewModelBase = withRoute({} as any)(ViewModelBase);\n\n/**\n * Abstract route-aware view model that extends `ViewModelBase<EmptyObject>`.\n *\n * Provides `route`, `payload`, `pathParams`, `query`, and `isMounted` properties.\n * Subclasses must declare `route = someRoute`.\n *\n * [**Documentation**](https://js2me.github.io/mobx-route/view-model/RouteViewModel)\n *\n * @example\n * ```ts\n * class MyVM extends RouteViewModel<typeof myRoute> {\n * route = myRoute;\n * }\n * ```\n */\nexport abstract class RouteViewModel<\n TRoute extends AnyAbstractRouteEntity = AnyAbstractRouteEntity,\n> extends _RouteViewModelBase {\n abstract override readonly route: TRoute;\n}\n"],"names":[],"mappings":";;;AAgBA,MAAM,cAA+C;AAAA,EACnD,CAAC,SAAS,QAAQ,YAAY;AAAA,EAC9B,CAAC,UAAU,OAAO;AACpB;AA+BO,SAAS,UACd,OACA;AACA,SAAO,CACL,SAC2E;AAAA,IAC3E,MAAM,4BAA4B,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAM5B,QAAgB;AAAA;AAAA;AAAA;AAAA,MAKzB,eAAoC,CAAA;AAAA,MAEpC,eAAe,MAAa;AAC1B,cAAM,GAAG,IAAI;AAGb,cAAM,mBAAoB,KAAa,UAAU,YAC7C,cAAc;AAAA,UAChB,GAAG,iBAAiB,WAAW;AAAA,UAC/B,GAAG,KAAK,CAAC,GAAG,UAAU,YAAY;AAAA,QAAA;AAGpC,wBAAgB,MAAM,aAAa,gBAAgB;AAAA,MACrD;AAAA;AAAA;AAAA;AAAA,MAKA,IAAa,UAA+B;AAC1C,YAAI,YAAY,KAAK,SAAS,KAAK,MAAM,UAAU,MAAM;AACvD,eAAK,eAAe,KAAK,MAAM;AAAA,QACjC;AAEA,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,IAAI,QAAsB;AACxB,YAAI,WAAW,KAAK,OAAO;AACzB,iBAAO,KAAK,MAAM;AAAA,QACpB;AAEA,eAAO,YAAY,MAAM;AAAA,MAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,IAAI,aAAa;AACf,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA,MAKA,IAAa,YAAY;AACvB,eAAO,MAAM,aAAa,KAAK,MAAM;AAAA,MACvC;AAAA,IAAA;AAGF,WAAO;AAAA,EACT;AACF;AAMA,MAAM,sBAAsB,UAAU,EAAS,EAAE,aAAa;AAiBvD,MAAe,uBAEZ,oBAAoB;AAE9B;"}