@flow.os/client 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.
package/build/config.ts CHANGED
@@ -27,8 +27,8 @@ const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`;
27
27
  const bold = (s: string) => `\x1b[1m${s}\x1b[0m`;
28
28
  const dim = (s: string) => `\x1b[2m${s}\x1b[0m`;
29
29
  const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*m/g, '');
30
- const CONTENT_W = 48;
31
- const BORDER_W = 52;
30
+ const MIN_CONTENT_W = 40;
31
+ const EXTRA_BOX_WIDTH = 20;
32
32
 
33
33
  function getNetworkUrl(port: number): string | null {
34
34
  const ifaces = os.networkInterfaces();
@@ -68,25 +68,32 @@ function resolveAlias(): Record<string, string> {
68
68
  return alias;
69
69
  }
70
70
 
71
+ const FLOW_STYLE_POSITIONS = ['top-left', 'top-right', 'bottom-left', 'bottom-right'] as const;
72
+ export type FlowStyleBuilderPosition = (typeof FLOW_STYLE_POSITIONS)[number];
73
+
71
74
  export type FlowConfigOptions = {
72
75
  /** Porta del dev server. Default: 3000 */
73
76
  port?: number;
74
77
  /** Esporre su rete. Default: true */
75
78
  host?: boolean;
76
79
  /**
77
- * Backend in dev: passa flowServer() per usare il server Flow (API da server/routes).
78
- * Per Go, Rust, Nitro o altro: non passare server e avvia il backend separatamente;
79
- * opzionalmente aggiungi in plugins un proxy verso la porta del tuo backend.
80
+ * Backend in dev: true = server Flow (come flowServer() da @flow.os/server), false/undefined = nessuno.
81
+ * Puoi anche passare un plugin Vite (es. server: flowServer() da @flow.os/server).
80
82
  */
81
- server?: Plugin;
83
+ server?: Plugin | boolean;
82
84
  /** Plugin Vite aggiuntivi. Default: [] */
83
85
  plugins?: Plugin[];
86
+ /**
87
+ * In dev: Flow Style Builder. Passa la posizione (es. 'bottom-right').
88
+ * Se non lo usi, non mettere la proprietà. Per i plugin Vite: flowStyleBuilder('bottom-right') da @flow.os/style.
89
+ */
90
+ flowStyleBuilder?: FlowStyleBuilderPosition;
84
91
  };
85
92
 
86
93
  const DEFAULTS = {
87
94
  port: 3000,
88
95
  host: true,
89
- server: undefined as Plugin | undefined,
96
+ server: undefined as Plugin | boolean | undefined,
90
97
  plugins: [] as Plugin[],
91
98
  };
92
99
 
@@ -103,22 +110,37 @@ function flowServerUrlPlugin(hostEnabled: boolean): Plugin {
103
110
  const localHost = host === '::' || host === '0.0.0.0' ? 'localhost' : host;
104
111
  const localUrl = `http://${localHost}:${port}/`;
105
112
  const networkUrl = hostEnabled ? getNetworkUrl(port) : null;
113
+ const styleServerUrl = (server as { __flowStyleServerUrl?: string }).__flowStyleServerUrl;
114
+
115
+ const styleLine = styleServerUrl ? dim('Style server: ') + cyan(styleServerUrl) : '';
116
+ const contents = [
117
+ bold('Flow OS'),
118
+ cyan('🏠') + ' ' + dim('Local: ') + cyan(localUrl),
119
+ networkUrl ? cyan('🌐') + ' ' + dim('Network: ') + cyan(networkUrl) : '',
120
+ styleLine,
121
+ ].filter(Boolean);
122
+ // Larghezza fissa: max lunghezza testo + extra così il rettangolo chiude bene a destra
123
+ const contentWidth =
124
+ Math.max(MIN_CONTENT_W, ...contents.map((c) => stripAnsi(c).length)) + EXTRA_BOX_WIDTH;
125
+ const borderW = contentWidth + 3;
106
126
 
107
127
  const line = (content: string) => {
108
- const pad = Math.max(0, CONTENT_W - stripAnsi(content).length);
109
- return dim(' ') + content + ' '.repeat(pad) + dim(' │');
128
+ const pad = Math.max(0, contentWidth - stripAnsi(content).length);
129
+ return dim(' ') + content + ' '.repeat(pad) + dim(' │');
110
130
  };
111
- const border = (c: string) => dim(' ' + c + '─'.repeat(BORDER_W) + (c === '╭' ? '╮' : '╯'));
112
- const out: string[] = [
113
- '',
114
- border('╭'),
115
- line(bold('Flow OS') + dim(' is running')),
116
- line(cyan('◆') + ' ' + dim('Local: ') + cyan(localUrl)),
117
- ...(networkUrl ? [line(dim('Network: ') + cyan(networkUrl))] : []),
118
- border(''),
119
- '',
131
+ const top = dim('') + '─'.repeat(borderW) + dim('╮');
132
+ const bottom = dim(' ╰') + '─'.repeat(borderW) + dim('╯');
133
+
134
+ const lines = [
135
+ top,
136
+ line(bold('Flow OS')),
137
+ line(cyan('🏠') + ' ' + dim('Local: ') + cyan(localUrl)),
138
+ networkUrl ? line(cyan('🌐') + ' ' + dim('Network: ') + cyan(networkUrl)) : line(''),
139
+ ...(styleLine ? [line(styleLine)] : []),
140
+ bottom,
120
141
  ];
121
- console.log(out.join('\n'));
142
+ // Sovrascrive le 2 righe sopra (comando shell + echo di bun)
143
+ process.stdout.write('\x1b[2A\r' + lines.join('\n') + '\n');
122
144
  });
123
145
  },
124
146
  };
@@ -128,7 +150,13 @@ function flowServerUrlPlugin(hostEnabled: boolean): Plugin {
128
150
  export default function config(
129
151
  options: FlowConfigOptions = {}
130
152
  ): () => Promise<ReturnType<typeof import('vite').defineConfig>> {
131
- const { port: optPort = DEFAULTS.port, host = DEFAULTS.host, server = DEFAULTS.server, plugins = DEFAULTS.plugins } = options;
153
+ const {
154
+ port: optPort = DEFAULTS.port,
155
+ host = DEFAULTS.host,
156
+ server = DEFAULTS.server,
157
+ plugins = DEFAULTS.plugins,
158
+ flowStyleBuilder,
159
+ } = options;
132
160
  return async () => {
133
161
  let port: number;
134
162
  if (process.env.FLOW_DEV_PORT) {
@@ -136,10 +164,27 @@ export default function config(
136
164
  } else {
137
165
  port = await findFreePort(3000, 3020);
138
166
  }
167
+ const serverPlugin: Plugin | undefined =
168
+ server === true
169
+ ? (await import('@flow.os/server')).flowServer()
170
+ : typeof server === 'object' && server != null
171
+ ? server
172
+ : undefined;
173
+ const flowStyleBuilderPosition: FlowStyleBuilderPosition | undefined =
174
+ typeof flowStyleBuilder === 'string' && (FLOW_STYLE_POSITIONS as readonly string[]).includes(flowStyleBuilder)
175
+ ? flowStyleBuilder
176
+ : undefined;
139
177
  const { flow } = await import('./vite.js');
178
+ const stylePlugin =
179
+ flowStyleBuilderPosition != null
180
+ ? (await import('@flow.os/style/vite-plugin')).styleFlowServerPlugin()
181
+ : undefined;
140
182
  const allPlugins = [
183
+ ...(stylePlugin ? [stylePlugin] : []),
141
184
  flowServerUrlPlugin(host === true),
142
- ...(server ? [server, flow(), ...plugins] : [flow(), ...plugins]),
185
+ ...(serverPlugin
186
+ ? [serverPlugin, flow({ flowStyleBuilder: flowStyleBuilderPosition }), ...plugins]
187
+ : [flow({ flowStyleBuilder: flowStyleBuilderPosition }), ...plugins]),
143
188
  ];
144
189
  const alias = resolveAlias();
145
190
  const baseLogger = createLogger('info');
@@ -147,7 +192,7 @@ export default function config(
147
192
  ...baseLogger,
148
193
  info: (msg: string, opts?: { clear?: boolean }) => {
149
194
  const t = msg.replace(/\s+/g, ' ');
150
- if (/VITE\s+v?\d|ready\s+in|Local:\s*http|Network:\s*http|press\s+h\s+enter|Port\s+\d+\s+is\s+in\s+use/i.test(t)) return;
195
+ if (/VITE\s+v?\d|ready\s+in|Local:\s*http|Network:\s*http|press\s+h\s+enter|Port\s+\d+\s+is\s+in\s+use|optimizing\s+dependencies|re-optimizing/i.test(t)) return;
151
196
  baseLogger.info(msg, opts);
152
197
  },
153
198
  };
package/build/vite.ts CHANGED
@@ -4,12 +4,49 @@ import { resolve } from 'path';
4
4
  import type { Plugin } from 'vite';
5
5
 
6
6
  const FLOW_ENTRY_ID = '\0flow-entry';
7
- const ENTRY_CODE = `import * as root from '/client/root.tsx';
8
- import { run } from '@flow.os/router';
9
- const opts = {};
10
- if (root.fallback != null) opts.fallback = root.fallback;
11
- if (root.notFound != null) opts.notFound = root.notFound;
12
- run(root.default, import.meta.glob('/client/routes/**/*.tsx'), Object.keys(opts).length ? opts : undefined);
7
+
8
+ const POSITIONS = ['top-left', 'top-right', 'bottom-left', 'bottom-right'] as const;
9
+ type Position = (typeof POSITIONS)[number];
10
+
11
+ function isPosition(s: unknown): s is Position {
12
+ return typeof s === 'string' && (POSITIONS as readonly string[]).includes(s);
13
+ }
14
+
15
+ function getEntryWithBuilder(position: Position): string {
16
+ const pos = JSON.stringify(position);
17
+ return `(async () => {
18
+ const isPreview = typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('preview');
19
+ if (import.meta.env.DEV && !isPreview) {
20
+ const app = document.getElementById('app');
21
+ if (app) {
22
+ app.innerHTML = '';
23
+ app.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100vh;z-index:1';
24
+ const iframe = document.createElement('iframe');
25
+ iframe.setAttribute('data-flow-preview', '1');
26
+ iframe.src = window.location.pathname + '?preview=1' + (window.location.hash || '');
27
+ iframe.style.cssText = 'width:100%;height:100%;border:0';
28
+ app.appendChild(iframe);
29
+ const { initFlowStyleBuilder } = await import('@flow.os/style');
30
+ initFlowStyleBuilder({ previewIframe: iframe, position: ${pos} });
31
+ }
32
+ return;
33
+ }
34
+ const root = await import('/client/root.tsx');
35
+ const { run } = await import('@flow.os/router');
36
+ const opts = {};
37
+ if (root.fallback != null) opts.fallback = root.fallback;
38
+ run(root.default, import.meta.glob('/client/routes/**/*.tsx'), Object.keys(opts).length ? opts : undefined);
39
+ })();
40
+ `;
41
+ }
42
+
43
+ const ENTRY_DEFAULT = `(async () => {
44
+ const root = await import('/client/root.tsx');
45
+ const { run } = await import('@flow.os/router');
46
+ const opts = {};
47
+ if (root.fallback != null) opts.fallback = root.fallback;
48
+ run(root.default, import.meta.glob('/client/routes/**/*.tsx'), Object.keys(opts).length ? opts : undefined);
49
+ })();
13
50
  `;
14
51
 
15
52
  /** Reset stili browser: niente margin/padding di default, layout full viewport. */
@@ -21,7 +58,14 @@ const HTML = `<!DOCTYPE html>
21
58
  <body><div id="app"></div><script type="module" src="/entry.tsx"></script></body>
22
59
  </html>`;
23
60
 
24
- export function flow(): Plugin {
61
+ export type FlowPluginOptions = {
62
+ flowStyleBuilder?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
63
+ };
64
+
65
+ export function flow(options: FlowPluginOptions = {}): Plugin {
66
+ const flowStyleBuilder = options.flowStyleBuilder;
67
+ const position: Position | undefined = flowStyleBuilder != null && isPosition(flowStyleBuilder) ? flowStyleBuilder : undefined;
68
+ const entryCode = position != null ? getEntryWithBuilder(position) : ENTRY_DEFAULT;
25
69
  const cwd = process.cwd();
26
70
  return {
27
71
  name: 'flow',
@@ -41,7 +85,7 @@ export function flow(): Plugin {
41
85
  return id === '/entry.tsx' ? FLOW_ENTRY_ID : null;
42
86
  },
43
87
  load(id) {
44
- return id === FLOW_ENTRY_ID ? ENTRY_CODE : null;
88
+ return id === FLOW_ENTRY_ID ? entryCode : null;
45
89
  },
46
90
  buildEnd(err) {
47
91
  if (err) {
package/features/attrs.ts CHANGED
@@ -2,7 +2,7 @@ import { effect } from '@flow.os/core';
2
2
  import { isGetter } from './utils.js';
3
3
 
4
4
  /** Props gestite altrove (non passare qui). */
5
- const SKIP = new Set(['children', 'class', 'className', 'classList', 'classFlow', 'style', 'styleFlow']);
5
+ const SKIP = new Set(['children', 'class', 'className', 'classList', 'classFlow', 'style', 's', 'styleFlow']);
6
6
 
7
7
  export function applyAttrs(el: HTMLElement, rest: Record<string, unknown>): void {
8
8
  for (const [k, v] of Object.entries(rest)) {
package/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /// <reference path="./runtime/jsx-types.d.ts" />
2
- // API pubblica: tutto da qui o da subpath (./config, ./vite, ./jsx-runtime, ./jsx-dev-runtime)
2
+ // Like Tailwind: one package. Use s or styleFlow in JSX; FlowStyle() for imperative / other frameworks.
3
3
  export { Fragment, jsx, jsxs, jsxDEV } from './runtime/jsx.js';
4
4
  export { normalizeChild, setContent } from './runtime/dom.js';
5
5
  export { default as config } from './build/config.js';
6
6
  export { applyClassFlow, applyClassAndClassList, applyStyle, applyStyleFlow, applyAttrs } from './features/index.js';
7
+ export { FlowStyle } from '@flow.os/style';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flow.os/client",
3
- "version": "0.0.1-dev.1771785969",
3
+ "version": "0.0.1-dev.1771840262",
4
4
  "license": "PolyForm-Shield-1.0.0",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -49,6 +49,8 @@ declare global {
49
49
  class?: string | (() => string) | (string | (() => string))[];
50
50
  classList?: Record<string, boolean | (() => boolean)>;
51
51
  classFlow?: (() => Record<string, string | number>) | Record<string, ClassFlowValue>;
52
+ /** Style layer: string | [base, layer] | layer. Use s or styleFlow, same thing. */
53
+ s?: StyleFlow;
52
54
  styleFlow?: StyleFlow;
53
55
  style?: Record<string, string | number | (() => string | number)>;
54
56
  children?: unknown;
package/runtime/jsx.ts CHANGED
@@ -54,11 +54,13 @@ function applyProps(el: HTMLElement, props: Props): void {
54
54
  classList,
55
55
  classFlow,
56
56
  style: styleProp,
57
+ s,
57
58
  styleFlow,
58
59
  ...rest
59
60
  } = props;
60
- if (styleFlow != null) {
61
- applyStyleFlow(el, styleFlow);
61
+ const styleFlowValue = s ?? styleFlow;
62
+ if (styleFlowValue != null) {
63
+ applyStyleFlow(el, styleFlowValue);
62
64
  } else {
63
65
  applyClassFlow(el, classFlow);
64
66
  applyClassAndClassList(el, classProp, classNameProp, classList);