round-core 0.1.8 → 0.2.0

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/README.md CHANGED
@@ -115,6 +115,80 @@ export default function App() {
115
115
  }
116
116
  ```
117
117
 
118
+ ## Markdown rendering with `@round-core/markdown`
119
+
120
+ Round includes an optional companion package for markdown rendering with
121
+ syntax highlighting and rich code blocks.
122
+
123
+ Install it alongside `round-core`:
124
+
125
+ ```bash
126
+ npm install @round-core/markdown
127
+ ```
128
+
129
+ or with Bun:
130
+
131
+ ```bash
132
+ bun add @round-core/markdown
133
+ ```
134
+
135
+ Then use it in a `.round` file or JSX component:
136
+
137
+ ```jsx
138
+ import { createElement } from 'round-core';
139
+ import { Markdown, defaultMarkdownStyles as markdown } from '@round-core/markdown';
140
+ import '@round-core/markdown/styles.css';
141
+
142
+ export default function App() {
143
+ const content = `
144
+ # Markdown with code blocks
145
+
146
+ You can render fenced code blocks with syntax highlighting:
147
+
148
+ \`\`\`javascript
149
+ const value = signal(0);
150
+ \`\`\`
151
+ `;
152
+
153
+ return (
154
+ <div>
155
+ <Markdown content={content}/>
156
+ </div>
157
+ );
158
+ }
159
+ ```
160
+
161
+ You can also theme the markdown surface (background/text) and accents via
162
+ `options.theme`:
163
+
164
+ ```jsx
165
+ <Markdown
166
+ content={content}
167
+ options={{
168
+ theme: {
169
+ // Dark card-like surface
170
+ markdownBackground: '#020617',
171
+ markdownText: '#e5e7eb',
172
+ primaryColor: '#e5e7eb',
173
+ secondaryColor: '#38bdf8',
174
+ },
175
+ }}
176
+ />
177
+
178
+ <Markdown
179
+ content={content}
180
+ options={{
181
+ theme: {
182
+ // Light mode
183
+ markdownBackground: '#ffffff',
184
+ markdownText: '#0f172a',
185
+ primaryColor: '#0f172a',
186
+ secondaryColor: '#2563eb',
187
+ },
188
+ }}
189
+ />
190
+ ```
191
+
118
192
  ## Core API & Examples
119
193
 
120
194
  ### `signal(initialValue)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "round-core",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "A lightweight frontend framework for SPA with signals and fine grained reactivity",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
package/src/cli.js CHANGED
@@ -231,7 +231,7 @@ async function runInit({ name }) {
231
231
  preview: 'round preview'
232
232
  },
233
233
  dependencies: {
234
- 'round-core': '^0.0.4'
234
+ 'round-core': '^0.1.9'
235
235
  },
236
236
  devDependencies: {
237
237
  vite: '^5.0.0'
@@ -17,6 +17,10 @@ let hasMatchForPath = false;
17
17
  const pathHasMatch = signal(false);
18
18
  const pathEvalReady = signal(true);
19
19
 
20
+ // Per-path scroll positions so each route can keep its own scroll offset.
21
+ const scrollPositions = hasWindow ? new Map() : null;
22
+ let lastScrollPath = hasWindow ? window.location.pathname : '/';
23
+
20
24
  let defaultNotFoundComponent = null;
21
25
  let autoNotFoundMounted = false;
22
26
  let userProvidedNotFound = false;
@@ -30,7 +34,26 @@ function ensureListener() {
30
34
  mountAutoNotFound();
31
35
 
32
36
  window.addEventListener('popstate', () => {
33
- currentPath(window.location.pathname);
37
+ const rawPrev = lastScrollPath;
38
+ const rawNext = window.location.pathname;
39
+
40
+ const prevPath = normalizePathname(rawPrev);
41
+ const nextPath = normalizePathname(rawNext);
42
+
43
+ if (scrollPositions) {
44
+ // Save scroll for the route we are leaving
45
+ const y = window.scrollY ?? window.pageYOffset ?? 0;
46
+ scrollPositions.set(prevPath, y);
47
+ }
48
+
49
+ lastScrollPath = rawNext;
50
+ currentPath(rawNext);
51
+
52
+ if (scrollPositions) {
53
+ // Restore scroll for the route we are entering (or default to top)
54
+ const saved = scrollPositions.get(nextPath) ?? 0;
55
+ window.scrollTo(0, saved);
56
+ }
34
57
  });
35
58
  }
36
59
 
@@ -76,7 +99,6 @@ export function useRouteReady() {
76
99
  export function getIsNotFound() {
77
100
  const pathname = normalizePathname(currentPath());
78
101
  if (pathname === '/') return false;
79
- if (!(Boolean(pathEvalReady()) && lastPathEvaluated === pathname)) return false;
80
102
  return !Boolean(pathHasMatch());
81
103
  }
82
104
 
@@ -84,7 +106,8 @@ export function useIsNotFound() {
84
106
  return () => {
85
107
  const pathname = normalizePathname(currentPath());
86
108
  if (pathname === '/') return false;
87
- if (!(Boolean(pathEvalReady()) && lastPathEvaluated === pathname)) return false;
109
+ // Mirror getIsNotFound: react immediately to the current path and
110
+ // the latest match flag, instead of waiting for pathEvalReady.
88
111
  return !Boolean(pathHasMatch());
89
112
  };
90
113
  }
@@ -136,12 +159,26 @@ export function navigate(to, options = {}) {
136
159
  if (!hasWindow) return;
137
160
  ensureListener();
138
161
 
162
+ const fromPath = normalizePathname(currentPath());
163
+ if (scrollPositions) {
164
+ const y = window.scrollY ?? window.pageYOffset ?? 0;
165
+ scrollPositions.set(fromPath, y);
166
+ }
167
+
139
168
  const normalizedTo = normalizeTo(to);
140
169
  const replace = Boolean(options.replace);
141
170
  if (replace) window.history.replaceState({}, '', normalizedTo);
142
171
  else window.history.pushState({}, '', normalizedTo);
143
172
 
144
- currentPath(window.location.pathname);
173
+ const rawNext = window.location.pathname;
174
+ lastScrollPath = rawNext;
175
+ currentPath(rawNext);
176
+
177
+ if (scrollPositions) {
178
+ const nextPath = normalizePathname(rawNext);
179
+ const saved = scrollPositions.get(nextPath) ?? 0;
180
+ window.scrollTo(0, saved);
181
+ }
145
182
  }
146
183
 
147
184
  function applyHead({ title, meta, links, icon, favicon }) {