@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 +67 -22
- package/build/vite.ts +52 -8
- package/features/attrs.ts +1 -1
- package/index.ts +2 -1
- package/package.json +1 -1
- package/runtime/jsx-types.d.ts +2 -0
- package/runtime/jsx.ts +4 -2
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
|
|
31
|
-
const
|
|
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:
|
|
78
|
-
*
|
|
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,
|
|
109
|
-
return dim('
|
|
128
|
+
const pad = Math.max(0, contentWidth - stripAnsi(content).length);
|
|
129
|
+
return dim(' │ ') + content + ' '.repeat(pad) + dim(' │');
|
|
110
130
|
};
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
line(
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
...(
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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 ?
|
|
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
|
-
//
|
|
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
package/runtime/jsx-types.d.ts
CHANGED
|
@@ -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
|
-
|
|
61
|
-
|
|
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);
|