@octanejs/tanstack-router 0.1.30 → 0.1.32

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": "@octanejs/tanstack-router",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -58,7 +58,7 @@
58
58
  "isbot": "^5.1.22"
59
59
  },
60
60
  "peerDependencies": {
61
- "octane": "0.1.31"
61
+ "octane": "0.1.33"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@tanstack/react-router": "1.170.18",
@@ -67,7 +67,7 @@
67
67
  "react": "^19.2.7",
68
68
  "react-dom": "^19.2.7",
69
69
  "vitest": "^4.1.10",
70
- "octane": "0.1.31"
70
+ "octane": "0.1.33"
71
71
  },
72
72
  "scripts": {
73
73
  "test": "vitest run"
package/src/Asset.tsrx CHANGED
@@ -10,6 +10,8 @@ export type AssetProps =
10
10
  target: 'head' | 'body';
11
11
  };
12
12
 
13
+ type ManagedAssetProps = AssetProps & { managedKey?: string };
14
+
13
15
  const MANAGED_KEY_ATTR = 'data-tsr-managed-key';
14
16
 
15
17
  function attributeName(name: string) {
@@ -51,16 +53,17 @@ function findManagedElement(parent: HTMLElement, key: string) {
51
53
  return undefined;
52
54
  }
53
55
 
54
- function mountAsset(props: AssetProps) {
56
+ function mountAsset(props: ManagedAssetProps) {
55
57
  const parent =
56
58
  props.target === 'head' ? document.head : document.body;
57
- const existing = findManagedElement(parent, props.assetKey);
59
+ const managedKey = props.managedKey ?? props.assetKey;
60
+ const existing = findManagedElement(parent, managedKey);
58
61
  if (existing) {
59
62
  return () => existing.remove();
60
63
  }
61
64
 
62
65
  const element = document.createElement(props.tag);
63
- element.setAttribute(MANAGED_KEY_ATTR, props.assetKey);
66
+ element.setAttribute(MANAGED_KEY_ATTR, managedKey);
64
67
  setAttributes(element, props.attrs);
65
68
  if (typeof props.children === 'string') {
66
69
  element.textContent = props.children;
@@ -72,10 +75,12 @@ function mountAsset(props: AssetProps) {
72
75
  export function Asset(props: AssetProps) @{
73
76
  const router = useRouter();
74
77
  const hydrated = useHydrated();
78
+ const managedKey = (props as ManagedAssetProps).managedKey ?? props.assetKey;
75
79
 
76
80
  if (!(isServer ?? router.isServer)) {
77
81
  useLayoutEffect(() => mountAsset(props), [
78
82
  props.assetKey,
83
+ managedKey,
79
84
  props.attrs,
80
85
  props.children,
81
86
  props.tag,
@@ -83,38 +88,37 @@ export function Asset(props: AssetProps) @{
83
88
  ]);
84
89
  }
85
90
 
86
- @if ((isServer ?? router.isServer) || props.target === 'body' && !hydrated) {
87
- @switch (props.tag) {
88
- @case 'title': {
89
- <title {...props.attrs} data-tsr-managed-key={props.assetKey}>
90
- {props.children ?? '' as string}
91
- </title>
92
- }
93
- @case 'meta': {
94
- <meta {...props.attrs} data-tsr-managed-key={props.assetKey} />
95
- }
96
- @case 'link': {
97
- <link {...props.attrs} data-tsr-managed-key={props.assetKey} />
98
- }
99
- @case 'style': {
100
- {createElement('style', {
101
- ...props.attrs,
102
- 'data-tsr-managed-key': props.assetKey,
103
- dangerouslySetInnerHTML: { __html: props.children ?? '' },
104
- })}
105
- }
106
- @case 'script': {
107
- <script
108
- {...props.attrs}
109
- data-tsr-managed-key={props.assetKey}
110
- dangerouslySetInnerHTML={{ __html: props.children ?? '' }}
111
- />
112
- }
113
- @default: {
114
- <></>
115
- }
91
+ const visibleTag =
92
+ (isServer ?? router.isServer) || props.target === 'body' && !hydrated ? props.tag : undefined;
93
+
94
+ @switch (visibleTag) {
95
+ @case 'title': {
96
+ <title {...props.attrs} data-tsr-managed-key={managedKey}>
97
+ {props.children ?? '' as string}
98
+ </title>
99
+ }
100
+ @case 'meta': {
101
+ <meta {...props.attrs} data-tsr-managed-key={managedKey} />
102
+ }
103
+ @case 'link': {
104
+ <link {...props.attrs} data-tsr-managed-key={managedKey} />
105
+ }
106
+ @case 'style': {
107
+ {createElement('style', {
108
+ ...props.attrs,
109
+ 'data-tsr-managed-key': managedKey,
110
+ dangerouslySetInnerHTML: { __html: props.children ?? '' },
111
+ })}
112
+ }
113
+ @case 'script': {
114
+ <script
115
+ {...props.attrs}
116
+ data-tsr-managed-key={managedKey}
117
+ dangerouslySetInnerHTML={{ __html: props.children ?? '' }}
118
+ />
119
+ }
120
+ @default: {
121
+ <></>
116
122
  }
117
- } @else {
118
- <></>
119
123
  }
120
124
  }
@@ -1,9 +1,9 @@
1
- import { createElement, useEffect } from 'octane';
1
+ import { createElement, useEffect, useId } from 'octane';
2
2
  import { DEV_STYLES_ATTR } from '@tanstack/router-core';
3
3
  import type { AssetCrossOriginConfig } from '@tanstack/router-core';
4
4
  import { Asset } from './Asset.tsrx';
5
5
  import type { AssetProps } from './Asset.tsrx';
6
- import { getAssetKey } from './assetKeys.ts';
6
+ import { getAssetKey, getManagedAssetKey } from './assetKeys.ts';
7
7
  import { useTags } from './headContentUtils.ts';
8
8
  import { useHydrated } from './ClientOnly.tsrx';
9
9
 
@@ -12,6 +12,7 @@ export interface HeadContentProps {
12
12
  }
13
13
 
14
14
  export function HeadContent(props: HeadContentProps) @{
15
+ const owner = useId();
15
16
  const tags = useTags(props.assetCrossOrigin);
16
17
  const hydrated = useHydrated();
17
18
 
@@ -27,15 +28,17 @@ export function HeadContent(props: HeadContentProps) @{
27
28
  ? tags.filter((tag) => tag.tag !== 'link' || tag.attrs?.[DEV_STYLES_ATTR] !== true)
28
29
  : tags;
29
30
  <>
30
- {filteredTags.map(
31
- (tag, index) => // `key` is lifted out of props by createElement (React semantics); the
31
+ {filteredTags.map((tag, index) => {
32
+ const assetKey = getAssetKey('head', tag, index);
33
+ // `key` is lifted out of props by createElement (React semantics); the
32
34
  // explicit type argument admits it alongside the AssetProps shape.
33
- createElement<AssetProps & { key?: string }>(Asset, {
35
+ return createElement<AssetProps & { key?: string; managedKey?: string }>(Asset, {
34
36
  ...tag,
35
- assetKey: getAssetKey('head', tag, index),
37
+ assetKey,
38
+ managedKey: getManagedAssetKey('head', owner, index),
36
39
  target: 'head',
37
- key: getAssetKey('head', tag, index),
38
- }),
39
- )}
40
+ key: assetKey,
41
+ });
42
+ })}
40
43
  </>
41
44
  }
package/src/Scripts.tsrx CHANGED
@@ -1,11 +1,12 @@
1
- import { createElement, useEffect, useState } from 'octane';
1
+ import { createElement, useEffect, useId, useState } from 'octane';
2
2
  import { isServer } from '@tanstack/router-core/isServer';
3
3
  import type { RouterManagedTag } from '@tanstack/router-core';
4
- import { getAssetKey } from './assetKeys.ts';
4
+ import { getAssetKey, getManagedAssetKey } from './assetKeys.ts';
5
5
  import { useRouter } from './context.ts';
6
6
  import { useScripts } from './scriptContentUtils.ts';
7
7
 
8
8
  export function Scripts() @{
9
+ const owner = useId();
9
10
  const router = useRouter();
10
11
  const scripts = useScripts();
11
12
  const [hydrating, setHydrating] = useState(true);
@@ -34,7 +35,7 @@ export function Scripts() @{
34
35
  return createElement('script', {
35
36
  ...script.attrs,
36
37
  key: assetKey,
37
- 'data-tsr-managed-key': assetKey,
38
+ 'data-tsr-managed-key': getManagedAssetKey('body', owner, index),
38
39
  dangerouslySetInnerHTML: { __html: script.children ?? '' },
39
40
  });
40
41
  })}
package/src/assetKeys.ts CHANGED
@@ -9,3 +9,7 @@ export function getAssetKey(scope: 'head' | 'body', asset: RouterManagedTag, ind
9
9
  inlineCss,
10
10
  })}`;
11
11
  }
12
+
13
+ export function getManagedAssetKey(scope: 'head' | 'body', owner: string, index: number) {
14
+ return `${scope}:${owner}:${index.toString(36)}`;
15
+ }
package/src/useStore.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  // `useSyncExternalStore`. The atoms come from router-core's client store factory
7
7
  // (`createAtom` from `@tanstack/store`): `.subscribe(cb) → { unsubscribe }` + `.get()`.
8
8
  import { useSyncExternalStore, useCallback, useRef } from 'octane';
9
+ import { isServer } from '@tanstack/router-core/isServer';
9
10
  import { subSlot, splitSlot } from './internal';
10
11
 
11
12
  interface Atom<T> {
@@ -25,6 +26,13 @@ export function useStore<T, S = T>(
25
26
  slot?: symbol,
26
27
  ): S;
27
28
  export function useStore(...args: any[]): any {
29
+ const rawAtom = args[0] as Atom<unknown>;
30
+ if (isServer && typeof rawAtom.subscribe !== 'function') {
31
+ const value = rawAtom.get();
32
+ const selector = args[1];
33
+ return typeof selector === 'function' ? selector(value) : value;
34
+ }
35
+
28
36
  const [user, slot] = splitSlot(args);
29
37
  const atom = user[0] as Atom<unknown>;
30
38
  const selector = (user[1] ?? ((s: unknown) => s)) as (s: unknown) => unknown;