create-flow-os 0.0.1-dev.1771785969 → 0.0.1-dev.1771840262

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 (93) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/packages/client/client/root.tsx +1 -8
  4. package/packages/client/client/routes/index.tsx +8 -7
  5. package/profiles/client/bun.lock +30 -13
  6. package/profiles/client/client/root.tsx +1 -8
  7. package/profiles/client/client/routes/about.tsx +7 -0
  8. package/profiles/client/client/routes/index.tsx +8 -7
  9. package/profiles/client/flow.config.ts +4 -1
  10. package/profiles/client/package.json +3 -0
  11. package/profiles/client/packages/client/build/config.ts +67 -22
  12. package/profiles/client/packages/client/build/vite.ts +52 -8
  13. package/profiles/client/packages/client/features/attrs.ts +1 -1
  14. package/profiles/client/packages/client/index.ts +2 -1
  15. package/profiles/client/packages/client/package.json +3 -3
  16. package/profiles/client/packages/client/runtime/jsx-types.d.ts +2 -0
  17. package/profiles/client/packages/client/runtime/jsx.ts +4 -2
  18. package/profiles/client/packages/router/index.ts +14 -168
  19. package/profiles/client/packages/router/matcher.ts +105 -0
  20. package/profiles/client/packages/router/router.ts +185 -0
  21. package/profiles/client/packages/router/types.ts +34 -0
  22. package/profiles/client/packages/router/utils.ts +45 -0
  23. package/profiles/client/packages/style/index.ts +29 -15
  24. package/profiles/client/packages/style/package.json +6 -1
  25. package/profiles/client/packages/style/react.ts +68 -0
  26. package/profiles/client/packages/style/resolve.ts +44 -1
  27. package/profiles/client/packages/style/server.ts +299 -0
  28. package/profiles/client/packages/style/shorthand.ts +34 -0
  29. package/profiles/client/packages/style/style-builder/button.ts +41 -0
  30. package/profiles/client/packages/style/style-builder/constants.ts +16 -0
  31. package/profiles/client/packages/style/style-builder/dom.ts +18 -0
  32. package/profiles/client/packages/style/style-builder/index.ts +48 -0
  33. package/profiles/client/packages/style/style-builder/panel.ts +69 -0
  34. package/profiles/client/packages/style/style-builder/position.ts +25 -0
  35. package/profiles/client/packages/style/visual-builder.ts +822 -0
  36. package/profiles/client/packages/style/vite-plugin.ts +86 -0
  37. package/profiles/full/bun.lock +30 -13
  38. package/profiles/full/client/root.tsx +1 -8
  39. package/profiles/full/client/routes/about.tsx +7 -0
  40. package/profiles/full/client/routes/index.tsx +8 -7
  41. package/profiles/full/flow.config.ts +2 -4
  42. package/profiles/full/package.json +3 -0
  43. package/profiles/full/packages/client/build/config.ts +67 -22
  44. package/profiles/full/packages/client/build/vite.ts +52 -8
  45. package/profiles/full/packages/client/features/attrs.ts +1 -1
  46. package/profiles/full/packages/client/index.ts +2 -1
  47. package/profiles/full/packages/client/package.json +3 -3
  48. package/profiles/full/packages/client/runtime/jsx-types.d.ts +2 -0
  49. package/profiles/full/packages/client/runtime/jsx.ts +4 -2
  50. package/profiles/full/packages/router/index.ts +14 -168
  51. package/profiles/full/packages/router/matcher.ts +105 -0
  52. package/profiles/full/packages/router/router.ts +185 -0
  53. package/profiles/full/packages/router/types.ts +34 -0
  54. package/profiles/full/packages/router/utils.ts +45 -0
  55. package/profiles/full/packages/style/index.ts +29 -15
  56. package/profiles/full/packages/style/package.json +6 -1
  57. package/profiles/full/packages/style/react.ts +68 -0
  58. package/profiles/full/packages/style/resolve.ts +44 -1
  59. package/profiles/full/packages/style/server.ts +299 -0
  60. package/profiles/full/packages/style/shorthand.ts +34 -0
  61. package/profiles/full/packages/style/style-builder/button.ts +41 -0
  62. package/profiles/full/packages/style/style-builder/constants.ts +16 -0
  63. package/profiles/full/packages/style/style-builder/dom.ts +18 -0
  64. package/profiles/full/packages/style/style-builder/index.ts +48 -0
  65. package/profiles/full/packages/style/style-builder/panel.ts +69 -0
  66. package/profiles/full/packages/style/style-builder/position.ts +25 -0
  67. package/profiles/full/packages/style/visual-builder.ts +822 -0
  68. package/profiles/full/packages/style/vite-plugin.ts +86 -0
  69. package/profiles/server/bun.lock +30 -13
  70. package/profiles/server/flow.config.ts +4 -3
  71. package/profiles/server/package.json +3 -0
  72. package/profiles/server/packages/router/index.ts +14 -168
  73. package/profiles/server/packages/router/matcher.ts +105 -0
  74. package/profiles/server/packages/router/router.ts +185 -0
  75. package/profiles/server/packages/router/types.ts +34 -0
  76. package/profiles/server/packages/router/utils.ts +45 -0
  77. package/profiles/server/packages/style/index.ts +29 -15
  78. package/profiles/server/packages/style/package.json +6 -1
  79. package/profiles/server/packages/style/react.ts +68 -0
  80. package/profiles/server/packages/style/resolve.ts +44 -1
  81. package/profiles/server/packages/style/server.ts +299 -0
  82. package/profiles/server/packages/style/shorthand.ts +34 -0
  83. package/profiles/server/packages/style/style-builder/button.ts +41 -0
  84. package/profiles/server/packages/style/style-builder/constants.ts +16 -0
  85. package/profiles/server/packages/style/style-builder/dom.ts +18 -0
  86. package/profiles/server/packages/style/style-builder/index.ts +48 -0
  87. package/profiles/server/packages/style/style-builder/panel.ts +69 -0
  88. package/profiles/server/packages/style/style-builder/position.ts +25 -0
  89. package/profiles/server/packages/style/visual-builder.ts +822 -0
  90. package/profiles/server/packages/style/vite-plugin.ts +86 -0
  91. package/templates/flow.config.client.ts +4 -1
  92. package/templates/flow.config.full.ts +6 -0
  93. package/templates/flow.config.server.ts +4 -3
@@ -1,174 +1,20 @@
1
- export const APP_ID = 'flow-app';
1
+ // ========== Core ==========
2
+ // App = root component (one <App /> in layout). run = call once in entry: run(App, import.meta.glob('./routes/**/*.tsx'))
3
+ export { App, run } from './router.js';
2
4
 
3
- /** Contenitore dove il router monta la pagina corrente. In root: <App /> (da @flow.os/router). */
4
- export function App(): HTMLDivElement {
5
- const el = document.createElement('div');
6
- el.id = APP_ID;
7
- el.setAttribute('role', 'main');
8
- return el;
9
- }
5
+ // ========== Utilities ==========
6
+ export { go, params } from './router.js';
7
+ export { base, path, href, query, setQuery } from './utils.js';
8
+ /*
9
+ base() => 'https://localhost:5173'
10
10
 
11
- export type RouteModule = {
12
- default: (() => Node) | ((params: Record<string, string>) => Node) | (() => string) | string;
13
- };
11
+ path() => '/posts/123'
14
12
 
15
- export type RunOptions = {
16
- /** Nodo (o factory) mostrato mentre la route lazy viene caricata. */
17
- fallback?: Node | (() => Node);
18
- /** Pagina 404: se fornita, viene mostrata full-page (sostituisce tutto #app, senza layout comune). */
19
- notFound?: Node | ((path: string) => Node);
20
- };
13
+ href() => 'https://localhost:5173/posts/123'
21
14
 
22
- function pathToPattern(key: string): string {
23
- const afterRoutes = key.replace(/^.*[/\\]routes[/\\]?/i, '').replace(/\.tsx?$/i, '');
24
- const s = afterRoutes.split(/[/\\]/).filter(Boolean).join('/');
25
- if (s === 'index' || s === '') return '';
26
- return s
27
- .split('/')
28
- .map((x) => (x.startsWith('[') && x.endsWith(']') ? `:${x.slice(1, -1)}` : x))
29
- .join('/');
30
- }
15
+ params() => ['posts', '123']
31
16
 
32
- function match(pattern: string, path: string): Record<string, string> | null {
33
- const pa = pattern ? pattern.split('/').filter(Boolean) : [];
34
- const ph = path.replace(/^\//, '').split('/').filter(Boolean);
35
- if (pa.length !== ph.length) return null;
36
- const params: Record<string, string> = {};
37
- for (let i = 0; i < pa.length; i++) {
38
- if (pa[i]!.startsWith(':')) params[pa[i]!.slice(1)] = ph[i] ?? '';
39
- else if (pa[i] !== ph[i]) return null;
40
- }
41
- return params;
42
- }
17
+ query() => URLSearchParams { page: '2' }
43
18
 
44
- function setContent(container: HTMLElement, content: Node | string): void {
45
- container.innerHTML = '';
46
- if (typeof content === 'string') container.textContent = content;
47
- else container.appendChild(content);
48
- }
49
-
50
- function defaultNotFound(path: string): Node {
51
- const wrap = document.createElement('div');
52
- wrap.setAttribute('role', 'alert');
53
- const h = document.createElement('h1');
54
- h.textContent = '404';
55
- const p = document.createElement('p');
56
- p.textContent = `Pagina non trovata: ${path || '/'}`;
57
- wrap.append(h, p);
58
- return wrap;
59
- }
60
-
61
- type RouterOptions = {
62
- fallback?: Node | (() => Node);
63
- notFound?: Node | ((path: string) => Node);
64
- /** Per 404 full-page: elemento #app e factory del layout da ripristinare quando si naviga via. */
65
- appRoot?: HTMLElement;
66
- getLayout?: () => Node;
67
- };
68
-
69
- export function createRouter(
70
- modules: Record<string, () => Promise<RouteModule>>,
71
- container: HTMLElement,
72
- options: RouterOptions = {}
73
- ) {
74
- const routes = Object.entries(modules).map(([key, loader]) => ({
75
- pattern: pathToPattern(key),
76
- loader,
77
- }));
78
- routes.sort(
79
- (a, b) =>
80
- b.pattern.split('/').filter(Boolean).length - a.pattern.split('/').filter(Boolean).length
81
- );
82
- const notFound = options.notFound ?? defaultNotFound;
83
- const fullPageNotFound = Boolean(options.appRoot && options.getLayout && options.notFound);
84
-
85
- function getContainer(): HTMLElement | null {
86
- return document.getElementById(APP_ID);
87
- }
88
-
89
- async function render(pathname: string): Promise<boolean> {
90
- const path = pathname === '/' ? '' : pathname.slice(1);
91
- for (const { pattern, loader } of routes) {
92
- const params = match(pattern, path);
93
- if (params === null) continue;
94
- let target = getContainer();
95
- if (fullPageNotFound && !target) {
96
- options.appRoot!.innerHTML = '';
97
- options.appRoot!.appendChild(options.getLayout!());
98
- target = getContainer();
99
- }
100
- if (!target) continue;
101
- const fallbackNode =
102
- options.fallback != null
103
- ? typeof options.fallback === 'function'
104
- ? options.fallback()
105
- : options.fallback
106
- : null;
107
- if (fallbackNode) setContent(target, fallbackNode);
108
- const mod = await loader();
109
- const def = mod.default;
110
- if (typeof def === 'function') {
111
- const out = def.length
112
- ? (def as (p: Record<string, string>) => Node)(params)
113
- : (def as () => Node | string)();
114
- if (out instanceof Node) setContent(target, out);
115
- else if (typeof out === 'string') target.innerHTML = out;
116
- else target.innerHTML = '';
117
- } else setContent(target, typeof def === 'string' ? def : (def as unknown as Node));
118
- return true;
119
- }
120
- const nfNode = typeof notFound === 'function' ? notFound(pathname) : notFound;
121
- if (fullPageNotFound) {
122
- options.appRoot!.innerHTML = '';
123
- options.appRoot!.appendChild(nfNode);
124
- } else {
125
- setContent(container, nfNode);
126
- }
127
- return false;
128
- }
129
-
130
- function go(path: string, replace = false): void {
131
- const p = path.startsWith('/') ? path : `/${path}`;
132
- render(p).then((ok) => {
133
- if (ok) (replace ? history.replaceState : history.pushState).call(history, null, '', p);
134
- });
135
- }
136
-
137
- function start(): void {
138
- render(location.pathname || '/');
139
- window.addEventListener('popstate', () => render(location.pathname || '/'));
140
- document.addEventListener('click', (e) => {
141
- const a = (e.target as Element).closest('a');
142
- if (a?.getAttribute('href')?.startsWith('/') && !a.href.startsWith('//')) {
143
- e.preventDefault();
144
- go(a.getAttribute('href')!);
145
- }
146
- });
147
- }
148
-
149
- return { go, start };
150
- }
151
-
152
- /** Monta App e avvia il router. Da usare nell'entry virtuale. */
153
- export function run(
154
- App: () => Node,
155
- modules: Record<string, () => Promise<RouteModule>>,
156
- options?: RunOptions
157
- ): void {
158
- const app = document.getElementById('app')!;
159
- app.appendChild(App());
160
- const appContainer = document.getElementById(APP_ID);
161
- if (!appContainer)
162
- throw new Error(
163
- `#${APP_ID} not found: use <App /> from @flow.os/router in your root layout.`
164
- );
165
- const routerOptions: RouterOptions = {
166
- fallback: options?.fallback,
167
- notFound: options?.notFound,
168
- };
169
- if (options?.notFound != null) {
170
- routerOptions.appRoot = app;
171
- routerOptions.getLayout = () => App();
172
- }
173
- createRouter(modules, appContainer as HTMLElement, routerOptions).start();
174
- }
19
+ setQuery({ page: '2' }) => 'https://localhost:5173/posts/123?page=2'
20
+ */
@@ -0,0 +1,105 @@
1
+ import type { RouteEntry } from './types.js';
2
+
3
+ const CATCH_ALL_PREFIX = ':*';
4
+
5
+ /**
6
+ * Converts a file-path key (e.g. from import.meta.glob) to a route pattern.
7
+ * - `routes/index.ts` → ''
8
+ * - `routes/about.ts` → 'about'
9
+ * - `routes/posts/[id].ts` → 'posts/:id'
10
+ * - `routes/docs/[...slug].ts` → 'docs/:*slug'
11
+ */
12
+ export function pathToPattern(key: string): string {
13
+ const afterRoutes = key.replace(/^.*[/\\]routes[/\\]?/i, '').replace(/\.tsx?$/i, '');
14
+ const segments = afterRoutes.split(/[/\\]/).filter(Boolean);
15
+ if (segments.length === 0 || segments[0] === 'index') return '';
16
+
17
+ const patternParts = segments.map((seg) => {
18
+ if (seg.startsWith('[...') && seg.endsWith(']')) {
19
+ const name = seg.slice(4, -1);
20
+ return name ? `${CATCH_ALL_PREFIX}${name}` : `${CATCH_ALL_PREFIX}rest`;
21
+ }
22
+ if (seg.startsWith('[') && seg.endsWith(']')) {
23
+ return ':' + seg.slice(1, -1);
24
+ }
25
+ return seg;
26
+ });
27
+
28
+ return patternParts.join('/');
29
+ }
30
+
31
+ /**
32
+ * Returns true if the pattern string contains a catch-all segment.
33
+ */
34
+ export function isCatchAllPattern(pattern: string): boolean {
35
+ return pattern.includes(CATCH_ALL_PREFIX);
36
+ }
37
+
38
+ /**
39
+ * Counts static (non-param) segments for route sort order.
40
+ */
41
+ export function countStaticSegments(pattern: string): number {
42
+ if (!pattern) return 0;
43
+ return pattern.split('/').filter((s) => s && !s.startsWith(':') && !s.startsWith(CATCH_ALL_PREFIX)).length;
44
+ }
45
+
46
+ /**
47
+ * Matches a path against a pattern. Supports :param and :*catchAll.
48
+ * - :param matches one segment.
49
+ * - :*name matches the rest of the path (one param with path segments joined by '/').
50
+ * Returns params object or null if no match.
51
+ */
52
+ export function match(pattern: string, path: string): Record<string, string> | null {
53
+ const patternSegs = pattern ? pattern.split('/').filter(Boolean) : [];
54
+ const pathSegs = path.replace(/^\//, '').split('/').filter(Boolean);
55
+
56
+ const params: Record<string, string> = {};
57
+ let pi = 0;
58
+
59
+ for (let i = 0; i < patternSegs.length; i++) {
60
+ const seg = patternSegs[i]!;
61
+ if (seg.startsWith(CATCH_ALL_PREFIX)) {
62
+ const name = seg.slice(CATCH_ALL_PREFIX.length) || 'rest';
63
+ params[name] = pathSegs.slice(pi).join('/');
64
+ return params;
65
+ }
66
+ if (pi >= pathSegs.length) return null;
67
+ if (seg.startsWith(':')) {
68
+ params[seg.slice(1)] = pathSegs[pi] ?? '';
69
+ pi++;
70
+ continue;
71
+ }
72
+ if (seg !== pathSegs[pi]) return null;
73
+ pi++;
74
+ }
75
+
76
+ return pi === pathSegs.length ? params : null;
77
+ }
78
+
79
+ /**
80
+ * Builds sorted route entries: more specific first (static > dynamic > catch-all),
81
+ * then by segment count descending.
82
+ */
83
+ export function buildRouteEntries(
84
+ modules: Record<string, () => Promise<import('./types.js').RouteModule>>
85
+ ): RouteEntry[] {
86
+ const entries: RouteEntry[] = Object.entries(modules).map(([key, loader]) => {
87
+ const pattern = pathToPattern(key);
88
+ return {
89
+ pattern,
90
+ loader,
91
+ staticSegments: countStaticSegments(pattern),
92
+ catchAll: isCatchAllPattern(pattern),
93
+ };
94
+ });
95
+
96
+ entries.sort((a, b) => {
97
+ if (a.catchAll !== b.catchAll) return a.catchAll ? 1 : -1;
98
+ if (a.staticSegments !== b.staticSegments) return b.staticSegments - a.staticSegments;
99
+ const lenA = a.pattern.split('/').filter(Boolean).length;
100
+ const lenB = b.pattern.split('/').filter(Boolean).length;
101
+ return lenB - lenA;
102
+ });
103
+
104
+ return entries;
105
+ }
@@ -0,0 +1,185 @@
1
+ import type { RouteModule, RouterOptions, RunOptions } from './types.js';
2
+ import { buildRouteEntries } from './matcher.js';
3
+ import { match } from './matcher.js';
4
+
5
+ let currentRouter: RouterAPI | null = null;
6
+ let currentParams: Record<string, string> = {};
7
+
8
+ export const APP_ID = 'flow-app';
9
+
10
+ /** Root container where the router mounts the current page. Use once in root as <App />. */
11
+ export function App(): HTMLDivElement {
12
+ const el = document.createElement('div');
13
+ el.id = APP_ID;
14
+ el.setAttribute('role', 'main');
15
+ return el;
16
+ }
17
+
18
+ function setContent(container: HTMLElement, content: Node | string): void {
19
+ container.innerHTML = '';
20
+ if (typeof content === 'string') container.textContent = content;
21
+ else container.appendChild(content);
22
+ }
23
+
24
+ function defaultNotFound(path: string): Node {
25
+ const wrap = document.createElement('div');
26
+ wrap.setAttribute('role', 'alert');
27
+ const h = document.createElement('h1');
28
+ h.textContent = '404';
29
+ const p = document.createElement('p');
30
+ p.textContent = `Page not found: ${path || '/'}`;
31
+ wrap.append(h, p);
32
+ return wrap;
33
+ }
34
+
35
+ export interface RouterAPI {
36
+ /** Navigate to a path. Use replace=true to avoid adding a history entry. */
37
+ go(path: string, replace?: boolean): void;
38
+ /** Start listening to popstate and link clicks. Call once after mounting App. */
39
+ start(): void;
40
+ }
41
+
42
+ const NOT_FOUND_PATTERN = '404';
43
+
44
+ export function createRouter(
45
+ modules: Record<string, () => Promise<RouteModule>>,
46
+ container: HTMLElement,
47
+ options: RouterOptions = {}
48
+ ): RouterAPI {
49
+ const allEntries = buildRouteEntries(modules);
50
+ const routes = allEntries.filter((e) => e.pattern !== NOT_FOUND_PATTERN);
51
+ const notFoundLoader = allEntries.find((e) => e.pattern === NOT_FOUND_PATTERN)?.loader ?? null;
52
+
53
+ const notFound: Node | ((path: string) => Node | Promise<Node>) =
54
+ options.notFound ??
55
+ (notFoundLoader
56
+ ? (path) =>
57
+ notFoundLoader().then((mod) => {
58
+ const def = mod.default;
59
+ return typeof def === 'function' ? (def as (p: string) => Node)(path) : (def as unknown as Node);
60
+ })
61
+ : defaultNotFound);
62
+ const fullPageNotFound = Boolean(
63
+ options.appRoot && options.getLayout && (options.notFound != null || notFoundLoader != null)
64
+ );
65
+
66
+ function getContainer(): HTMLElement | null {
67
+ return document.getElementById(APP_ID);
68
+ }
69
+
70
+ async function render(pathname: string): Promise<boolean> {
71
+ const path = pathname === '/' ? '' : pathname.slice(1);
72
+
73
+ for (const { pattern, loader } of routes) {
74
+ const routeParams = match(pattern, path);
75
+ if (routeParams === null) continue;
76
+
77
+ currentParams = routeParams;
78
+ let target = getContainer();
79
+ if (fullPageNotFound && !target) {
80
+ options.appRoot!.innerHTML = '';
81
+ options.appRoot!.appendChild(options.getLayout!());
82
+ target = getContainer();
83
+ }
84
+ if (!target) continue;
85
+
86
+ const fallbackNode =
87
+ options.fallback != null
88
+ ? typeof options.fallback === 'function'
89
+ ? options.fallback()
90
+ : options.fallback
91
+ : null;
92
+ if (fallbackNode) setContent(target, fallbackNode);
93
+
94
+ const mod = await loader();
95
+ const def = mod.default;
96
+ if (typeof def === 'function') {
97
+ const out = def.length
98
+ ? (def as (p: Record<string, string>) => Node)(routeParams)
99
+ : (def as () => Node | string)();
100
+ if (out instanceof Node) setContent(target, out);
101
+ else if (typeof out === 'string') target.innerHTML = out;
102
+ else target.innerHTML = '';
103
+ } else {
104
+ setContent(target, typeof def === 'string' ? def : (def as unknown as Node));
105
+ }
106
+ return true;
107
+ }
108
+
109
+ currentParams = {};
110
+ const nfRaw = typeof notFound === 'function' ? notFound(pathname) : notFound;
111
+ const nfNode = nfRaw && typeof (nfRaw as Promise<unknown>).then === 'function' ? await (nfRaw as Promise<Node>) : (nfRaw as Node);
112
+ if (fullPageNotFound) {
113
+ options.appRoot!.innerHTML = '';
114
+ options.appRoot!.appendChild(nfNode);
115
+ } else {
116
+ setContent(container, nfNode);
117
+ }
118
+ return false;
119
+ }
120
+
121
+ function go(path: string, replace = false): void {
122
+ const p = path.startsWith('/') ? path : `/${path}`;
123
+ render(p).then((ok) => {
124
+ if (ok) (replace ? history.replaceState : history.pushState).call(history, null, '', p);
125
+ });
126
+ }
127
+
128
+ function start(): void {
129
+ render(location.pathname || '/');
130
+ window.addEventListener('popstate', () => render(location.pathname || '/'));
131
+ document.addEventListener('click', (e) => {
132
+ const a = (e.target as Element).closest('a');
133
+ if (a?.getAttribute('href')?.startsWith('/') && !a.href.startsWith('//')) {
134
+ e.preventDefault();
135
+ go(a.getAttribute('href')!);
136
+ }
137
+ });
138
+ }
139
+
140
+ const api: RouterAPI = { go, start };
141
+ currentRouter = api;
142
+ return api;
143
+ }
144
+
145
+ /**
146
+ * Navigate to a path. Import and use anywhere; works after run() (or createRouter().start()) has been called.
147
+ * No-op if the router is not started yet.
148
+ */
149
+ export function go(path: string, replace = false): void {
150
+ currentRouter?.go(path, replace);
151
+ }
152
+
153
+ /** Current route params. params() → all, params('id') → one. */
154
+ export function params(): Record<string, string>;
155
+ export function params(name: string): string | undefined;
156
+ export function params(name?: string): Record<string, string> | string | undefined {
157
+ if (name === undefined) return { ...currentParams };
158
+ return currentParams[name] ?? undefined;
159
+ }
160
+
161
+ /**
162
+ * Mounts App and starts the router. Use in your virtual entry.
163
+ * Returns the router API so you can call go() (and start()) from outside.
164
+ */
165
+ export function run(
166
+ AppFactory: () => Node,
167
+ modules: Record<string, () => Promise<RouteModule>>,
168
+ options?: RunOptions
169
+ ): RouterAPI {
170
+ const app = document.getElementById('app')!;
171
+ app.appendChild(AppFactory());
172
+ const appContainer = document.getElementById(APP_ID);
173
+ if (!appContainer) {
174
+ throw new Error(`#${APP_ID} not found: use <App /> from @flow.os/router in your root layout.`);
175
+ }
176
+ const routerOptions: RouterOptions = {
177
+ fallback: options?.fallback,
178
+ notFound: options?.notFound,
179
+ appRoot: app,
180
+ getLayout: () => AppFactory(),
181
+ };
182
+ const api = createRouter(modules, appContainer as HTMLElement, routerOptions);
183
+ api.start();
184
+ return api;
185
+ }
@@ -0,0 +1,34 @@
1
+ /** Route module: default can be a component (with optional params), a factory, or raw string/Node. */
2
+ export type RouteModule = {
3
+ default:
4
+ | (() => Node)
5
+ | ((params: Record<string, string>) => Node)
6
+ | (() => string)
7
+ | string;
8
+ };
9
+
10
+ /** Options for run(): fallback while loading, custom 404. */
11
+ export type RunOptions = {
12
+ /** Node (or factory) shown while the lazy route is loading. */
13
+ fallback?: Node | (() => Node);
14
+ /** 404 page: if provided, shown full-page (replaces entire #app, no shared layout). */
15
+ notFound?: Node | ((path: string) => Node);
16
+ };
17
+
18
+ /** Internal router options (fallback, notFound, full-page 404 wiring). */
19
+ export type RouterOptions = {
20
+ fallback?: Node | (() => Node);
21
+ notFound?: Node | ((path: string) => Node | Promise<Node>);
22
+ appRoot?: HTMLElement;
23
+ getLayout?: () => Node;
24
+ };
25
+
26
+ /** Internal: normalized route entry for matching. */
27
+ export type RouteEntry = {
28
+ pattern: string;
29
+ loader: () => Promise<RouteModule>;
30
+ /** Number of static segments (for sort order). */
31
+ staticSegments: number;
32
+ /** True if pattern ends with catch-all. */
33
+ catchAll: boolean;
34
+ };
@@ -0,0 +1,45 @@
1
+ /** Origin: protocol + host (e.g. https://localhost:5173 or https://example.com). */
2
+ export function base(): string {
3
+ return typeof location !== 'undefined' ? location.origin : '';
4
+ }
5
+
6
+ /** Current pathname (e.g. /posts/123). */
7
+ export function path(p?: string): string {
8
+ const raw = p ?? (typeof location !== 'undefined' ? location.pathname || '/' : '/');
9
+ return raw.startsWith('/') ? raw : `/${raw}`;
10
+ }
11
+
12
+ /** Full path + search + hash. No args = current. */
13
+ export function href(p?: string): string {
14
+ const basePath = path(p);
15
+ if (typeof location === 'undefined') return basePath;
16
+ return basePath + (location.search ?? '') + (location.hash ?? '');
17
+ }
18
+
19
+ /** Query (after ?). query() → all as object, query('key') → one value. */
20
+ export function query(): Record<string, string>;
21
+ export function query(name: string): string | null;
22
+ export function query(name?: string): Record<string, string> | string | null {
23
+ const s = typeof location !== 'undefined' ? location.search : '';
24
+ const p = new URLSearchParams(s);
25
+ if (name !== undefined) return p.get(name);
26
+ const out: Record<string, string> = {};
27
+ p.forEach((v, k) => (out[k] = v));
28
+ return out;
29
+ }
30
+
31
+ /** New path with query updated. go(setQuery({ page: '2' })) to navigate. */
32
+ export function setQuery(
33
+ obj: Record<string, string | number | boolean | undefined | null>,
34
+ p?: string
35
+ ): string {
36
+ const basePath = path(p);
37
+ const current = query();
38
+ const next = { ...current };
39
+ for (const [key, value] of Object.entries(obj)) {
40
+ if (value === undefined || value === null) delete next[key];
41
+ else next[key] = String(value);
42
+ }
43
+ const search = new URLSearchParams(next).toString();
44
+ return search ? `${basePath}?${search}` : basePath;
45
+ }
@@ -1,16 +1,30 @@
1
- export { SHORTHAND_MAP, isStyleKey, getStyleProp, toStyleValue } from './shorthand.js';
2
- export type { StyleShorthandKey } from './shorthand.js';
3
- export {
4
- VIEWPORT_KEYS,
5
- PSEUDO_KEYS,
6
- VIEWPORT_WIDTHS,
7
- getViewportKeyFromWidth,
8
- } from './breakpoints.js';
1
+ // ───────────────────────────────────────────────────────────────────────────────
2
+ // For other frameworks for wrapping App
3
+ // <FlowStyle> <App /> </FlowStyle>
4
+ // ───────────────────────────────────────────────────────────────────────────────
5
+ export { flowStyle as FlowStyle } from './resolve.js';
6
+
7
+
8
+ // ───────────────────────────────────────────────────────────────────────────────
9
+ // Visual builder (UI)
10
+ // ───────────────────────────────────────────────────────────────────────────────
11
+ export { VisualStyleBuilder, initVisualStyleBuilder } from './visual-builder.js';
12
+ export type { VisualStyleBuilderOptions } from './visual-builder.js';
13
+
14
+ // Plugin Vite (node only): usa import('@flow.os/style/vite-plugin').styleFlowServerPlugin()
15
+
16
+ // ───────────────────────────────────────────────────────────────────────────────
17
+ // Flow Style Builder – sempre esportato da style; pulsante trascinabile + pannello
18
+ // ───────────────────────────────────────────────────────────────────────────────
19
+ export { initFlowStyleBuilder, flowStyleBuilder } from './style-builder/index.js';
20
+ export type { FlowStyleBuilderOptions, FlowStyleBuilderPosition } from './style-builder/index.js';
21
+
22
+
23
+ // ───────────────────────────────────────────────────────────────────────────────
24
+ // For @flow.os/client internal use
25
+ // ───────────────────────────────────────────────────────────────────────────────
26
+ export { resolveLayer, resolvePseudoStyle, styleToCssText } from './resolve.js';
27
+ export { VIEWPORT_KEYS, PSEUDO_KEYS, getViewportKeyFromWidth } from './breakpoints.js';
28
+ export type { PlainLayer } from './resolve.js';
9
29
  export type { ViewportKey, PseudoKey } from './breakpoints.js';
10
- export {
11
- resolveLayer,
12
- mergeResolved,
13
- resolvePseudoStyle,
14
- styleToCssText,
15
- } from './resolve.js';
16
- export type { PlainLayer, ResolvedLayer } from './resolve.js';
30
+ export type { StyleShorthandKey } from './shorthand.js';
@@ -10,6 +10,11 @@
10
10
  "types": "./index.ts",
11
11
  "import": "./index.ts",
12
12
  "default": "./index.ts"
13
+ },
14
+ "./vite-plugin": {
15
+ "types": "./vite-plugin.ts",
16
+ "import": "./vite-plugin.ts",
17
+ "default": "./vite-plugin.ts"
13
18
  }
14
19
  }
15
- }
20
+ }