create-flow-os 0.0.1-dev.1771614697 → 0.0.1-dev.1771615015
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/gen.ts +5 -2
- package/package.json +1 -1
- package/packages/core/client/routes/index.tsx +47 -0
- package/packages/router/client/root.tsx +16 -0
- package/profiles/client/client/routes/about.tsx +1 -1
- package/profiles/client/client/routes/index.tsx +1 -1
- package/profiles/client/package.json +8 -12
- package/profiles/client/packages/client/dom.ts +1 -1
- package/profiles/client/packages/client/features/attrs.ts +1 -1
- package/profiles/client/packages/client/features/class-flow.ts +1 -1
- package/profiles/client/packages/client/features/style-flow.ts +1 -1
- package/profiles/client/packages/client/features/style.ts +1 -1
- package/profiles/client/packages/client/features/viewport.ts +1 -1
- package/profiles/client/packages/client/jsx.ts +1 -1
- package/profiles/client/packages/client/package.json +1 -1
- package/profiles/client/packages/flow-os/dom.ts +43 -0
- package/profiles/client/packages/flow-os/for.ts +42 -0
- package/profiles/client/packages/flow-os/index.ts +5 -0
- package/profiles/client/packages/flow-os/lifecycle.ts +6 -0
- package/profiles/client/packages/flow-os/package.json +15 -0
- package/profiles/client/packages/flow-os/show-switch.ts +56 -0
- package/profiles/client/packages/flow-os/state.ts +56 -0
- package/profiles/client/packages/router/index.ts +139 -0
- package/profiles/client/packages/router/package.json +10 -0
- package/profiles/client/packages/style/breakpoints.ts +20 -0
- package/profiles/client/packages/style/extension/.vscodeignore +5 -0
- package/profiles/client/packages/style/extension/README.md +7 -0
- package/profiles/client/packages/style/extension/flow-style-colors-0.0.1.vsix +0 -0
- package/profiles/client/packages/style/extension/out/extension.js +130 -0
- package/profiles/client/packages/style/extension/package.json +24 -0
- package/profiles/client/packages/style/extension/src/extension.ts +146 -0
- package/profiles/client/packages/style/extension/tsconfig.json +12 -0
- package/profiles/client/packages/style/index.ts +16 -0
- package/profiles/client/packages/style/package.json +14 -0
- package/profiles/client/packages/style/resolve.ts +84 -0
- package/profiles/client/packages/style/shorthand.ts +62 -0
- package/profiles/client/tsconfig.json +1 -1
- package/profiles/full/.dockerignore +12 -0
- package/profiles/full/.oxfmtrc.json +7 -0
- package/profiles/full/.oxlintrc.json +13 -0
- package/profiles/full/.vscode/settings.json +12 -0
- package/profiles/full/Dockerfile +26 -0
- package/profiles/full/bun.lock +334 -0
- package/profiles/full/client/root.css +9 -0
- package/profiles/full/client/root.tsx +17 -0
- package/profiles/full/client/routes/about.tsx +22 -0
- package/profiles/full/client/routes/index.tsx +48 -0
- package/profiles/full/flow.config.ts +8 -0
- package/profiles/full/index.html +5 -0
- package/profiles/full/package.json +28 -0
- package/profiles/full/packages/client/config.ts +68 -0
- package/profiles/full/packages/client/dom.ts +5 -0
- package/profiles/full/packages/client/features/attrs.ts +32 -0
- package/profiles/full/packages/client/features/class-flow.ts +116 -0
- package/profiles/full/packages/client/features/index.ts +8 -0
- package/profiles/full/packages/client/features/pseudo-injector.ts +40 -0
- package/profiles/full/packages/client/features/style-flow.ts +106 -0
- package/profiles/full/packages/client/features/style.ts +27 -0
- package/profiles/full/packages/client/features/utils.ts +4 -0
- package/profiles/full/packages/client/features/viewport.ts +20 -0
- package/profiles/full/packages/client/index.ts +4 -0
- package/profiles/full/packages/client/jsx-dev-runtime.ts +1 -0
- package/profiles/full/packages/client/jsx-runtime.ts +1 -0
- package/profiles/full/packages/client/jsx-types.d.ts +64 -0
- package/profiles/full/packages/client/jsx.ts +99 -0
- package/profiles/full/packages/client/package.json +42 -0
- package/profiles/full/packages/client/vite.ts +42 -0
- package/profiles/full/packages/flow-os/dom.ts +43 -0
- package/profiles/full/packages/flow-os/for.ts +42 -0
- package/profiles/full/packages/flow-os/index.ts +5 -0
- package/profiles/full/packages/flow-os/lifecycle.ts +6 -0
- package/profiles/full/packages/flow-os/package.json +15 -0
- package/profiles/full/packages/flow-os/show-switch.ts +56 -0
- package/profiles/full/packages/flow-os/state.ts +56 -0
- package/profiles/full/packages/router/index.ts +139 -0
- package/profiles/full/packages/router/package.json +10 -0
- package/profiles/full/packages/server/core.ts +140 -0
- package/profiles/full/packages/server/index.ts +24 -0
- package/profiles/full/packages/server/package.json +29 -0
- package/profiles/full/packages/server/plugin.ts +79 -0
- package/profiles/full/packages/server/production.ts +75 -0
- package/profiles/full/packages/server/start.ts +15 -0
- package/profiles/full/packages/style/breakpoints.ts +20 -0
- package/profiles/full/packages/style/extension/.vscodeignore +5 -0
- package/profiles/full/packages/style/extension/README.md +7 -0
- package/profiles/full/packages/style/extension/flow-style-colors-0.0.1.vsix +0 -0
- package/profiles/full/packages/style/extension/out/extension.js +130 -0
- package/profiles/full/packages/style/extension/package.json +24 -0
- package/profiles/full/packages/style/extension/src/extension.ts +146 -0
- package/profiles/full/packages/style/extension/tsconfig.json +12 -0
- package/profiles/full/packages/style/index.ts +16 -0
- package/profiles/full/packages/style/package.json +14 -0
- package/profiles/full/packages/style/resolve.ts +84 -0
- package/profiles/full/packages/style/shorthand.ts +62 -0
- package/profiles/full/server/routes/hello.get.ts +3 -0
- package/profiles/full/tsconfig.json +30 -0
- package/profiles/server/.dockerignore +12 -0
- package/profiles/server/.oxfmtrc.json +7 -0
- package/profiles/server/.oxlintrc.json +13 -0
- package/profiles/server/.vscode/settings.json +12 -0
- package/profiles/server/Dockerfile +26 -0
- package/profiles/server/bun.lock +334 -0
- package/profiles/server/flow.config.ts +8 -0
- package/profiles/server/index.html +5 -0
- package/profiles/server/package.json +28 -0
- package/profiles/server/packages/flow-os/dom.ts +43 -0
- package/profiles/server/packages/flow-os/for.ts +42 -0
- package/profiles/server/packages/flow-os/index.ts +5 -0
- package/profiles/server/packages/flow-os/lifecycle.ts +6 -0
- package/profiles/server/packages/flow-os/package.json +15 -0
- package/profiles/server/packages/flow-os/show-switch.ts +56 -0
- package/profiles/server/packages/flow-os/state.ts +56 -0
- package/profiles/server/packages/router/index.ts +139 -0
- package/profiles/server/packages/router/package.json +10 -0
- package/profiles/server/packages/server/core.ts +140 -0
- package/profiles/server/packages/server/index.ts +24 -0
- package/profiles/server/packages/server/package.json +29 -0
- package/profiles/server/packages/server/plugin.ts +79 -0
- package/profiles/server/packages/server/production.ts +75 -0
- package/profiles/server/packages/server/start.ts +15 -0
- package/profiles/server/packages/style/breakpoints.ts +20 -0
- package/profiles/server/packages/style/extension/.vscodeignore +5 -0
- package/profiles/server/packages/style/extension/README.md +7 -0
- package/profiles/server/packages/style/extension/flow-style-colors-0.0.1.vsix +0 -0
- package/profiles/server/packages/style/extension/out/extension.js +130 -0
- package/profiles/server/packages/style/extension/package.json +24 -0
- package/profiles/server/packages/style/extension/src/extension.ts +146 -0
- package/profiles/server/packages/style/extension/tsconfig.json +12 -0
- package/profiles/server/packages/style/index.ts +16 -0
- package/profiles/server/packages/style/package.json +14 -0
- package/profiles/server/packages/style/resolve.ts +84 -0
- package/profiles/server/packages/style/shorthand.ts +62 -0
- package/profiles/server/server/routes/hello.get.ts +3 -0
- package/profiles/server/tsconfig.json +30 -0
package/gen.ts
CHANGED
|
@@ -95,7 +95,7 @@ async function main() {
|
|
|
95
95
|
await rm(packagesDir, { recursive: true, force: true });
|
|
96
96
|
|
|
97
97
|
const rootPkg = (await Bun.file(join(REPO_ROOT, "package.json")).json()) as { dependencies?: Record<string, string> };
|
|
98
|
-
const flowDepNames = Object.keys(rootPkg.dependencies ?? {}).filter((k) => k
|
|
98
|
+
const flowDepNames = Object.keys(rootPkg.dependencies ?? {}).filter((k) => k.startsWith("@flow-os"));
|
|
99
99
|
|
|
100
100
|
for (const [id, profile] of Object.entries(config.profiles)) {
|
|
101
101
|
const dest = join(profilesDir, id);
|
|
@@ -120,9 +120,12 @@ async function main() {
|
|
|
120
120
|
if (!dir) continue;
|
|
121
121
|
const folderName = basename(dir);
|
|
122
122
|
const destPkg = join(profilePackagesDir, folderName);
|
|
123
|
-
|
|
123
|
+
const copyExclude = ["node_modules", ".git", "dist"];
|
|
124
|
+
if (folderName === "flow-os") copyExclude.push("client", "server", "router", "style", "extension");
|
|
125
|
+
await copyWithExclude(dir, destPkg, copyExclude);
|
|
124
126
|
deps[pkgName] = "file:./packages/" + folderName;
|
|
125
127
|
}
|
|
128
|
+
delete deps["@flow-os"];
|
|
126
129
|
pkg.dependencies = deps;
|
|
127
130
|
delete (pkg as Record<string, unknown>).workspaces;
|
|
128
131
|
await Bun.write(pkgPath, JSON.stringify(pkg, null, 2));
|
package/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { state } from '@flow-os/core';
|
|
2
|
+
|
|
3
|
+
export default function Home() {
|
|
4
|
+
const [count, setCount] = state(0);
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
<div styleFlow='flex'>
|
|
8
|
+
<h1 class="text-2xl bg-[#b57070] font-bold text-primary">Flow</h1>
|
|
9
|
+
<p>
|
|
10
|
+
<a href="/about">Vai ad About</a>
|
|
11
|
+
</p>
|
|
12
|
+
<p>
|
|
13
|
+
Contatore: <span>{count}</span>
|
|
14
|
+
</p>
|
|
15
|
+
<p
|
|
16
|
+
class="transition-all text-2vw"
|
|
17
|
+
classFlow={{
|
|
18
|
+
text: [count, 'px'],
|
|
19
|
+
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
Dimensione reattiva (cresce con il contatore)
|
|
23
|
+
</p>
|
|
24
|
+
<button type="button" onClick={() => setCount(count() + 1)}>
|
|
25
|
+
+1
|
|
26
|
+
</button>
|
|
27
|
+
<div
|
|
28
|
+
styleFlow={[
|
|
29
|
+
"rounded font-sans font-bold transition-all-300",
|
|
30
|
+
{
|
|
31
|
+
base: "border border-#720000 ",
|
|
32
|
+
p: 4,
|
|
33
|
+
color:"#816e29",
|
|
34
|
+
mb: () => count() * 2,
|
|
35
|
+
mob: { p: 2 },
|
|
36
|
+
tab: { p: 4 },
|
|
37
|
+
des: { p: 6 },
|
|
38
|
+
hover: { bg: "primary", scale: 1.02 },
|
|
39
|
+
active: { scale: 0.98 },
|
|
40
|
+
},
|
|
41
|
+
]}
|
|
42
|
+
>
|
|
43
|
+
styleFlow: base + responsive (mob/tab/des) + hover/active
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import './root.css';
|
|
2
|
+
import { App } from '@flow-os/router';
|
|
3
|
+
import { state } from '@flow-os/core';
|
|
4
|
+
|
|
5
|
+
export const fallback = undefined;
|
|
6
|
+
|
|
7
|
+
export default function Root() {
|
|
8
|
+
return (
|
|
9
|
+
<>
|
|
10
|
+
<nav>
|
|
11
|
+
<a href="/">Home</a> | <a href="/about">About</a>
|
|
12
|
+
</nav>
|
|
13
|
+
<App />
|
|
14
|
+
</>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "placeholder",
|
|
3
3
|
"version": "0.0.0",
|
|
4
4
|
"private": true,
|
|
5
|
-
"workspaces": ["packages/*"],
|
|
6
5
|
"type": "module",
|
|
7
6
|
"scripts": {
|
|
8
7
|
"dev": "bunx --bun vite --config flow.config.ts",
|
|
@@ -14,19 +13,16 @@
|
|
|
14
13
|
"fmt:check": "oxfmt --check"
|
|
15
14
|
},
|
|
16
15
|
"dependencies": {
|
|
17
|
-
"@flow-os/client": "
|
|
18
|
-
"@flow-os/server": "
|
|
19
|
-
"@flow-os": "
|
|
20
|
-
"@flow-os/router": "
|
|
21
|
-
"@flow-os/style": "
|
|
16
|
+
"@flow-os/client": "file:./packages/client",
|
|
17
|
+
"@flow-os/server": "^0.0.1",
|
|
18
|
+
"@flow-os/core": "file:./packages/flow-os",
|
|
19
|
+
"@flow-os/router": "file:./packages/router",
|
|
20
|
+
"@flow-os/style": "file:./packages/style"
|
|
22
21
|
},
|
|
23
22
|
"devDependencies": {
|
|
24
23
|
"bun-types": "~1.2.0",
|
|
25
|
-
"oxfmt": "^0.33.0",
|
|
26
|
-
"oxlint": "^0.15.0",
|
|
27
24
|
"typescript": "~5.7.0",
|
|
28
|
-
"vite": "^7.3.1"
|
|
29
|
-
"vite-plugin-oxlint": "^1.6.0"
|
|
25
|
+
"vite": "^7.3.1"
|
|
30
26
|
},
|
|
31
27
|
"packageManager": "bun@1.3.9"
|
|
32
|
-
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper DOM per Show/For: normalizza children (getter, string, Node, array) e setContent.
|
|
3
|
+
*/
|
|
4
|
+
import { effect } from './state.js';
|
|
5
|
+
|
|
6
|
+
function isGetter(fn: unknown): fn is () => unknown {
|
|
7
|
+
return typeof fn === 'function' && fn.length === 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function normalizeChild(c: unknown): Node | string | null {
|
|
11
|
+
if (c == null) return null;
|
|
12
|
+
if (typeof c === 'string' || typeof c === 'number') return String(c);
|
|
13
|
+
if (c instanceof Node) return c;
|
|
14
|
+
if (isGetter(c)) {
|
|
15
|
+
const text = document.createTextNode('');
|
|
16
|
+
effect(() => {
|
|
17
|
+
text.textContent = String(c());
|
|
18
|
+
});
|
|
19
|
+
return text;
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(c)) {
|
|
22
|
+
const frag = document.createDocumentFragment();
|
|
23
|
+
for (const x of c) {
|
|
24
|
+
const n = normalizeChild(x);
|
|
25
|
+
if (n !== null) frag.appendChild(typeof n === 'string' ? document.createTextNode(n) : n);
|
|
26
|
+
}
|
|
27
|
+
return frag;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function setContent(el: HTMLElement, c: unknown): void {
|
|
33
|
+
const n = normalizeChild(c);
|
|
34
|
+
if (n === null) {
|
|
35
|
+
el.replaceChildren();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (typeof n === 'string') {
|
|
39
|
+
el.replaceChildren(document.createTextNode(n));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
el.replaceChildren(n);
|
|
43
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* For: lista reattiva. each={() => T[]}, children (item, index) => content.
|
|
3
|
+
* Opzionale key (item, index) => string|number per reconciliation keyed.
|
|
4
|
+
*/
|
|
5
|
+
import { effect } from './state.js';
|
|
6
|
+
import { normalizeChild } from './dom.js';
|
|
7
|
+
|
|
8
|
+
function isGetter(fn: unknown): fn is () => unknown {
|
|
9
|
+
return typeof fn === 'function' && fn.length === 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function appendNormalized(parent: Node, c: unknown): void {
|
|
13
|
+
const n = normalizeChild(c);
|
|
14
|
+
if (n === null) return;
|
|
15
|
+
parent.appendChild(typeof n === 'string' ? document.createTextNode(n) : n);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** For: rende una lista reattiva. each=getter array, children=(item, index)=>content, fallback=vuoto. */
|
|
19
|
+
export function For<T>(props: {
|
|
20
|
+
each: (() => T[]) | T[];
|
|
21
|
+
fallback?: unknown;
|
|
22
|
+
children: (item: T, index: number) => unknown;
|
|
23
|
+
}): HTMLElement {
|
|
24
|
+
const container = document.createElement('span');
|
|
25
|
+
container.style.display = 'contents';
|
|
26
|
+
const getList = isGetter(props.each) ? props.each : () => props.each as T[];
|
|
27
|
+
effect(() => {
|
|
28
|
+
const list = getList();
|
|
29
|
+
const len = Array.isArray(list) ? list.length : 0;
|
|
30
|
+
if (len === 0) {
|
|
31
|
+
appendNormalized(container, props.fallback);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
container.replaceChildren();
|
|
35
|
+
for (let i = 0; i < len; i++) {
|
|
36
|
+
const item = list[i];
|
|
37
|
+
if (item === undefined) continue;
|
|
38
|
+
appendNormalized(container, props.children(item, i));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return container;
|
|
42
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flow-os/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./index.ts",
|
|
6
|
+
"types": "./index.ts",
|
|
7
|
+
"dependencies": {},
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.ts",
|
|
11
|
+
"import": "./index.ts",
|
|
12
|
+
"default": "./index.ts"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Show: rendering condizionale (when/fallback).
|
|
3
|
+
* Switch: primo branch con when() truthy, altrimenti fallback.
|
|
4
|
+
*/
|
|
5
|
+
import { effect } from './state.js';
|
|
6
|
+
import { setContent } from './dom.js';
|
|
7
|
+
|
|
8
|
+
function isGetter(fn: unknown): fn is () => unknown {
|
|
9
|
+
return typeof fn === 'function' && fn.length === 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Show: quando when() è truthy mostra children, altrimenti fallback. */
|
|
13
|
+
export function Show(props: {
|
|
14
|
+
when: (() => unknown) | unknown;
|
|
15
|
+
fallback?: unknown;
|
|
16
|
+
children?: unknown;
|
|
17
|
+
}): HTMLElement {
|
|
18
|
+
const wrapper = document.createElement('span');
|
|
19
|
+
wrapper.style.display = 'contents';
|
|
20
|
+
const getWhen = isGetter(props.when) ? props.when : () => props.when;
|
|
21
|
+
effect(() => {
|
|
22
|
+
setContent(wrapper, getWhen() ? props.children : props.fallback);
|
|
23
|
+
});
|
|
24
|
+
return wrapper;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type MatchBranch = { when: () => boolean; children: unknown };
|
|
28
|
+
|
|
29
|
+
/** Switch: primo branch con when() truthy, altrimenti fallback. branches = [{ when, children }, ...]. */
|
|
30
|
+
export function Switch(props: {
|
|
31
|
+
fallback?: unknown;
|
|
32
|
+
branches: MatchBranch[];
|
|
33
|
+
}): HTMLElement {
|
|
34
|
+
const wrapper = document.createElement('span');
|
|
35
|
+
wrapper.style.display = 'contents';
|
|
36
|
+
effect(() => {
|
|
37
|
+
const list = props.branches;
|
|
38
|
+
for (let i = 0; i < list.length; i++) {
|
|
39
|
+
const b = list[i];
|
|
40
|
+
if (b === undefined) continue;
|
|
41
|
+
const cond = isGetter(b.when) ? b.when() : b.when;
|
|
42
|
+
if (cond) {
|
|
43
|
+
setContent(wrapper, b.children);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
setContent(wrapper, props.fallback);
|
|
48
|
+
});
|
|
49
|
+
return wrapper;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Helper per costruire i branch di Switch: Match({ when: () => cond, children: <Node/> }) => branch. */
|
|
53
|
+
export function Match(props: { when: (() => boolean) | boolean; children?: unknown }): MatchBranch {
|
|
54
|
+
const whenFn = isGetter(props.when) ? props.when : () => Boolean(props.when);
|
|
55
|
+
return { when: whenFn, children: props.children };
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
type Subscriber = () => void;
|
|
2
|
+
type SubscriberSet = Set<Subscriber>;
|
|
3
|
+
|
|
4
|
+
interface EffectRecord {
|
|
5
|
+
run: () => void;
|
|
6
|
+
deps: Set<SubscriberSet>;
|
|
7
|
+
cleanups: Array<() => void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let currentEffect: EffectRecord | null = null;
|
|
11
|
+
|
|
12
|
+
export function state<T>(initialValue: T): [() => T, (value: T | ((prev: T) => T)) => void] {
|
|
13
|
+
let value = initialValue;
|
|
14
|
+
const subscribers: SubscriberSet = new Set();
|
|
15
|
+
|
|
16
|
+
function get(): T {
|
|
17
|
+
if (currentEffect !== null) {
|
|
18
|
+
subscribers.add(currentEffect.run);
|
|
19
|
+
currentEffect.deps.add(subscribers);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function set(next: T | ((prev: T) => T)): void {
|
|
25
|
+
const nextValue = typeof next === 'function' ? (next as (prev: T) => T)(value) : next;
|
|
26
|
+
if (Object.is(value, nextValue)) return;
|
|
27
|
+
value = nextValue;
|
|
28
|
+
const copy = Array.from(subscribers);
|
|
29
|
+
copy.forEach((fn) => fn());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return [get, set];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function effect(fn: Subscriber): void {
|
|
36
|
+
const deps = new Set<SubscriberSet>();
|
|
37
|
+
const cleanups: Array<() => void> = [];
|
|
38
|
+
const run: Subscriber = () => {
|
|
39
|
+
for (const fn of cleanups) fn();
|
|
40
|
+
cleanups.length = 0;
|
|
41
|
+
deps.forEach((subs) => subs.delete(run));
|
|
42
|
+
deps.clear();
|
|
43
|
+
const prev = currentEffect;
|
|
44
|
+
currentEffect = { run, deps, cleanups };
|
|
45
|
+
try {
|
|
46
|
+
fn();
|
|
47
|
+
} finally {
|
|
48
|
+
currentEffect = prev;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
run();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function onCleanup(fn: () => void): void {
|
|
55
|
+
if (currentEffect !== null) currentEffect.cleanups.push(fn);
|
|
56
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
export const APP_ID = 'flow-app';
|
|
2
|
+
|
|
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
|
+
}
|
|
10
|
+
|
|
11
|
+
export type RouteModule = {
|
|
12
|
+
default: (() => Node) | ((params: Record<string, string>) => Node) | (() => string) | string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type RunOptions = {
|
|
16
|
+
/** Nodo (o factory) mostrato mentre la route lazy viene caricata. */
|
|
17
|
+
fallback?: Node | (() => Node);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function pathToPattern(key: string): string {
|
|
21
|
+
const afterRoutes = key.replace(/^.*[/\\]routes[/\\]?/i, '').replace(/\.tsx?$/i, '');
|
|
22
|
+
const s = afterRoutes.split(/[/\\]/).filter(Boolean).join('/');
|
|
23
|
+
if (s === 'index' || s === '') return '';
|
|
24
|
+
return s
|
|
25
|
+
.split('/')
|
|
26
|
+
.map((x) => (x.startsWith('[') && x.endsWith(']') ? `:${x.slice(1, -1)}` : x))
|
|
27
|
+
.join('/');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function match(pattern: string, path: string): Record<string, string> | null {
|
|
31
|
+
const pa = pattern ? pattern.split('/').filter(Boolean) : [];
|
|
32
|
+
const ph = path.replace(/^\//, '').split('/').filter(Boolean);
|
|
33
|
+
if (pa.length !== ph.length) return null;
|
|
34
|
+
const params: Record<string, string> = {};
|
|
35
|
+
for (let i = 0; i < pa.length; i++) {
|
|
36
|
+
if (pa[i]!.startsWith(':')) params[pa[i]!.slice(1)] = ph[i] ?? '';
|
|
37
|
+
else if (pa[i] !== ph[i]) return null;
|
|
38
|
+
}
|
|
39
|
+
return params;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function setContent(container: HTMLElement, content: Node | string): void {
|
|
43
|
+
container.innerHTML = '';
|
|
44
|
+
if (typeof content === 'string') container.textContent = content;
|
|
45
|
+
else container.appendChild(content);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function defaultNotFound(path: string): Node {
|
|
49
|
+
const wrap = document.createElement('div');
|
|
50
|
+
wrap.setAttribute('role', 'alert');
|
|
51
|
+
const h = document.createElement('h1');
|
|
52
|
+
h.textContent = '404';
|
|
53
|
+
const p = document.createElement('p');
|
|
54
|
+
p.textContent = `Pagina non trovata: ${path || '/'}`;
|
|
55
|
+
wrap.append(h, p);
|
|
56
|
+
return wrap;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createRouter(
|
|
60
|
+
modules: Record<string, () => Promise<RouteModule>>,
|
|
61
|
+
container: HTMLElement,
|
|
62
|
+
options: { fallback?: Node | (() => Node); notFound?: Node | ((path: string) => Node) } = {}
|
|
63
|
+
) {
|
|
64
|
+
const routes = Object.entries(modules).map(([key, loader]) => ({
|
|
65
|
+
pattern: pathToPattern(key),
|
|
66
|
+
loader,
|
|
67
|
+
}));
|
|
68
|
+
routes.sort(
|
|
69
|
+
(a, b) =>
|
|
70
|
+
b.pattern.split('/').filter(Boolean).length - a.pattern.split('/').filter(Boolean).length
|
|
71
|
+
);
|
|
72
|
+
const notFound = options.notFound ?? defaultNotFound;
|
|
73
|
+
|
|
74
|
+
async function render(pathname: string): Promise<boolean> {
|
|
75
|
+
const path = pathname === '/' ? '' : pathname.slice(1);
|
|
76
|
+
for (const { pattern, loader } of routes) {
|
|
77
|
+
const params = match(pattern, path);
|
|
78
|
+
if (params === null) continue;
|
|
79
|
+
const fallbackNode =
|
|
80
|
+
options.fallback != null
|
|
81
|
+
? typeof options.fallback === 'function'
|
|
82
|
+
? options.fallback()
|
|
83
|
+
: options.fallback
|
|
84
|
+
: null;
|
|
85
|
+
if (fallbackNode) setContent(container, fallbackNode);
|
|
86
|
+
const mod = await loader();
|
|
87
|
+
const def = mod.default;
|
|
88
|
+
if (typeof def === 'function') {
|
|
89
|
+
const out = def.length
|
|
90
|
+
? (def as (p: Record<string, string>) => Node)(params)
|
|
91
|
+
: (def as () => Node | string)();
|
|
92
|
+
if (out instanceof Node) setContent(container, out);
|
|
93
|
+
else if (typeof out === 'string') container.innerHTML = out;
|
|
94
|
+
else container.innerHTML = '';
|
|
95
|
+
} else setContent(container, typeof def === 'string' ? def : (def as unknown as Node));
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
const nfNode = typeof notFound === 'function' ? notFound(pathname) : notFound;
|
|
99
|
+
setContent(container, nfNode);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function go(path: string, replace = false): void {
|
|
104
|
+
const p = path.startsWith('/') ? path : `/${path}`;
|
|
105
|
+
render(p).then((ok) => {
|
|
106
|
+
if (ok) (replace ? history.replaceState : history.pushState).call(history, null, '', p);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function start(): void {
|
|
111
|
+
render(location.pathname || '/');
|
|
112
|
+
window.addEventListener('popstate', () => render(location.pathname || '/'));
|
|
113
|
+
document.addEventListener('click', (e) => {
|
|
114
|
+
const a = (e.target as Element).closest('a');
|
|
115
|
+
if (a?.getAttribute('href')?.startsWith('/') && !a.href.startsWith('//')) {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
go(a.getAttribute('href')!);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { go, start };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Monta App e avvia il router. Da usare nell'entry virtuale. */
|
|
126
|
+
export function run(
|
|
127
|
+
App: () => Node,
|
|
128
|
+
modules: Record<string, () => Promise<RouteModule>>,
|
|
129
|
+
options?: RunOptions
|
|
130
|
+
): void {
|
|
131
|
+
const app = document.getElementById('app')!;
|
|
132
|
+
app.appendChild(App());
|
|
133
|
+
const appContainer = document.getElementById(APP_ID);
|
|
134
|
+
if (!appContainer)
|
|
135
|
+
throw new Error(
|
|
136
|
+
`#${APP_ID} not found: use <App /> from @flow-os/router in your root layout.`
|
|
137
|
+
);
|
|
138
|
+
createRouter(modules, appContainer as HTMLElement, options).start();
|
|
139
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Chiavi viewport: ordine da piccolo a grande (merge sovrascrive). */
|
|
2
|
+
export const VIEWPORT_KEYS = ['mob', 'tab', 'des'] as const;
|
|
3
|
+
export type ViewportKey = (typeof VIEWPORT_KEYS)[number];
|
|
4
|
+
|
|
5
|
+
/** Chiavi pseudo-stato (hover, active, focus). */
|
|
6
|
+
export const PSEUDO_KEYS = ['hover', 'active', 'focus', 'focusVisible'] as const;
|
|
7
|
+
export type PseudoKey = (typeof PSEUDO_KEYS)[number];
|
|
8
|
+
|
|
9
|
+
/** Larghezze min (px) per viewport. mob < 640, tab 640–1023, des 1024+. */
|
|
10
|
+
export const VIEWPORT_WIDTHS: Record<ViewportKey, number> = {
|
|
11
|
+
mob: 0,
|
|
12
|
+
tab: 640,
|
|
13
|
+
des: 1024,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function getViewportKeyFromWidth(w: number): ViewportKey {
|
|
17
|
+
if (w >= 1024) return 'des';
|
|
18
|
+
if (w >= 640) return 'tab';
|
|
19
|
+
return 'mob';
|
|
20
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Flow Style — Color Picker
|
|
2
|
+
|
|
3
|
+
- **Pallino** (cerchio) prima di ogni colore `#hex` o `rgb(...)`.
|
|
4
|
+
- **Quadratino** dell’editor sul colore: **click** → picker nativo inline.
|
|
5
|
+
- Alternativa: **Ctrl+Shift+C** con cursore sul colore → input hex/rgb.
|
|
6
|
+
|
|
7
|
+
Installazione: Install from VSIX → `flow-style-colors-0.0.1.vsix`.
|