@nordcraft/runtime 1.0.50 → 1.0.52

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.
@@ -39,7 +39,7 @@ function getFormulaCacheConfig(formula, component) {
39
39
  if (!op) {
40
40
  return;
41
41
  }
42
- if (op.type == 'path' && op.path[0] !== 'Args') {
42
+ if (op.type === 'path' && op.path[0] !== 'Args') {
43
43
  paths.push(op.path);
44
44
  }
45
45
  if (Array.isArray(op?.arguments)) {
@@ -1 +1 @@
1
- {"version":3,"file":"createFormulaCache.js","sourceRoot":"","sources":["../../src/utils/createFormulaCache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"createFormulaCache.js","sourceRoot":"","sources":["../../src/utils/createFormulaCache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const storeScrollState: (key?: string, querySelector?: string, comparerFn?: (node: Element) => string | null) => (selectorFn: (id: string) => HTMLElement | null) => void;
2
+ export declare const getScrollStateRestorer: (key: string) => (selectorFn: (id: string) => HTMLElement | null) => void;
@@ -0,0 +1,39 @@
1
+ export const storeScrollState = (key = '', querySelector = '[data-id]', comparerFn = (node) => node.getAttribute('data-id')) => {
2
+ const scrollPositions = {};
3
+ Array.from(document.querySelectorAll(querySelector)).forEach((node) => {
4
+ const nodeId = comparerFn(node);
5
+ if (nodeId && (node.scrollTop || node.scrollLeft)) {
6
+ scrollPositions[nodeId] = {
7
+ y: node.scrollTop,
8
+ x: node.scrollLeft,
9
+ };
10
+ }
11
+ });
12
+ // Always store window scroll position as well
13
+ scrollPositions['__window'] = {
14
+ y: window.scrollY,
15
+ x: window.scrollX,
16
+ };
17
+ sessionStorage.setItem(`scroll-position(${key})`, JSON.stringify(scrollPositions));
18
+ return getScrollStateRestorer(key);
19
+ };
20
+ export const getScrollStateRestorer = (key) => (selectorFn) => {
21
+ const { __window, ...rest } = JSON.parse(sessionStorage.getItem(`scroll-position(${key})`) ?? '{}');
22
+ if (!__window) {
23
+ return;
24
+ }
25
+ Object.entries(rest).forEach(([nodeId, scrollPosition]) => {
26
+ const domNode = selectorFn(nodeId);
27
+ if (!domNode) {
28
+ return;
29
+ }
30
+ if (scrollPosition?.y) {
31
+ domNode.scrollTop = scrollPosition.y;
32
+ }
33
+ if (scrollPosition?.x) {
34
+ domNode.scrollLeft = scrollPosition.x;
35
+ }
36
+ });
37
+ window.scrollTo(__window.x, __window.y);
38
+ };
39
+ //# sourceMappingURL=storeScrollState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storeScrollState.js","sourceRoot":"","sources":["../../src/utils/storeScrollState.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,MAAc,EAAE,EAChB,aAAa,GAAG,WAAW,EAC3B,aAAa,CAAC,IAAa,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,EAC5D,EAAE;IACF,MAAM,eAAe,GAAoB,EAAE,CAAA;IAC3C,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAc,aAAa,CAAC,CAAC,CAAC,OAAO,CACvE,CAAC,IAAI,EAAE,EAAE;QACP,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;QAC/B,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAClD,eAAe,CAAC,MAAM,CAAC,GAAG;gBACxB,CAAC,EAAE,IAAI,CAAC,SAAS;gBACjB,CAAC,EAAE,IAAI,CAAC,UAAU;aACnB,CAAA;QACH,CAAC;IACH,CAAC,CACF,CAAA;IAED,8CAA8C;IAC9C,eAAe,CAAC,UAAU,CAAC,GAAG;QAC5B,CAAC,EAAE,MAAM,CAAC,OAAO;QACjB,CAAC,EAAE,MAAM,CAAC,OAAO;KAClB,CAAA;IAED,cAAc,CAAC,OAAO,CACpB,mBAAmB,GAAG,GAAG,EACzB,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,CAChC,CAAA;IAED,OAAO,sBAAsB,CAAC,GAAG,CAAC,CAAA;AACpC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GACjC,CAAC,GAAW,EAAE,EAAE,CAAC,CAAC,UAA8C,EAAE,EAAE;IAClE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK,CACtC,cAAc,CAAC,OAAO,CAAC,mBAAmB,GAAG,GAAG,CAAC,IAAI,IAAI,CACvC,CAAA;IACpB,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAM;IACR,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,EAAE;QACxD,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAA;QAClC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAM;QACR,CAAC;QAED,IAAI,cAAc,EAAE,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,SAAS,GAAG,cAAc,CAAC,CAAC,CAAA;QACtC,CAAC;QACD,IAAI,cAAc,EAAE,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,UAAU,GAAG,cAAc,CAAC,CAAC,CAAA;QACvC,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAA;AACzC,CAAC,CAAA"}
package/package.json CHANGED
@@ -4,8 +4,8 @@
4
4
  "type": "module",
5
5
  "homepage": "https://github.com/nordcraftengine/nordcraft",
6
6
  "dependencies": {
7
- "@nordcraft/core": "1.0.50",
8
- "@nordcraft/std-lib": "1.0.50",
7
+ "@nordcraft/core": "1.0.52",
8
+ "@nordcraft/std-lib": "1.0.52",
9
9
  "fast-deep-equal": "3.1.3",
10
10
  "path-to-regexp": "6.3.0"
11
11
  },
@@ -21,5 +21,5 @@
21
21
  "files": ["dist", "src"],
22
22
  "main": "dist/page.main.js",
23
23
  "types": "dist/page.main.d.ts",
24
- "version": "1.0.50"
24
+ "version": "1.0.52"
25
25
  }
@@ -25,7 +25,7 @@ export function subscribeToContext(
25
25
  if (!formulaDataSignal) {
26
26
  // eslint-disable-next-line no-console
27
27
  console.warn(
28
- `Provider ${providerName} does not expose a formula named "${formulaName}". Available formulas are: ["${Object.keys(
28
+ `Component error(${component.name}): Provider ${providerName} does not expose a formula named "${formulaName}". Available formulas are: ["${Object.keys(
29
29
  provider.formulaDataSignals,
30
30
  ).join('", "')}"]`,
31
31
  )
@@ -63,7 +63,7 @@ export function subscribeToContext(
63
63
  if (!testProvider) {
64
64
  // eslint-disable-next-line no-console
65
65
  console.error(
66
- `Could not find provider "${providerName}". No such component exist.`,
66
+ `Component error(${component.name}): Could not find provider "${providerName}". No such component exist.`,
67
67
  )
68
68
  return
69
69
  }
@@ -116,7 +116,7 @@ export function subscribeToContext(
116
116
  if (!formula) {
117
117
  // eslint-disable-next-line no-console
118
118
  console.warn(
119
- `Could not find formula "${formulaName}" in component "${providerName}"`,
119
+ `Component error(${component.name}): Could not find formula "${formulaName}" in provider "${providerName}"`,
120
120
  )
121
121
  return [formulaName, null]
122
122
  }
@@ -31,11 +31,11 @@ export class ToddleComponent extends HTMLElement {
31
31
  #ctx: ComponentContext
32
32
  #shadowRoot: ShadowRoot
33
33
  #signal: Signal<ComponentData>
34
- #files: { themes: Theme[] }
34
+ #files: { themes: Record<string, Theme> }
35
35
 
36
36
  constructor(
37
37
  component: Component,
38
- options: { components: Component[]; themes: Theme[] },
38
+ options: { components: Component[]; themes: Record<string, Theme> },
39
39
  toddle: Toddle<LocationSignal, never>,
40
40
  ) {
41
41
  super()
@@ -175,7 +175,9 @@ export class ToddleComponent extends HTMLElement {
175
175
  const styles = createStylesheet(
176
176
  this.#ctx.component,
177
177
  this.#ctx.components,
178
- this.#files.themes ? Object.values(this.#files.themes)[0] : defaultTheme,
178
+ Object.entries(this.#files.themes ?? {}).length > 0
179
+ ? this.#files.themes
180
+ : { defaultTheme },
179
181
  { includeResetStyle: true, createFontFaces: false },
180
182
  )
181
183
  const stylesElem = document.createElement('style')
@@ -27,7 +27,7 @@ export const defineComponents = (
27
27
  componentNames: string[],
28
28
  options: {
29
29
  components: Component[]
30
- themes: Theme[]
30
+ themes: Record<string, Theme>
31
31
  },
32
32
  toddle: Toddle<LocationSignal, never>,
33
33
  ) => {
@@ -3,12 +3,13 @@
3
3
  /* eslint-disable no-case-declarations */
4
4
  /* eslint-disable no-fallthrough */
5
5
  import { isLegacyApi } from '@nordcraft/core/dist/api/api'
6
- import type {
7
- AnimationKeyframe,
8
- Component,
9
- ComponentData,
10
- MetaEntry,
11
- StyleVariant,
6
+ import {
7
+ HeadTagTypes,
8
+ type AnimationKeyframe,
9
+ type Component,
10
+ type ComponentData,
11
+ type MetaEntry,
12
+ type StyleVariant,
12
13
  } from '@nordcraft/core/dist/component/component.types'
13
14
  import { isPageComponent } from '@nordcraft/core/dist/component/isPageComponent'
14
15
  import type {
@@ -25,8 +26,7 @@ import {
25
26
  import { valueFormula } from '@nordcraft/core/dist/formula/formulaUtils'
26
27
  import { getClassName } from '@nordcraft/core/dist/styling/className'
27
28
  import type { OldTheme, Theme } from '@nordcraft/core/dist/styling/theme'
28
- import { getThemeCss } from '@nordcraft/core/dist/styling/theme'
29
- import { theme } from '@nordcraft/core/dist/styling/theme.const'
29
+ import { getThemeCss, renderTheme } from '@nordcraft/core/dist/styling/theme'
30
30
  import type {
31
31
  ActionHandler,
32
32
  ActionHandlerV2,
@@ -68,6 +68,10 @@ import { createFormulaCache } from './utils/createFormulaCache'
68
68
  import { getNodeAndAncestors, isNodeOrAncestorConditional } from './utils/nodes'
69
69
  import { omitSubnodeStyleForComponent } from './utils/omitStyle'
70
70
  import { rectHasPoint } from './utils/rectHasPoint'
71
+ import {
72
+ getScrollStateRestorer,
73
+ storeScrollState,
74
+ } from './utils/storeScrollState'
71
75
 
72
76
  type ToddlePreviewEvent =
73
77
  | {
@@ -109,7 +113,7 @@ type ToddlePreviewEvent =
109
113
  type: 'global_actions'
110
114
  actions: Record<string, PluginActionV2 | PluginAction>
111
115
  }
112
- | { type: 'theme'; theme: Theme | OldTheme }
116
+ | { type: 'theme'; theme: Record<string, OldTheme | Theme> }
113
117
  | { type: 'mode'; mode: 'design' | 'test' }
114
118
  | { type: 'attrs'; attrs: Record<string, unknown> }
115
119
  | { type: 'selection'; selectedNodeId: string | null }
@@ -152,7 +156,18 @@ type ToddlePreviewEvent =
152
156
  | undefined
153
157
  fillMode: 'none' | 'forwards' | 'backwards' | 'both' | undefined
154
158
  }
155
- | { type: 'preview_style'; styles: Record<string, string> | null }
159
+ | {
160
+ type: 'preview_style'
161
+ styles: Record<string, string> | null
162
+ theme?: {
163
+ key: string
164
+ value: Theme
165
+ }
166
+ }
167
+ | {
168
+ type: 'preview_theme'
169
+ theme: string | null
170
+ }
156
171
 
157
172
  /**
158
173
  * Styles required for rendering the same exact text again somewhere else (on a overlay rect in the editor)
@@ -305,7 +320,6 @@ export const createRoot = (
305
320
  return false
306
321
  }
307
322
 
308
- insertTheme(document.head, theme)
309
323
  const dataSignal = signal<ComponentData>({
310
324
  Location: {
311
325
  query: {},
@@ -445,8 +459,16 @@ export const createRoot = (
445
459
  if (!message.data.component) {
446
460
  return
447
461
  }
448
- if (message.data.component.name != component?.name) {
462
+ let scrollStateRestorer:
463
+ | ReturnType<typeof getScrollStateRestorer>
464
+ | undefined
465
+
466
+ if (message.data.component.name !== component?.name) {
467
+ storeScrollState(component?.name)
449
468
  showSignal.cleanSubscribers()
469
+ scrollStateRestorer = getScrollStateRestorer(
470
+ message.data.component.name,
471
+ )
450
472
  }
451
473
 
452
474
  component = updateComponentLinks(message.data.component)
@@ -493,6 +515,12 @@ export const createRoot = (
493
515
  }
494
516
  }
495
517
 
518
+ requestAnimationFrame(() => {
519
+ scrollStateRestorer?.((nodeId) =>
520
+ document.querySelector(`[data-id="${nodeId}"]`),
521
+ )
522
+ })
523
+
496
524
  break
497
525
  }
498
526
  case 'components': {
@@ -909,6 +937,9 @@ export const createRoot = (
909
937
  })
910
938
  }
911
939
  break
940
+ case undefined:
941
+ // TODO: Handle the case where the drag state is undefined
942
+ break
912
943
  }
913
944
  break
914
945
  case 'keydown':
@@ -1051,7 +1082,7 @@ export const createRoot = (
1051
1082
  })
1052
1083
  break
1053
1084
  case 'preview_style':
1054
- const { styles: previewStyleStyles } = message.data
1085
+ const { styles: previewStyleStyles, theme } = message.data
1055
1086
  cancelAnimationFrame(previewStyleAnimationFrame)
1056
1087
  previewStyleAnimationFrame = requestAnimationFrame(() => {
1057
1088
  // Update or create a new style tag and set the given styles with important priority
@@ -1091,19 +1122,70 @@ export const createRoot = (
1091
1122
  }
1092
1123
  }
1093
1124
 
1094
- const previewStyles = Object.entries(previewStyleStyles)
1095
- .map(([key, value]) => `${key}: ${value} !important;`)
1096
- .join('\n')
1097
- styleTag.innerHTML = `[data-id="${selectedNodeId}"]${pseudoElement}, [data-id="${selectedNodeId}"] ~ [data-id^="${selectedNodeId}("]${pseudoElement} {
1125
+ // If theme property preview, then override happens at root level and with reasonable specificity.
1126
+ // Otherwise, force (!important) the style directly on the element.
1127
+ if (theme) {
1128
+ theme.value.propertyDefinitions = Object.fromEntries(
1129
+ Object.entries(theme.value.propertyDefinitions ?? {})
1130
+ .filter(([key]) => previewStyleStyles[key])
1131
+ .map(([key, val]) => [
1132
+ key,
1133
+ { ...val, value: previewStyleStyles[key] },
1134
+ ]),
1135
+ )
1136
+ const cssBlocks: string[] = []
1137
+ if (theme.value.default) {
1138
+ cssBlocks.push(renderTheme(`:host, :root`, theme.value))
1139
+ }
1140
+ if (theme.value.defaultDark) {
1141
+ cssBlocks.push(
1142
+ renderTheme(
1143
+ `:host, :root`,
1144
+ theme.value,
1145
+ '@media (prefers-color-scheme: dark)',
1146
+ ),
1147
+ )
1148
+ }
1149
+ if (theme.value.defaultLight) {
1150
+ cssBlocks.push(
1151
+ renderTheme(
1152
+ `:host, :root`,
1153
+ theme.value,
1154
+ '@media (prefers-color-scheme: light)',
1155
+ ),
1156
+ )
1157
+ }
1158
+ cssBlocks.push(
1159
+ renderTheme(`[data-theme~="${theme.key}"]`, theme.value),
1160
+ )
1161
+ styleTag.innerHTML = cssBlocks.join('\n')
1162
+ } else {
1163
+ const previewStyles = Object.entries(previewStyleStyles)
1164
+ .map(([key, value]) => `${key}: ${value} !important;`)
1165
+ .join('\n')
1166
+ styleTag.innerHTML = `[data-id="${selectedNodeId}"]${pseudoElement}, [data-id="${selectedNodeId}"] ~ [data-id^="${selectedNodeId}("]${pseudoElement} {
1098
1167
  ${previewStyles}
1099
1168
  transition: none !important;
1100
1169
  }`
1170
+ }
1101
1171
  })
1102
1172
  break
1173
+ case 'preview_theme': {
1174
+ const { theme } = message.data
1175
+ if (theme) {
1176
+ document.body.setAttribute('data-theme', theme)
1177
+ } else {
1178
+ document.body.removeAttribute('data-theme')
1179
+ }
1180
+ }
1103
1181
  }
1104
1182
  },
1105
1183
  )
1106
1184
 
1185
+ window.addEventListener('beforeunload', () => {
1186
+ storeScrollState(component?.name)
1187
+ })
1188
+
1107
1189
  const updateStyle = () => {
1108
1190
  if (component) {
1109
1191
  insertStyles(document.head, component, getAllComponents())
@@ -1225,6 +1307,7 @@ export const createRoot = (
1225
1307
  return
1226
1308
  }
1227
1309
 
1310
+ const scrollStateRestorer = storeScrollState()
1228
1311
  let { Attributes, Variables, Contexts } = dataSignal.get()
1229
1312
  if (
1230
1313
  fastDeepEqual(ctx?.component.attributes, _component.attributes) === false
@@ -1588,6 +1671,9 @@ export const createRoot = (
1588
1671
  }
1589
1672
 
1590
1673
  ctx = newCtx
1674
+ scrollStateRestorer((nodeId) =>
1675
+ document.querySelector(`[data-id="${nodeId}"]`),
1676
+ )
1591
1677
  }
1592
1678
 
1593
1679
  const createContext = (
@@ -1819,7 +1905,7 @@ const insertHeadTags = (
1819
1905
  // Skip anything that is not <link> or <script> tags, as they don't have any influence on the preview
1820
1906
  Object.entries(entries).forEach(([id, entry]) => {
1821
1907
  switch (entry.tag) {
1822
- case 'link':
1908
+ case HeadTagTypes.Link:
1823
1909
  return insertOrReplaceHeadNode(
1824
1910
  id,
1825
1911
  document.createRange().createContextualFragment(`
@@ -1831,7 +1917,7 @@ const insertHeadTags = (
1831
1917
  />
1832
1918
  `),
1833
1919
  )
1834
- case 'script':
1920
+ case HeadTagTypes.Script:
1835
1921
  return insertOrReplaceHeadNode(
1836
1922
  id,
1837
1923
  document.createRange().createContextualFragment(`
@@ -1843,6 +1929,9 @@ const insertHeadTags = (
1843
1929
  ></script>
1844
1930
  `),
1845
1931
  )
1932
+ default:
1933
+ // TODO: handle style meta tags?
1934
+ break
1846
1935
  }
1847
1936
  })
1848
1937
  }
@@ -1882,12 +1971,15 @@ function getNodeId(component: Component, path: string[]) {
1882
1971
  return getId(path, 'root')
1883
1972
  }
1884
1973
 
1885
- const insertTheme = (parent: HTMLElement, theme: Theme | OldTheme) => {
1974
+ const insertTheme = (
1975
+ parent: HTMLElement,
1976
+ themes: Record<string, OldTheme | Theme>,
1977
+ ) => {
1886
1978
  document.getElementById('theme-style')?.remove()
1887
1979
  const styleElem = document.createElement('style')
1888
1980
  styleElem.setAttribute('type', 'text/css')
1889
1981
  styleElem.setAttribute('id', 'theme-style')
1890
- styleElem.innerHTML = getThemeCss(theme, {
1982
+ styleElem.innerHTML = getThemeCss(themes, {
1891
1983
  includeResetStyle: false,
1892
1984
  createFontFaces: true,
1893
1985
  })
@@ -442,18 +442,21 @@ export function handleAction(
442
442
  const args = (action.arguments ?? []).reduce<
443
443
  Record<string, unknown>
444
444
  >(
445
- (args, arg) => ({
446
- ...args,
447
- [arg.name]: applyFormula(arg.formula, {
448
- data,
449
- component: ctx.component,
450
- formulaCache: ctx.formulaCache,
451
- root: ctx.root,
452
- package: ctx.package,
453
- toddle: ctx.toddle,
454
- env: ctx.env,
455
- }),
456
- }),
445
+ (args, arg) =>
446
+ arg
447
+ ? {
448
+ ...args,
449
+ [arg.name]: applyFormula(arg.formula, {
450
+ data,
451
+ component: ctx.component,
452
+ formulaCache: ctx.formulaCache,
453
+ root: ctx.root,
454
+ package: ctx.package,
455
+ toddle: ctx.toddle,
456
+ env: ctx.env,
457
+ }),
458
+ }
459
+ : args,
457
460
  {},
458
461
  )
459
462
  const result = newAction.handler?.(
@@ -496,7 +499,7 @@ export function handleAction(
496
499
  }
497
500
  // First evaluate any arguments (input) to the action
498
501
  const args = action.arguments?.map((arg) =>
499
- applyFormula(arg.formula, {
502
+ applyFormula(arg?.formula, {
500
503
  data,
501
504
  component: ctx.component,
502
505
  formulaCache: ctx.formulaCache,
@@ -53,7 +53,7 @@ function getFormulaCacheConfig(formula: Formula, component: Component) {
53
53
  if (!op) {
54
54
  return
55
55
  }
56
- if (op.type == 'path' && op.path[0] !== 'Args') {
56
+ if (op.type === 'path' && op.path[0] !== 'Args') {
57
57
  paths.push(op.path)
58
58
  }
59
59
  if (Array.isArray((op as any)?.arguments)) {
@@ -0,0 +1,66 @@
1
+ interface ScrollPosition {
2
+ x: number
3
+ y: number
4
+ }
5
+
6
+ type ScrollPositionKey = '__window' | string
7
+
8
+ type ScrollPositions = Partial<Record<ScrollPositionKey, ScrollPosition>>
9
+
10
+ export const storeScrollState = (
11
+ key: string = '',
12
+ querySelector = '[data-id]',
13
+ comparerFn = (node: Element) => node.getAttribute('data-id'),
14
+ ) => {
15
+ const scrollPositions: ScrollPositions = {}
16
+ Array.from(document.querySelectorAll<HTMLElement>(querySelector)).forEach(
17
+ (node) => {
18
+ const nodeId = comparerFn(node)
19
+ if (nodeId && (node.scrollTop || node.scrollLeft)) {
20
+ scrollPositions[nodeId] = {
21
+ y: node.scrollTop,
22
+ x: node.scrollLeft,
23
+ }
24
+ }
25
+ },
26
+ )
27
+
28
+ // Always store window scroll position as well
29
+ scrollPositions['__window'] = {
30
+ y: window.scrollY,
31
+ x: window.scrollX,
32
+ }
33
+
34
+ sessionStorage.setItem(
35
+ `scroll-position(${key})`,
36
+ JSON.stringify(scrollPositions),
37
+ )
38
+
39
+ return getScrollStateRestorer(key)
40
+ }
41
+
42
+ export const getScrollStateRestorer =
43
+ (key: string) => (selectorFn: (id: string) => HTMLElement | null) => {
44
+ const { __window, ...rest } = JSON.parse(
45
+ sessionStorage.getItem(`scroll-position(${key})`) ?? '{}',
46
+ ) as ScrollPositions
47
+ if (!__window) {
48
+ return
49
+ }
50
+
51
+ Object.entries(rest).forEach(([nodeId, scrollPosition]) => {
52
+ const domNode = selectorFn(nodeId)
53
+ if (!domNode) {
54
+ return
55
+ }
56
+
57
+ if (scrollPosition?.y) {
58
+ domNode.scrollTop = scrollPosition.y
59
+ }
60
+ if (scrollPosition?.x) {
61
+ domNode.scrollLeft = scrollPosition.x
62
+ }
63
+ })
64
+
65
+ window.scrollTo(__window.x, __window.y)
66
+ }