create-flow-os 0.0.1-dev.1771614697
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 +4 -0
- package/config.json +54 -0
- package/flow.config.client.ts +3 -0
- package/gen.ts +163 -0
- package/index.ts +175 -0
- package/package.json +29 -0
- package/profiles/client/.dockerignore +12 -0
- package/profiles/client/.oxfmtrc.json +7 -0
- package/profiles/client/.oxlintrc.json +13 -0
- package/profiles/client/.vscode/settings.json +12 -0
- package/profiles/client/Dockerfile +26 -0
- package/profiles/client/bun.lock +334 -0
- package/profiles/client/client/root.css +9 -0
- package/profiles/client/client/root.tsx +17 -0
- package/profiles/client/client/routes/about.tsx +22 -0
- package/profiles/client/client/routes/index.tsx +48 -0
- package/profiles/client/flow.config.ts +3 -0
- package/profiles/client/index.html +5 -0
- package/profiles/client/package.json +32 -0
- package/profiles/client/packages/client/config.ts +68 -0
- package/profiles/client/packages/client/dom.ts +5 -0
- package/profiles/client/packages/client/features/attrs.ts +32 -0
- package/profiles/client/packages/client/features/class-flow.ts +116 -0
- package/profiles/client/packages/client/features/index.ts +8 -0
- package/profiles/client/packages/client/features/pseudo-injector.ts +40 -0
- package/profiles/client/packages/client/features/style-flow.ts +106 -0
- package/profiles/client/packages/client/features/style.ts +27 -0
- package/profiles/client/packages/client/features/utils.ts +4 -0
- package/profiles/client/packages/client/features/viewport.ts +20 -0
- package/profiles/client/packages/client/index.ts +4 -0
- package/profiles/client/packages/client/jsx-dev-runtime.ts +1 -0
- package/profiles/client/packages/client/jsx-runtime.ts +1 -0
- package/profiles/client/packages/client/jsx-types.d.ts +64 -0
- package/profiles/client/packages/client/jsx.ts +99 -0
- package/profiles/client/packages/client/package.json +42 -0
- package/profiles/client/packages/client/vite.ts +42 -0
- package/profiles/client/tsconfig.json +30 -0
- package/utils.ts +74 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { effect } from '@flow-os';
|
|
2
|
+
import { isGetter } from './utils.js';
|
|
3
|
+
|
|
4
|
+
export function toStyleValue(v: unknown): string {
|
|
5
|
+
return typeof v === 'number' ? `${v}px` : String(v ?? '');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function setStyleProp(el: HTMLElement, key: string, value: string): void {
|
|
9
|
+
if (key.startsWith('--')) el.style.setProperty(key, value);
|
|
10
|
+
else (el.style as unknown as Record<string, string>)[key] = value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const CLASS_FLOW_MAP: Record<string, string> = {
|
|
14
|
+
text: 'fontSize',
|
|
15
|
+
mb: 'marginBottom',
|
|
16
|
+
mt: 'marginTop',
|
|
17
|
+
ml: 'marginLeft',
|
|
18
|
+
mr: 'marginRight',
|
|
19
|
+
mx: 'marginLeft',
|
|
20
|
+
my: 'marginTop',
|
|
21
|
+
m: 'margin',
|
|
22
|
+
pb: 'paddingBottom',
|
|
23
|
+
pt: 'paddingTop',
|
|
24
|
+
pl: 'paddingLeft',
|
|
25
|
+
pr: 'paddingRight',
|
|
26
|
+
px: 'paddingLeft',
|
|
27
|
+
py: 'paddingTop',
|
|
28
|
+
p: 'padding',
|
|
29
|
+
w: 'width',
|
|
30
|
+
h: 'height',
|
|
31
|
+
minW: 'minWidth',
|
|
32
|
+
minH: 'minHeight',
|
|
33
|
+
maxW: 'maxWidth',
|
|
34
|
+
maxH: 'maxHeight',
|
|
35
|
+
gap: 'gap',
|
|
36
|
+
rounded: 'borderRadius',
|
|
37
|
+
top: 'top',
|
|
38
|
+
left: 'left',
|
|
39
|
+
right: 'right',
|
|
40
|
+
bottom: 'bottom',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function resolveClassFlowKey(k: string): string {
|
|
44
|
+
if (k.startsWith('--')) return k;
|
|
45
|
+
return CLASS_FLOW_MAP[k] ?? k;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isClassFlowReactive(val: unknown): boolean {
|
|
49
|
+
return isGetter(val) || (Array.isArray(val) && val.length === 2 && isGetter(val[0]));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function resolveClassFlowValue(val: unknown): string {
|
|
53
|
+
if (isGetter(val)) return toStyleValue((val as () => unknown)());
|
|
54
|
+
if (Array.isArray(val) && val.length === 2 && isGetter(val[0]))
|
|
55
|
+
return String((val[0] as () => unknown)()) + String(val[1]);
|
|
56
|
+
return toStyleValue(val);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function applyClassFlowObj(el: HTMLElement, obj: Record<string, unknown>): void {
|
|
60
|
+
for (const [k, val] of Object.entries(obj))
|
|
61
|
+
setStyleProp(el, resolveClassFlowKey(k), resolveClassFlowValue(val));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function applyClassFlow(el: HTMLElement, classFlowVal: unknown): void {
|
|
65
|
+
if (classFlowVal == null) return;
|
|
66
|
+
if (isGetter(classFlowVal)) {
|
|
67
|
+
effect(() => applyClassFlowObj(el, (classFlowVal as () => Record<string, unknown>)()));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (typeof classFlowVal !== 'object' || Array.isArray(classFlowVal) || classFlowVal instanceof Node) return;
|
|
71
|
+
const obj = classFlowVal as Record<string, unknown>;
|
|
72
|
+
const hasReactive = Object.values(obj).some(isClassFlowReactive);
|
|
73
|
+
if (hasReactive) {
|
|
74
|
+
effect(() => {
|
|
75
|
+
for (const [k, val] of Object.entries(obj))
|
|
76
|
+
setStyleProp(el, resolveClassFlowKey(k), resolveClassFlowValue(val));
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
applyClassFlowObj(el, obj);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function applyClassAndClassList(
|
|
84
|
+
el: HTMLElement,
|
|
85
|
+
classVal: unknown,
|
|
86
|
+
classNameVal: unknown,
|
|
87
|
+
classListVal: unknown
|
|
88
|
+
): void {
|
|
89
|
+
const base = classVal ?? classNameVal;
|
|
90
|
+
if (base == null && (classListVal == null || typeof classListVal !== 'object')) return;
|
|
91
|
+
effect(() => {
|
|
92
|
+
let baseStr = '';
|
|
93
|
+
if (base != null) {
|
|
94
|
+
if (Array.isArray(base)) {
|
|
95
|
+
baseStr = base
|
|
96
|
+
.map((item) => (isGetter(item) ? String((item as () => unknown)()) : String(item)))
|
|
97
|
+
.filter(Boolean)
|
|
98
|
+
.join(' ');
|
|
99
|
+
} else if (isGetter(base)) {
|
|
100
|
+
baseStr = String((base as () => unknown)());
|
|
101
|
+
} else {
|
|
102
|
+
baseStr = String(base);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const list = classListVal as Record<string, boolean | (() => boolean)> | undefined;
|
|
106
|
+
const truthy: string[] = [];
|
|
107
|
+
if (list && typeof list === 'object' && !(list instanceof Node)) {
|
|
108
|
+
for (const [cls, val] of Object.entries(list)) {
|
|
109
|
+
const v = isGetter(val) ? (val as () => boolean)() : val;
|
|
110
|
+
if (v) truthy.push(cls);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const full = [baseStr, ...truthy].filter(Boolean).join(' ');
|
|
114
|
+
el.setAttribute('class', full);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature applicate a div, p, button, ecc.
|
|
3
|
+
* Un file per feature; qui si raggruppano e si espongono a jsx.
|
|
4
|
+
*/
|
|
5
|
+
export { applyClassFlow, applyClassAndClassList } from './class-flow.js';
|
|
6
|
+
export { applyStyle } from './style.js';
|
|
7
|
+
export { applyStyleFlow } from './style-flow.js';
|
|
8
|
+
export { applyAttrs } from './attrs.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { styleToCssText } from '@flow-os/style';
|
|
2
|
+
import type { PseudoKey } from '@flow-os/style';
|
|
3
|
+
|
|
4
|
+
const PSEUDO_SELECTOR: Record<PseudoKey, string> = {
|
|
5
|
+
hover: ':hover',
|
|
6
|
+
active: ':active',
|
|
7
|
+
focus: ':focus',
|
|
8
|
+
focusVisible: ':focus-visible',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let styleEl: HTMLStyleElement | null = null;
|
|
12
|
+
const cache = new Map<string, string>();
|
|
13
|
+
let id = 0;
|
|
14
|
+
|
|
15
|
+
function ensureStyleEl(): HTMLStyleElement {
|
|
16
|
+
if (styleEl) return styleEl;
|
|
17
|
+
styleEl = document.createElement('style');
|
|
18
|
+
styleEl.setAttribute('data-flow-style', '');
|
|
19
|
+
document.head.appendChild(styleEl);
|
|
20
|
+
return styleEl;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Restituisce className da aggiungere all'elemento. La regola .sf-{pseudo}-{id}:{pseudo} { ... } è iniettata una sola volta. */
|
|
24
|
+
export function getPseudoClass(
|
|
25
|
+
pseudo: PseudoKey,
|
|
26
|
+
style: Record<string, string>
|
|
27
|
+
): string {
|
|
28
|
+
const cssText = styleToCssText(style);
|
|
29
|
+
if (!cssText) return '';
|
|
30
|
+
const key = `${pseudo}:${cssText}`;
|
|
31
|
+
let className = cache.get(key);
|
|
32
|
+
if (className) return className;
|
|
33
|
+
className = `sf-${pseudo}-${++id}`;
|
|
34
|
+
cache.set(key, className);
|
|
35
|
+
const selector = `.${className}${PSEUDO_SELECTOR[pseudo]}`;
|
|
36
|
+
const rule = `${selector} { ${cssText} }`;
|
|
37
|
+
const el = ensureStyleEl();
|
|
38
|
+
el.appendChild(document.createTextNode(rule));
|
|
39
|
+
return className;
|
|
40
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { effect } from '@flow-os';
|
|
2
|
+
import { isGetter } from './utils.js';
|
|
3
|
+
import {
|
|
4
|
+
VIEWPORT_KEYS,
|
|
5
|
+
PSEUDO_KEYS,
|
|
6
|
+
resolveLayer,
|
|
7
|
+
resolvePseudoStyle,
|
|
8
|
+
type PlainLayer,
|
|
9
|
+
type ViewportKey,
|
|
10
|
+
type PseudoKey,
|
|
11
|
+
} from '@flow-os/style';
|
|
12
|
+
import { getViewportBreakpoint } from './viewport.js';
|
|
13
|
+
import { getPseudoClass } from './pseudo-injector.js';
|
|
14
|
+
|
|
15
|
+
const RESERVED = new Set<string>([...VIEWPORT_KEYS, ...PSEUDO_KEYS]);
|
|
16
|
+
|
|
17
|
+
/** Chiama getter e restituisce oggetto plain (una livello, escluse chiavi reserved). */
|
|
18
|
+
function gatherPlain(obj: Record<string, unknown> | null | undefined): PlainLayer {
|
|
19
|
+
const out: PlainLayer = {};
|
|
20
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return out;
|
|
21
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
22
|
+
if (RESERVED.has(k)) continue;
|
|
23
|
+
if (v === undefined || v === null) continue;
|
|
24
|
+
if (isGetter(v)) {
|
|
25
|
+
const x = (v as () => unknown)();
|
|
26
|
+
if (typeof x === 'boolean') out[k] = x;
|
|
27
|
+
else if (typeof x === 'number' || typeof x === 'string') out[k] = x;
|
|
28
|
+
else if (Array.isArray(x) && x.length === 2 && isGetter(x[0]))
|
|
29
|
+
out[k] = String((x[0] as () => unknown)()) + String(x[1]);
|
|
30
|
+
else out[k] = String(x);
|
|
31
|
+
} else if (typeof v === 'object' && v !== null && !(v instanceof Node)) {
|
|
32
|
+
if (k === 'base') out['base'] = (v as Record<string, string>)['base'] ?? String(v);
|
|
33
|
+
// nested layer (mob/tab/des/hover/...) già esclusi da RESERVED
|
|
34
|
+
} else {
|
|
35
|
+
out[k] = v as string | number | boolean;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Normalizza value in base string + flat object. */
|
|
42
|
+
function normalize(
|
|
43
|
+
value: unknown
|
|
44
|
+
): { base: string; flat: Record<string, unknown> } {
|
|
45
|
+
if (value == null) return { base: '', flat: {} };
|
|
46
|
+
if (typeof value === 'string') return { base: value, flat: {} };
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
const base = typeof value[0] === 'string' ? value[0] : '';
|
|
49
|
+
const flat = (value[1] && typeof value[1] === 'object' && !Array.isArray(value[1]))
|
|
50
|
+
? (value[1] as Record<string, unknown>)
|
|
51
|
+
: {};
|
|
52
|
+
return { base, flat };
|
|
53
|
+
}
|
|
54
|
+
if (typeof value === 'object' && !Array.isArray(value) && !(value instanceof Node)) {
|
|
55
|
+
const flat = value as Record<string, unknown>;
|
|
56
|
+
const base = typeof flat['base'] === 'string' ? flat['base'] : '';
|
|
57
|
+
return { base, flat };
|
|
58
|
+
}
|
|
59
|
+
return { base: '', flat: {} };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function applyStyleFlow(el: HTMLElement, value: unknown): void {
|
|
63
|
+
if (value == null) return;
|
|
64
|
+
const { base, flat } = normalize(value);
|
|
65
|
+
let prevStyleKeys: string[] = [];
|
|
66
|
+
|
|
67
|
+
effect(() => {
|
|
68
|
+
const viewport: ViewportKey = getViewportBreakpoint();
|
|
69
|
+
const plainBase = gatherPlain(flat);
|
|
70
|
+
if (base) plainBase.base = (plainBase.base ? `${plainBase.base} ` : '') + base;
|
|
71
|
+
const viewportLayer = flat[viewport];
|
|
72
|
+
const plainViewport = gatherPlain(
|
|
73
|
+
viewportLayer && typeof viewportLayer === 'object' && !Array.isArray(viewportLayer)
|
|
74
|
+
? (viewportLayer as Record<string, unknown>)
|
|
75
|
+
: {}
|
|
76
|
+
);
|
|
77
|
+
const merged: PlainLayer = { ...plainBase };
|
|
78
|
+
if (plainViewport.base) merged.base = (merged.base ? `${merged.base} ` : '') + plainViewport.base;
|
|
79
|
+
for (const [k, v] of Object.entries(plainViewport)) {
|
|
80
|
+
if (k === 'base') continue;
|
|
81
|
+
(merged as Record<string, unknown>)[k] = v;
|
|
82
|
+
}
|
|
83
|
+
const resolved = resolveLayer(merged);
|
|
84
|
+
|
|
85
|
+
const pseudoClasses: string[] = [];
|
|
86
|
+
for (const pseudo of PSEUDO_KEYS) {
|
|
87
|
+
const rawPseudo = flat[pseudo];
|
|
88
|
+
const plainPseudo = gatherPlain(
|
|
89
|
+
rawPseudo && typeof rawPseudo === 'object' && !Array.isArray(rawPseudo)
|
|
90
|
+
? (rawPseudo as Record<string, unknown>)
|
|
91
|
+
: {}
|
|
92
|
+
);
|
|
93
|
+
const stylePseudo = resolvePseudoStyle(plainPseudo);
|
|
94
|
+
const cls = getPseudoClass(pseudo as PseudoKey, stylePseudo);
|
|
95
|
+
if (cls) pseudoClasses.push(cls);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
el.className = [resolved.class, ...pseudoClasses].filter(Boolean).join(' ');
|
|
99
|
+
for (const k of prevStyleKeys) el.style.removeProperty(k);
|
|
100
|
+
prevStyleKeys = Object.keys(resolved.style);
|
|
101
|
+
for (const [k, v] of Object.entries(resolved.style)) {
|
|
102
|
+
if (k.startsWith('--')) el.style.setProperty(k, v);
|
|
103
|
+
else (el.style as unknown as Record<string, string>)[k] = v;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { effect } from '@flow-os';
|
|
2
|
+
import { isGetter } from './utils.js';
|
|
3
|
+
import { setStyleProp, toStyleValue } from './class-flow.js';
|
|
4
|
+
|
|
5
|
+
export function applyStyle(el: HTMLElement, styleVal: unknown): void {
|
|
6
|
+
if (styleVal == null) return;
|
|
7
|
+
if (isGetter(styleVal)) {
|
|
8
|
+
effect(() => {
|
|
9
|
+
const style = (styleVal as () => Record<string, string> | string)();
|
|
10
|
+
if (typeof style === 'string') {
|
|
11
|
+
el.setAttribute('style', style);
|
|
12
|
+
} else if (style && typeof style === 'object') {
|
|
13
|
+
for (const [sk, sv] of Object.entries(style)) setStyleProp(el, sk, toStyleValue(sv));
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (typeof styleVal !== 'object' || styleVal === null || styleVal instanceof Node) return;
|
|
19
|
+
const style = styleVal as Record<string, string | number | (() => string | number)>;
|
|
20
|
+
for (const [sk, sv] of Object.entries(style)) {
|
|
21
|
+
if (isGetter(sv)) {
|
|
22
|
+
effect(() => setStyleProp(el, sk, toStyleValue((sv as () => unknown)())));
|
|
23
|
+
} else {
|
|
24
|
+
setStyleProp(el, sk, toStyleValue(sv));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { state } from '@flow-os';
|
|
2
|
+
import type { ViewportKey } from '@flow-os/style';
|
|
3
|
+
import { getViewportKeyFromWidth } from '@flow-os/style';
|
|
4
|
+
|
|
5
|
+
const [getViewport, setViewport] = state<ViewportKey>('mob');
|
|
6
|
+
let subscribed = false;
|
|
7
|
+
|
|
8
|
+
function subscribe(): void {
|
|
9
|
+
if (subscribed) return;
|
|
10
|
+
subscribed = true;
|
|
11
|
+
const update = () => setViewport(getViewportKeyFromWidth(window.innerWidth));
|
|
12
|
+
update();
|
|
13
|
+
window.addEventListener('resize', update);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Breakpoint viewport reattivo (mob/tab/des). Una sola subscription condivisa. */
|
|
17
|
+
export function getViewportBreakpoint(): ViewportKey {
|
|
18
|
+
subscribe();
|
|
19
|
+
return getViewport();
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './jsx.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './jsx.js';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { StyleShorthandKey } from '@flow-os/style';
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
namespace JSX {
|
|
5
|
+
type Element = Node;
|
|
6
|
+
|
|
7
|
+
/** classFlow (legacy) */
|
|
8
|
+
type ClassFlowValue = string | number | (() => string | number) | [() => number, string];
|
|
9
|
+
|
|
10
|
+
/** styleFlow: valori per proprietà stile (numero→px, getter, [getter, unità]). */
|
|
11
|
+
type StyleFlowStyleValue =
|
|
12
|
+
| number
|
|
13
|
+
| string
|
|
14
|
+
| (() => number | string)
|
|
15
|
+
| [() => number, string];
|
|
16
|
+
/** styleFlow: valori per classList (bool/getter). */
|
|
17
|
+
type StyleFlowClassValue = boolean | (() => boolean);
|
|
18
|
+
|
|
19
|
+
/** Layer per hover/active/focus: solo shorthand stile. */
|
|
20
|
+
interface StyleFlowPseudoLayer
|
|
21
|
+
extends Partial<Record<StyleShorthandKey, StyleFlowStyleValue>> {
|
|
22
|
+
[key: string]: StyleFlowStyleValue | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Layer styleFlow: base + responsive + pseudo + shorthand + classList. */
|
|
26
|
+
interface StyleFlowLayer
|
|
27
|
+
extends Partial<Record<StyleShorthandKey, StyleFlowStyleValue>> {
|
|
28
|
+
base?: string;
|
|
29
|
+
mob?: StyleFlowLayer;
|
|
30
|
+
tab?: StyleFlowLayer;
|
|
31
|
+
des?: StyleFlowLayer;
|
|
32
|
+
hover?: StyleFlowPseudoLayer;
|
|
33
|
+
active?: StyleFlowPseudoLayer;
|
|
34
|
+
focus?: StyleFlowPseudoLayer;
|
|
35
|
+
focusVisible?: StyleFlowPseudoLayer;
|
|
36
|
+
[key: string]:
|
|
37
|
+
| StyleFlowStyleValue
|
|
38
|
+
| StyleFlowClassValue
|
|
39
|
+
| StyleFlowLayer
|
|
40
|
+
| StyleFlowPseudoLayer
|
|
41
|
+
| string
|
|
42
|
+
| undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** styleFlow: stringa (solo classi) | [base, layer] | layer. */
|
|
46
|
+
type StyleFlow = string | [string, StyleFlowLayer] | StyleFlowLayer;
|
|
47
|
+
|
|
48
|
+
interface FlowHTMLAttributes {
|
|
49
|
+
class?: string | (() => string) | (string | (() => string))[];
|
|
50
|
+
classList?: Record<string, boolean | (() => boolean)>;
|
|
51
|
+
classFlow?: (() => Record<string, string | number>) | Record<string, ClassFlowValue>;
|
|
52
|
+
styleFlow?: StyleFlow;
|
|
53
|
+
style?: Record<string, string | number | (() => string | number)>;
|
|
54
|
+
children?: unknown;
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface IntrinsicElements {
|
|
59
|
+
[tag: string]: FlowHTMLAttributes;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/// <reference path="./jsx-types.d.ts" />
|
|
2
|
+
import { effect } from '@flow-os';
|
|
3
|
+
import { isGetter } from './features/utils.js';
|
|
4
|
+
import {
|
|
5
|
+
applyClassFlow,
|
|
6
|
+
applyClassAndClassList,
|
|
7
|
+
applyStyle,
|
|
8
|
+
applyStyleFlow,
|
|
9
|
+
applyAttrs,
|
|
10
|
+
} from './features/index.js';
|
|
11
|
+
|
|
12
|
+
export const Fragment = Symbol.for('flow.fragment');
|
|
13
|
+
|
|
14
|
+
type Props = Record<string, unknown> & { children?: unknown };
|
|
15
|
+
type JsxType = string | ((props: Props) => Node | null) | typeof Fragment;
|
|
16
|
+
|
|
17
|
+
function normalizeChild(c: unknown): Node | string | null {
|
|
18
|
+
if (c == null) return null;
|
|
19
|
+
if (typeof c === 'string' || typeof c === 'number') return String(c);
|
|
20
|
+
if (c instanceof Node) return c;
|
|
21
|
+
if (isGetter(c)) {
|
|
22
|
+
const text = document.createTextNode('');
|
|
23
|
+
effect(() => {
|
|
24
|
+
text.textContent = String(c());
|
|
25
|
+
});
|
|
26
|
+
return text;
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(c)) {
|
|
29
|
+
const frag = document.createDocumentFragment();
|
|
30
|
+
for (const x of c) {
|
|
31
|
+
const n = normalizeChild(x);
|
|
32
|
+
if (n !== null) frag.appendChild(typeof n === 'string' ? document.createTextNode(n) : n);
|
|
33
|
+
}
|
|
34
|
+
return frag;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function appendChildren(el: Node, children: unknown): void {
|
|
40
|
+
const c = normalizeChild(children);
|
|
41
|
+
if (c === null) return;
|
|
42
|
+
if (typeof c === 'string') {
|
|
43
|
+
el.appendChild(document.createTextNode(c));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
el.appendChild(c);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function applyProps(el: HTMLElement, props: Props): void {
|
|
50
|
+
const {
|
|
51
|
+
children,
|
|
52
|
+
class: classProp,
|
|
53
|
+
className: classNameProp,
|
|
54
|
+
classList,
|
|
55
|
+
classFlow,
|
|
56
|
+
style: styleProp,
|
|
57
|
+
styleFlow,
|
|
58
|
+
...rest
|
|
59
|
+
} = props;
|
|
60
|
+
if (styleFlow != null) {
|
|
61
|
+
applyStyleFlow(el, styleFlow);
|
|
62
|
+
} else {
|
|
63
|
+
applyClassFlow(el, classFlow);
|
|
64
|
+
applyClassAndClassList(el, classProp, classNameProp, classList);
|
|
65
|
+
applyStyle(el, styleProp);
|
|
66
|
+
}
|
|
67
|
+
applyAttrs(el, rest);
|
|
68
|
+
if (children !== undefined) appendChildren(el, children);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function jsxs(type: JsxType, props: Props, _key?: string | number): Node {
|
|
72
|
+
return jsx(type, props, _key);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function jsx(type: JsxType, props: Props, _key?: string | number): Node {
|
|
76
|
+
if (type === Fragment) {
|
|
77
|
+
const frag = document.createDocumentFragment();
|
|
78
|
+
if (props.children !== undefined) appendChildren(frag, props.children);
|
|
79
|
+
return frag;
|
|
80
|
+
}
|
|
81
|
+
if (typeof type === 'function') {
|
|
82
|
+
const out = type(props);
|
|
83
|
+
return out ?? document.createDocumentFragment();
|
|
84
|
+
}
|
|
85
|
+
const el = document.createElement(type as string);
|
|
86
|
+
applyProps(el, props);
|
|
87
|
+
return el;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function jsxDEV(
|
|
91
|
+
type: JsxType,
|
|
92
|
+
props: Props,
|
|
93
|
+
key: string | number | undefined,
|
|
94
|
+
_isStatic: boolean,
|
|
95
|
+
_source: unknown,
|
|
96
|
+
_self: unknown
|
|
97
|
+
): Node {
|
|
98
|
+
return jsx(type, props, key as string | undefined);
|
|
99
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flow-os/client",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./index.ts",
|
|
6
|
+
"types": "./index.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@flow-os": "workspace:*",
|
|
9
|
+
"@flow-os/router": "workspace:*",
|
|
10
|
+
"@flow-os/style": "workspace:*"
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"vite": ">=5.0.0"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./index.ts",
|
|
18
|
+
"import": "./index.ts",
|
|
19
|
+
"default": "./index.ts"
|
|
20
|
+
},
|
|
21
|
+
"./config": {
|
|
22
|
+
"types": "./config.ts",
|
|
23
|
+
"import": "./config.ts",
|
|
24
|
+
"default": "./config.ts"
|
|
25
|
+
},
|
|
26
|
+
"./vite": {
|
|
27
|
+
"types": "./vite.ts",
|
|
28
|
+
"import": "./vite.ts",
|
|
29
|
+
"default": "./vite.ts"
|
|
30
|
+
},
|
|
31
|
+
"./jsx-runtime": {
|
|
32
|
+
"types": "./jsx.ts",
|
|
33
|
+
"import": "./jsx.ts",
|
|
34
|
+
"default": "./jsx.ts"
|
|
35
|
+
},
|
|
36
|
+
"./jsx-dev-runtime": {
|
|
37
|
+
"types": "./jsx.ts",
|
|
38
|
+
"import": "./jsx.ts",
|
|
39
|
+
"default": "./jsx.ts"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { writeFileSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import type { Plugin } from 'vite';
|
|
5
|
+
|
|
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
|
+
run(root.default, import.meta.glob('/client/routes/**/*.tsx'), root.fallback != null ? { fallback: root.fallback } : undefined);
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
/** Reset stili browser: niente margin/padding di default, layout full viewport. */
|
|
13
|
+
const FLOW_BASE_STYLE = `<style>html,body{margin:0;padding:0;min-height:100vh}#app{min-height:100vh;box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}</style>`;
|
|
14
|
+
|
|
15
|
+
const HTML = `<!DOCTYPE html>
|
|
16
|
+
<html lang="it">
|
|
17
|
+
<head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>Flow</title>${FLOW_BASE_STYLE}</head>
|
|
18
|
+
<body><div id="app"></div><script type="module" src="/entry.tsx"></script></body>
|
|
19
|
+
</html>`;
|
|
20
|
+
|
|
21
|
+
export function flow(): Plugin {
|
|
22
|
+
return {
|
|
23
|
+
name: 'flow',
|
|
24
|
+
config() {
|
|
25
|
+
return {
|
|
26
|
+
root: '.',
|
|
27
|
+
esbuild: { jsx: 'automatic', jsxImportSource: '@flow-os/client' },
|
|
28
|
+
resolve: { alias: { '~': resolve(process.cwd(), 'client') } },
|
|
29
|
+
build: { target: 'esnext', minify: 'esbuild', sourcemap: true },
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
configResolved(_config) {
|
|
33
|
+
writeFileSync(resolve(process.cwd(), 'index.html'), HTML);
|
|
34
|
+
},
|
|
35
|
+
resolveId(id) {
|
|
36
|
+
return id === '/entry.tsx' ? FLOW_ENTRY_ID : null;
|
|
37
|
+
},
|
|
38
|
+
load(id) {
|
|
39
|
+
return id === FLOW_ENTRY_ID ? ENTRY_CODE : null;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"isolatedModules": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"noUncheckedIndexedAccess": true,
|
|
15
|
+
"noImplicitOverride": true,
|
|
16
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
17
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
18
|
+
"types": ["vite/client"],
|
|
19
|
+
"jsx": "react-jsx",
|
|
20
|
+
"jsxImportSource": "@flow-os/client",
|
|
21
|
+
"baseUrl": ".",
|
|
22
|
+
"paths": {
|
|
23
|
+
"~/*": ["client/*"],
|
|
24
|
+
"~server/*": ["server/*"],
|
|
25
|
+
"@flow-os": ["packages/flow-os/index.ts"],
|
|
26
|
+
"@flow-os/*": ["packages/*"]
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"include": ["client", "server", "packages/*", "vite.config.ts"]
|
|
30
|
+
}
|