round-core 0.0.5 → 0.0.7

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.
Files changed (43) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +49 -0
  3. package/dist/index.d.ts +341 -0
  4. package/dist/vite-plugin.js +0 -31
  5. package/extension/.vscodeignore +5 -0
  6. package/extension/LICENSE +21 -0
  7. package/extension/cgmanifest.json +45 -0
  8. package/extension/extension.js +163 -0
  9. package/extension/images/round-config-dark.svg +10 -0
  10. package/extension/images/round-config-light.svg +10 -0
  11. package/extension/images/round-dark.svg +10 -0
  12. package/extension/images/round-light.svg +10 -0
  13. package/extension/javascript-language-configuration.json +241 -0
  14. package/extension/package-lock.json +97 -0
  15. package/extension/package.json +119 -0
  16. package/extension/package.nls.json +4 -0
  17. package/extension/round-0.1.0.vsix +0 -0
  18. package/extension/round-lsp/package-lock.json +185 -0
  19. package/extension/round-lsp/package.json +21 -0
  20. package/extension/round-lsp/src/round-transformer-lsp.js +248 -0
  21. package/extension/round-lsp/src/server.js +396 -0
  22. package/extension/snippets/javascript.code-snippets +266 -0
  23. package/extension/snippets/round.code-snippets +109 -0
  24. package/extension/syntaxes/JavaScript.tmLanguage.json +6001 -0
  25. package/extension/syntaxes/JavaScriptReact.tmLanguage.json +6066 -0
  26. package/extension/syntaxes/Readme.md +12 -0
  27. package/extension/syntaxes/Regular Expressions (JavaScript).tmLanguage +237 -0
  28. package/extension/syntaxes/Round.tmLanguage.json +290 -0
  29. package/extension/syntaxes/RoundInject.tmLanguage.json +20 -0
  30. package/extension/tags-language-configuration.json +152 -0
  31. package/extension/temp_astro/package-lock.json +912 -0
  32. package/extension/temp_astro/package.json +16 -0
  33. package/extension/types/round-core.d.ts +326 -0
  34. package/package.json +2 -1
  35. package/src/cli.js +53 -0
  36. package/src/compiler/vite-plugin.js +0 -35
  37. package/src/index.d.ts +341 -0
  38. package/src/runtime/context.js +12 -0
  39. package/src/runtime/dom.js +10 -0
  40. package/src/runtime/router.js +28 -0
  41. package/src/runtime/signals.js +38 -0
  42. package/src/runtime/store.js +7 -0
  43. package/vite.config.build.js +12 -0
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  npm install round-core
17
17
  ```
18
18
 
19
- Instead of a Virtual DOM diff, Round updates the UI by subscribing DOM updates directly to reactive primitives (**signals**). This keeps rendering predictable, small, and fast for interactive apps.
19
+ Instead of a Virtual DOM diff, Round updates the UI by subscribing DOM updates directly to reactive primitives (**signals**) and **bindables**. This keeps rendering predictable, small, and fast for interactive apps.
20
20
 
21
21
  ## What Round is focused on
22
22
 
@@ -93,7 +93,7 @@ This scaffolds a minimal Round app with `src/app.round` and an example `src/coun
93
93
 
94
94
  ## `.round` files
95
95
 
96
- A `.round` file is a JSX-based component module (ESM) compiled by the Round toolchain.
96
+ A `.round` file is a JSX-based component module (ESM) compiled by the Round toolchain. you can also use .jsx files but you wont get the round JSX superset features like conditional rendering and other features.
97
97
 
98
98
  Example `src/app.round`:
99
99
 
package/dist/cli.js CHANGED
@@ -10,6 +10,19 @@ function onSignal() {
10
10
  }
11
11
  process.on("SIGINT", onSignal);
12
12
  process.on("SIGTERM", onSignal);
13
+ process.on("exit", () => {
14
+ cleanupTemporaryFiles();
15
+ });
16
+ const temporaryFiles = /* @__PURE__ */ new Set();
17
+ function cleanupTemporaryFiles() {
18
+ for (const f of temporaryFiles) {
19
+ try {
20
+ if (fs.existsSync(f)) fs.unlinkSync(f);
21
+ } catch {
22
+ }
23
+ }
24
+ temporaryFiles.clear();
25
+ }
13
26
  function normalizePath(p) {
14
27
  return p.replaceAll("\\", "/");
15
28
  }
@@ -323,6 +336,40 @@ function coerceNumber(v, fallback) {
323
336
  const n = Number(v);
324
337
  return Number.isFinite(n) ? n : fallback;
325
338
  }
339
+ function generateIndexHtml(config) {
340
+ const name = config?.name ?? "Round";
341
+ const rawEntry = config?.entry ?? "./src/app.round";
342
+ let entry = rawEntry;
343
+ if (entry.startsWith("./")) entry = entry.slice(1);
344
+ if (!entry.startsWith("/")) entry = "/" + entry;
345
+ return [
346
+ "<!DOCTYPE html>",
347
+ '<html lang="en">',
348
+ "<head>",
349
+ ' <meta charset="UTF-8" />',
350
+ ' <meta name="viewport" content="width=device-width, initial-scale=1.0" />',
351
+ ` <title>${name}</title>`,
352
+ "</head>",
353
+ "<body>",
354
+ ' <div id="app"></div>',
355
+ ' <script type="module">',
356
+ " import { render } from 'round-core';",
357
+ ` import App from '${entry}';`,
358
+ "",
359
+ " render(App, document.getElementById('app'));",
360
+ " <\/script>",
361
+ "</body>",
362
+ "</html>"
363
+ ].join("\n");
364
+ }
365
+ function writeTemporaryIndex(rootDir, config) {
366
+ const indexPath = path.resolve(rootDir, "index.html");
367
+ if (fs.existsSync(indexPath)) return false;
368
+ const content = generateIndexHtml(config);
369
+ fs.writeFileSync(indexPath, content, "utf8");
370
+ temporaryFiles.add(indexPath);
371
+ return true;
372
+ }
326
373
  async function runDev({ rootDir, configPathAbs, config }) {
327
374
  const startedAt = Date.now();
328
375
  const configDir = path.dirname(configPathAbs);
@@ -344,6 +391,7 @@ async function runDev({ rootDir, configPathAbs, config }) {
344
391
  const serverPort2 = coerceNumber(nextConfig?.dev?.port, 5173);
345
392
  const open2 = Boolean(nextConfig?.dev?.open);
346
393
  const base2 = nextConfig?.routing?.base ?? "/";
394
+ writeTemporaryIndex(rootDir, nextConfig);
347
395
  if (showBanner) {
348
396
  banner();
349
397
  process.stdout.write(`${c(" Config", "gray")} ${configPathAbs}
@@ -413,6 +461,7 @@ async function runBuild({ rootDir, configPathAbs, config }) {
413
461
  normalizePath(path.relative(rootDir, entryAbs));
414
462
  const outDir = config?.output ? resolveFrom(configDir, config.output) : resolveFrom(rootDir, "./dist");
415
463
  const base = config?.routing?.base ?? "/";
464
+ writeTemporaryIndex(rootDir, config);
416
465
  banner();
417
466
  process.stdout.write(`${c(" Config", "gray")} ${configPathAbs}
418
467
  `);
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Round Framework Type Definitions
3
+ */
4
+
5
+ export interface RoundSignal<T> {
6
+ /**
7
+ * Get or set the current value.
8
+ */
9
+ (newValue?: T): T;
10
+
11
+ /**
12
+ * Get the current value (reactive).
13
+ */
14
+ value: T;
15
+
16
+ /**
17
+ * Get the current value without tracking dependencies.
18
+ */
19
+ peek(): T;
20
+
21
+ /**
22
+ * Creates a transformed view of this signal.
23
+ */
24
+ transform<U>(fromInput: (v: U) => T, toOutput: (v: T) => U): RoundSignal<U>;
25
+
26
+ /**
27
+ * Attaches validation logic to the signal.
28
+ */
29
+ validate(validator: (next: T, prev: T) => string | boolean | undefined | null, options?: {
30
+ /** Timing of validation: 'input' (default) or 'blur'. */
31
+ validateOn?: 'input' | 'blur';
32
+ /** Whether to run validation immediately on startup. */
33
+ validateInitial?: boolean;
34
+ }): RoundSignal<T> & {
35
+ /** Signal containing the current validation error message. */
36
+ error: RoundSignal<string | null>;
37
+ /** Manually trigger validation check. Returns true if valid. */
38
+ check(): boolean
39
+ };
40
+
41
+ /**
42
+ * Creates a read/write view of a specific property path.
43
+ */
44
+ $pick<K extends keyof T>(path: K): RoundSignal<T[K]>;
45
+ $pick(path: string | string[]): RoundSignal<any>;
46
+
47
+ /**
48
+ * Internal: marks the signal as bindable for two-way bindings.
49
+ */
50
+ bind?: boolean;
51
+ }
52
+
53
+ export interface Signal {
54
+ <T>(initialValue?: T): RoundSignal<T>;
55
+ object<T extends object>(initialState: T): { [K in keyof T]: RoundSignal<T[K]> };
56
+ }
57
+
58
+ /**
59
+ * Creates a reactive signal.
60
+ */
61
+ export const signal: Signal;
62
+
63
+ /**
64
+ * Creates a bindable signal intended for two-way DOM bindings.
65
+ */
66
+ export interface Bindable {
67
+ <T>(initialValue?: T): RoundSignal<T>;
68
+ object<T extends object>(initialState: T): { [K in keyof T]: RoundSignal<T[K]> };
69
+ }
70
+
71
+ /**
72
+ * Creates a bindable signal intended for two-way DOM bindings.
73
+ */
74
+ export const bindable: Bindable;
75
+
76
+ /**
77
+ * Run a function without tracking any signals it reads.
78
+ * Any signals accessed inside the function will not become dependencies of the current effect.
79
+ */
80
+ export function untrack<T>(fn: () => T): T;
81
+
82
+ /**
83
+ * Create a reactive side-effect that runs whenever its signal dependencies change.
84
+ */
85
+ export function effect(fn: () => void | (() => void), options?: {
86
+ /** If false, the effect won't run immediately on creation. Defaults to true. */
87
+ onLoad?: boolean
88
+ }): () => void;
89
+
90
+ /**
91
+ * Create a reactive side-effect with explicit dependencies.
92
+ */
93
+ export function effect(deps: any[], fn: () => void | (() => void), options?: {
94
+ /** If false, the effect won't run immediately on creation. Defaults to true. */
95
+ onLoad?: boolean
96
+ }): () => void;
97
+
98
+ /**
99
+ * Create a read-only computed signal derived from other signals.
100
+ */
101
+ export function derive<T>(fn: () => T): () => T;
102
+
103
+ /**
104
+ * Create a read/write view of a specific path within a signal object.
105
+ */
106
+ export function pick<T = any>(root: RoundSignal<any>, path: string | string[]): RoundSignal<T>;
107
+
108
+ /**
109
+ * Store API
110
+ */
111
+ export interface RoundStore<T> {
112
+ /**
113
+ * Access a specific key from the store as a bindable signal.
114
+ */
115
+ use<K extends keyof T>(key: K): RoundSignal<T[K]>;
116
+
117
+ /**
118
+ * Update a specific key in the store.
119
+ */
120
+ set<K extends keyof T>(key: K, value: T[K]): T[K];
121
+
122
+ /**
123
+ * Batch update multiple keys in the store.
124
+ */
125
+ patch(obj: Partial<T>): void;
126
+
127
+ /**
128
+ * Get a snapshot of the current state.
129
+ */
130
+ snapshot(options?: {
131
+ /** If true, the returned values will be reactive signals. */
132
+ reactive?: boolean
133
+ }): T;
134
+
135
+ /**
136
+ * Enable persistence for the store.
137
+ */
138
+ persist(storageKey: string, options?: {
139
+ /** The storage implementation (defaults to localStorage). */
140
+ storage?: Storage;
141
+ /** Debounce time in milliseconds for writes. */
142
+ debounce?: number;
143
+ /** Array of keys to exclude from persistence. */
144
+ exclude?: string[];
145
+ }): RoundStore<T>;
146
+
147
+ /**
148
+ * Action methods defined during store creation.
149
+ */
150
+ actions: Record<string, Function>;
151
+ }
152
+
153
+ /**
154
+ * Create a shared global state store with actions and optional persistence.
155
+ */
156
+ export function createStore<T, A extends Record<string, (state: T, ...args: any[]) => Partial<T> | void>>(
157
+ initialState: T,
158
+ actions?: A
159
+ ): RoundStore<T> & { [K in keyof A]: (...args: Parameters<A[K]> extends [any, ...infer P] ? P : never) => any };
160
+
161
+ /**
162
+ * Router API
163
+ */
164
+ export interface RouteProps {
165
+ /** The path to match. Must start with a forward slash. */
166
+ route?: string;
167
+ /** If true, only matches if the path is exactly the same. */
168
+ exact?: boolean;
169
+ /** Page title to set in the document header when active. */
170
+ title?: string;
171
+ /** Meta description to set in the document header when active. */
172
+ description?: string;
173
+ /** Advanced head configuration including links and meta tags. */
174
+ head?: any;
175
+ /** Fragment or elements to render when matched. */
176
+ children?: any;
177
+ }
178
+
179
+ /**
180
+ * Define a route that renders its children when the path matches.
181
+ */
182
+ export function Route(props: RouteProps): any;
183
+
184
+ /**
185
+ * An alias for Route, typically used for top-level pages.
186
+ */
187
+ export function Page(props: RouteProps): any;
188
+
189
+ export interface LinkProps {
190
+ /** The destination path. */
191
+ href: string;
192
+ /** Alias for href. */
193
+ to?: string;
194
+ /** Use SPA navigation (prevents full page reloads). Defaults to true. */
195
+ spa?: boolean;
196
+ /** Force a full page reload on navigation. */
197
+ reload?: boolean;
198
+ /** Custom click event handler. */
199
+ onClick?: (e: MouseEvent) => void;
200
+ /** Link content (text or elements). */
201
+ children?: any;
202
+ [key: string]: any;
203
+ }
204
+
205
+ /**
206
+ * A standard link component that performs SPA navigation.
207
+ */
208
+ export function Link(props: LinkProps): any;
209
+
210
+ /**
211
+ * Define a fallback component or content for when no routes match.
212
+ */
213
+ export function NotFound(props: {
214
+ /** Optional component to render for the 404 state. */
215
+ component?: any;
216
+ /** Fallback content. ignored if 'component' is provided. */
217
+ children?: any
218
+ }): any;
219
+
220
+ /**
221
+ * Navigate to a different path programmatically.
222
+ */
223
+ export function navigate(to: string, options?: {
224
+ /** If true, replaces the current history entry instead of pushing. */
225
+ replace?: boolean
226
+ }): void;
227
+
228
+ /**
229
+ * Hook to get a reactive function returning the current normalized pathname.
230
+ */
231
+ export function usePathname(): () => string;
232
+
233
+ /**
234
+ * Get the current normalized pathname.
235
+ */
236
+ export function getPathname(): string;
237
+
238
+ /**
239
+ * Hook to get a reactive function returning the current location object.
240
+ */
241
+ export function useLocation(): () => { pathname: string; search: string; hash: string };
242
+
243
+ /**
244
+ * Get the current location object (pathname, search, hash).
245
+ */
246
+ export function getLocation(): { pathname: string; search: string; hash: string };
247
+
248
+ /**
249
+ * Hook to get a reactive function returning whether the current path has no matches.
250
+ */
251
+ export function useIsNotFound(): () => boolean;
252
+
253
+ /**
254
+ * Get whether the current path is NOT matched by any defined route.
255
+ */
256
+ export function getIsNotFound(): boolean;
257
+
258
+ /**
259
+ * DOM & Context API
260
+ */
261
+
262
+ /**
263
+ * Create a DOM element or instance a component.
264
+ */
265
+ export function createElement(tag: any, props?: any, ...children: any[]): any;
266
+
267
+ /**
268
+ * A grouping component that returns its children without a wrapper element.
269
+ */
270
+ export function Fragment(props: { children?: any }): any;
271
+
272
+ export interface Context<T> {
273
+ /** Internal identifier for the context. */
274
+ id: number;
275
+ /** Default value used when no Provider is found in the tree. */
276
+ defaultValue: T;
277
+ /** Component that provides a value to all its descendants. */
278
+ Provider: (props: { value: T; children?: any }) => any;
279
+ }
280
+
281
+ /**
282
+ * Create a new Context object for sharing state between components.
283
+ */
284
+ export function createContext<T>(defaultValue?: T): Context<T>;
285
+
286
+ /**
287
+ * Read the current value of a context from the component tree.
288
+ */
289
+ export function readContext<T>(ctx: Context<T>): T;
290
+
291
+ /**
292
+ * Returns a reactive function that reads the current context value.
293
+ */
294
+ export function bindContext<T>(ctx: Context<T>): () => T;
295
+
296
+ /**
297
+ * Async & Code Splitting
298
+ */
299
+
300
+ /**
301
+ * Mark a component for lazy loading (code-splitting).
302
+ * Expects a function returning a dynamic import promise.
303
+ */
304
+ export function lazy<T>(fn: () => Promise<{ default: T } | T>): any;
305
+
306
+ declare module "*.round";
307
+
308
+ export interface SuspenseProps {
309
+ /** Content to show while children (e.g. lazy components) are loading. */
310
+ fallback: any;
311
+ /** Content that might trigger a loading state. */
312
+ children?: any;
313
+ }
314
+
315
+ /**
316
+ * Component that boundaries async operations and renders a fallback while loading.
317
+ */
318
+ export function Suspense(props: SuspenseProps): any;
319
+
320
+ /**
321
+ * Head Management
322
+ */
323
+
324
+ /**
325
+ * Define static head metadata (titles, meta tags, favicons, etc.).
326
+ */
327
+ export function startHead(head: any): any;
328
+
329
+ /**
330
+ * Markdown
331
+ */
332
+
333
+ /**
334
+ * Component that renders Markdown content into HTML.
335
+ */
336
+ export function Markdown(props: {
337
+ /** The markdown string or a function returning it. */
338
+ content: string | (() => string);
339
+ /** Remark/Rehype configuration options. */
340
+ options?: any
341
+ }): any;
@@ -614,40 +614,9 @@ ${head.raw}`;
614
614
  };
615
615
  },
616
616
  resolveId(id) {
617
- if (id === "/index.html" || id === "index.html") {
618
- const fullPath = path.resolve(state.rootDir, "index.html");
619
- if (!fs.existsSync(fullPath)) {
620
- return "/index.html";
621
- }
622
- }
623
617
  return null;
624
618
  },
625
619
  load(id) {
626
- if (id === "/index.html" || id === "index.html") {
627
- const fullPath = path.resolve(state.rootDir, "index.html");
628
- if (fs.existsSync(fullPath)) return null;
629
- const entry = state.entryRel ?? "./src/index.js";
630
- const entryPath = entry.startsWith("/") ? entry : `/${entry}`;
631
- return [
632
- "<!DOCTYPE html>",
633
- '<html lang="en">',
634
- "<head>",
635
- ' <meta charset="UTF-8" />',
636
- ' <meta name="viewport" content="width=device-width, initial-scale=1.0" />',
637
- ` <title>${state.name}</title>`,
638
- "</head>",
639
- "<body>",
640
- ' <div id="app"></div>',
641
- ' <script type="module">',
642
- " import { render } from 'round-core';",
643
- ` import App from '${entryPath}';`,
644
- "",
645
- " render(App, document.getElementById('app'));",
646
- " <\/script>",
647
- "</body>",
648
- "</html>"
649
- ].join("\n");
650
- }
651
620
  if (!isMdRawRequest(id)) return;
652
621
  const fileAbs = stripQuery(id);
653
622
  try {
@@ -0,0 +1,5 @@
1
+ test/**
2
+ src/**/*.ts
3
+ syntaxes/Readme.md
4
+ tsconfig.json
5
+ cgmanifest.json
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ZtaMDev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,45 @@
1
+ {
2
+ "registrations": [
3
+ {
4
+ "component": {
5
+ "type": "git",
6
+ "git": {
7
+ "name": "microsoft/TypeScript-TmLanguage",
8
+ "repositoryUrl": "https://github.com/microsoft/TypeScript-TmLanguage",
9
+ "commitHash": "3133e3d914db9a2bb8812119f9273727a305f16b"
10
+ }
11
+ },
12
+ "license": "MIT",
13
+ "version": "0.0.1",
14
+ "description": "The file syntaxes/JavaScript.tmLanguage.json was derived from TypeScriptReact.tmLanguage in https://github.com/microsoft/TypeScript-TmLanguage."
15
+ },
16
+ {
17
+ "component": {
18
+ "type": "git",
19
+ "git": {
20
+ "name": "textmate/javascript.tmbundle",
21
+ "repositoryUrl": "https://github.com/textmate/javascript.tmbundle",
22
+ "commitHash": "fccf0af0c95430a42e1bf98f0c7a4723a53283e7"
23
+ }
24
+ },
25
+ "licenseDetail": [
26
+ "Copyright (c) textmate-javascript.tmbundle project authors",
27
+ "",
28
+ "If not otherwise specified (see below), files in this repository fall under the following license:",
29
+ "",
30
+ "Permission to copy, use, modify, sell and distribute this",
31
+ "software is granted. This software is provided \"as is\" without",
32
+ "express or implied warranty, and with no claim as to its",
33
+ "suitability for any purpose.",
34
+ "",
35
+ "An exception is made for files in readable text which contain their own license information,",
36
+ "or files where an accompanying file exists (in the same directory) with a \"-license\" suffix added",
37
+ "to the base-name name of the original file, and an extension of txt, html, or similar. For example",
38
+ "\"tidy\" is accompanied by \"tidy-license.txt\"."
39
+ ],
40
+ "license": "TextMate Bundle License",
41
+ "version": "0.0.0"
42
+ }
43
+ ],
44
+ "version": 1
45
+ }