@cloudcrane/app-runtime 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +39 -2
- package/THIRD_PARTY_LICENSES.txt +980 -0
- package/dist/compiler/compile.d.ts +16 -0
- package/dist/compiler/compile.js +56 -0
- package/dist/compiler/runtime.generated.d.ts +3 -0
- package/dist/compiler/runtime.generated.js +4 -0
- package/dist/host.d.ts +3 -0
- package/dist/host.js +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/runtime/styles.d.ts +2 -0
- package/dist/runtime/styles.js +12 -0
- package/dist/runtime/theme.d.ts +10 -0
- package/dist/runtime/theme.js +71 -0
- package/package.json +68 -3
- package/runtime-lock.json +4 -0
package/dist/host.d.ts
ADDED
package/dist/host.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// What a host that publishes spec-built apps needs (CloudCrane vendors this as
|
|
2
|
+
// one file, built by vendor.mjs): check a spec, build it, and describe it as a
|
|
3
|
+
// blueprint. The runtime's code is exported so a host can check a built page
|
|
4
|
+
// without it: the runtime is reviewed once per version and locked by its hash.
|
|
5
|
+
export { validateSpec, leakProblems, deriveBlueprint, CLOUDCRANE_CATALOG_ID } from "@cloudcrane/app-spec";
|
|
6
|
+
export { compile, CompileError } from "./compiler/compile.js";
|
|
7
|
+
export { RUNTIME_JS, RUNTIME_SHA256, RUNTIME_VERSION } from "./compiler/runtime.generated.js";
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Builds a CloudCrane app spec into one HTML page, the same bytes every time.
|
|
2
|
+
export { compile, CompileError } from "./compiler/compile.js";
|
|
3
|
+
export { RUNTIME_VERSION, RUNTIME_SHA256 } from "./compiler/runtime.generated.js";
|
|
4
|
+
export { tokenVariables, tokensCss } from "./runtime/theme.js";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const DATESTRIP_CSS = ".cc-datestrip{display:flex;gap:8px;overflow-x:auto;padding:2px 0 6px;scrollbar-width:none}.cc-datestrip::-webkit-scrollbar{display:none}\n.cc-day{flex:0 0 48px;height:64px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;border-radius:var(--a2ui-border-radius,12px);border:1px solid var(--a2ui-color-border);background:var(--a2ui-color-surface);color:var(--a2ui-color-on-surface);font:inherit;cursor:pointer;padding:0}\n.cc-day .cc-wd{font-size:9.5px;font-weight:700;opacity:.65}.cc-day.cc-today .cc-wd{font-size:8.5px}.cc-day .cc-n{font-size:15px;font-weight:800}.cc-day .cc-dot{width:5px;height:5px;border-radius:50%}\n.cc-day.cc-today{border:1.5px solid color-mix(in srgb,var(--a2ui-color-primary) 50%,transparent)}.cc-day.cc-on{background:var(--a2ui-color-primary);border-color:var(--a2ui-color-primary);color:var(--a2ui-color-on-primary,#fff)}.cc-day.cc-on .cc-wd{opacity:1}\n.cc-day:focus-visible{outline:2px solid var(--a2ui-color-primary);outline-offset:2px}\n.cc-field{display:flex;flex-direction:column;gap:4px;margin:var(--a2ui-spacing-s,6px) 0;font-size:var(--a2ui-label-font-size,13px);font-weight:var(--a2ui-label-font-weight,600);color:var(--a2ui-color-on-surface)}\n.cc-field input,.cc-field textarea{font:inherit;font-weight:400;font-size:16px;min-height:44px;padding:var(--a2ui-textfield-padding,10px 12px);color:var(--a2ui-color-on-input);background:var(--a2ui-color-input);border:var(--a2ui-textfield-border,1px solid var(--a2ui-color-border));border-radius:var(--a2ui-textfield-border-radius,10px);width:100%}\n.cc-field input:focus-visible,.cc-field textarea:focus-visible{outline:2px solid var(--a2ui-textfield-color-border-focus,var(--a2ui-color-primary));outline-offset:1px}\n.cc-field.cc-invalid input,.cc-field.cc-invalid textarea{border-color:var(--a2ui-textfield-color-error,var(--a2ui-color-error))}.cc-field-error{font-weight:400;color:var(--a2ui-textfield-color-error,var(--a2ui-color-error))}";
|
|
2
|
+
export declare const CODED_CSS = ".cc-coded{display:block;width:100%;overflow:hidden;border-radius:var(--a2ui-border-radius,12px);background:var(--a2ui-color-surface);border:1px solid var(--a2ui-color-border)}";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The CloudCrane catalog widgets' CSS, apart from their components so the compiler
|
|
2
|
+
// (in Node) never loads the renderer. Styled from A2UI's theme variables.
|
|
3
|
+
export const DATESTRIP_CSS = `.cc-datestrip{display:flex;gap:8px;overflow-x:auto;padding:2px 0 6px;scrollbar-width:none}.cc-datestrip::-webkit-scrollbar{display:none}
|
|
4
|
+
.cc-day{flex:0 0 48px;height:64px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;border-radius:var(--a2ui-border-radius,12px);border:1px solid var(--a2ui-color-border);background:var(--a2ui-color-surface);color:var(--a2ui-color-on-surface);font:inherit;cursor:pointer;padding:0}
|
|
5
|
+
.cc-day .cc-wd{font-size:9.5px;font-weight:700;opacity:.65}.cc-day.cc-today .cc-wd{font-size:8.5px}.cc-day .cc-n{font-size:15px;font-weight:800}.cc-day .cc-dot{width:5px;height:5px;border-radius:50%}
|
|
6
|
+
.cc-day.cc-today{border:1.5px solid color-mix(in srgb,var(--a2ui-color-primary) 50%,transparent)}.cc-day.cc-on{background:var(--a2ui-color-primary);border-color:var(--a2ui-color-primary);color:var(--a2ui-color-on-primary,#fff)}.cc-day.cc-on .cc-wd{opacity:1}
|
|
7
|
+
.cc-day:focus-visible{outline:2px solid var(--a2ui-color-primary);outline-offset:2px}
|
|
8
|
+
.cc-field{display:flex;flex-direction:column;gap:4px;margin:var(--a2ui-spacing-s,6px) 0;font-size:var(--a2ui-label-font-size,13px);font-weight:var(--a2ui-label-font-weight,600);color:var(--a2ui-color-on-surface)}
|
|
9
|
+
.cc-field input,.cc-field textarea{font:inherit;font-weight:400;font-size:16px;min-height:44px;padding:var(--a2ui-textfield-padding,10px 12px);color:var(--a2ui-color-on-input);background:var(--a2ui-color-input);border:var(--a2ui-textfield-border,1px solid var(--a2ui-color-border));border-radius:var(--a2ui-textfield-border-radius,10px);width:100%}
|
|
10
|
+
.cc-field input:focus-visible,.cc-field textarea:focus-visible{outline:2px solid var(--a2ui-textfield-color-border-focus,var(--a2ui-color-primary));outline-offset:1px}
|
|
11
|
+
.cc-field.cc-invalid input,.cc-field.cc-invalid textarea{border-color:var(--a2ui-textfield-color-error,var(--a2ui-color-error))}.cc-field-error{font-weight:400;color:var(--a2ui-textfield-color-error,var(--a2ui-color-error))}`;
|
|
12
|
+
export const CODED_CSS = `.cc-coded{display:block;width:100%;overflow:hidden;border-radius:var(--a2ui-border-radius,12px);background:var(--a2ui-color-surface);border:1px solid var(--a2ui-color-border)}`;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TokenNode } from "@cloudcrane/app-spec/schema";
|
|
2
|
+
/** Every token as [--a2ui-name, value], in name order. A group's $type applies to its tokens. */
|
|
3
|
+
export declare function tokenVariables(tokens: TokenNode): [string, string][];
|
|
4
|
+
/**
|
|
5
|
+
* The tokens as a :root rule, with the page's colour scheme taken from its own
|
|
6
|
+
* background: A2UI's defaults for what a spec leaves out (a plain button's
|
|
7
|
+
* text) are light-dark() pairs, which would otherwise follow the phone's
|
|
8
|
+
* setting and put light text on a light page. Light unless the background is dark.
|
|
9
|
+
*/
|
|
10
|
+
export declare function tokensCss(tokens: TokenNode): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
function cssValue(type, v) {
|
|
2
|
+
if (typeof v === "number")
|
|
3
|
+
return String(v);
|
|
4
|
+
if (typeof v === "string")
|
|
5
|
+
return v;
|
|
6
|
+
if (Array.isArray(v))
|
|
7
|
+
return type === "fontFamily" ? v.map((f) => (/^[\w-]+$/.test(String(f)) ? String(f) : JSON.stringify(String(f)))).join(", ") : null;
|
|
8
|
+
if (v && typeof v === "object") {
|
|
9
|
+
const o = v;
|
|
10
|
+
if (typeof o["hex"] === "string")
|
|
11
|
+
return o["hex"];
|
|
12
|
+
if (Array.isArray(o["components"]) && o["colorSpace"] === "srgb") {
|
|
13
|
+
const [r, g, b] = o["components"].map((c) => Math.round(Math.max(0, Math.min(1, c)) * 255));
|
|
14
|
+
const a = typeof o["alpha"] === "number" ? o["alpha"] : 1;
|
|
15
|
+
return a === 1 ? `rgb(${r} ${g} ${b})` : `rgb(${r} ${g} ${b} / ${a})`;
|
|
16
|
+
}
|
|
17
|
+
if (typeof o["value"] === "number" && typeof o["unit"] === "string")
|
|
18
|
+
return `${o["value"]}${o["unit"]}`;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
/** Every token as [--a2ui-name, value], in name order. A group's $type applies to its tokens. */
|
|
23
|
+
export function tokenVariables(tokens) {
|
|
24
|
+
const out = [];
|
|
25
|
+
const walk = (node, path, type) => {
|
|
26
|
+
const t = node.$type ?? type;
|
|
27
|
+
if (node.$value !== undefined) {
|
|
28
|
+
const v = cssValue(t, node.$value);
|
|
29
|
+
if (v !== null && path.length)
|
|
30
|
+
out.push([`--a2ui-${path.join("-")}`, v]);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const [k, child] of Object.entries(node))
|
|
34
|
+
if (!k.startsWith("$") && child && typeof child === "object")
|
|
35
|
+
walk(child, [...path, k], t);
|
|
36
|
+
};
|
|
37
|
+
walk(tokens, [], undefined);
|
|
38
|
+
return out.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
39
|
+
}
|
|
40
|
+
/** A colour's relative lightness, 0 to 1, from #rgb, #rrggbb or rgb(r g b); null for anything else. */
|
|
41
|
+
function lightness(css) {
|
|
42
|
+
let rgb = null;
|
|
43
|
+
const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(css.trim());
|
|
44
|
+
if (hex) {
|
|
45
|
+
const h = hex[1].length === 3 ? [...hex[1]].map((c) => c + c).join("") : hex[1];
|
|
46
|
+
rgb = [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
47
|
+
}
|
|
48
|
+
const fn = /^rgb\(\s*(\d+)\s+(\d+)\s+(\d+)/.exec(css.trim());
|
|
49
|
+
if (fn)
|
|
50
|
+
rgb = fn.slice(1, 4).map(Number);
|
|
51
|
+
if (!rgb)
|
|
52
|
+
return null;
|
|
53
|
+
const [r, g, b] = rgb.map((c) => {
|
|
54
|
+
const x = c / 255;
|
|
55
|
+
return x <= 0.04045 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
|
56
|
+
});
|
|
57
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The tokens as a :root rule, with the page's colour scheme taken from its own
|
|
61
|
+
* background: A2UI's defaults for what a spec leaves out (a plain button's
|
|
62
|
+
* text) are light-dark() pairs, which would otherwise follow the phone's
|
|
63
|
+
* setting and put light text on a light page. Light unless the background is dark.
|
|
64
|
+
*/
|
|
65
|
+
export function tokensCss(tokens) {
|
|
66
|
+
const vars = tokenVariables(tokens);
|
|
67
|
+
const background = vars.find(([k]) => k === "--a2ui-color-background")?.[1];
|
|
68
|
+
const l = background === undefined ? null : lightness(background);
|
|
69
|
+
const scheme = l !== null && l < 0.18 ? "dark" : "light";
|
|
70
|
+
return `:root{color-scheme:${scheme};${vars.map(([k, v]) => `${k}:${v};`).join("")}}`;
|
|
71
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,71 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcrane/app-runtime",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Builds a CloudCrane app spec into one HTML page, the same bytes every time, and the runtime that draws it with A2UI's renderer.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cloudcrane",
|
|
7
|
+
"a2ui",
|
|
8
|
+
"compiler",
|
|
9
|
+
"deterministic",
|
|
10
|
+
"pwa"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/cloudcrane-dev/blackbox/tree/main/packages/app-runtime#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/cloudcrane-dev/blackbox/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/cloudcrane-dev/blackbox.git",
|
|
19
|
+
"directory": "packages/app-runtime"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "Chirag Phor",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22.5"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./host": {
|
|
33
|
+
"types": "./dist/host.d.ts",
|
|
34
|
+
"default": "./dist/host.js"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"runtime-lock.json",
|
|
41
|
+
"THIRD_PARTY_LICENSES.txt",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@cloudcrane/app-spec": "^0.2.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"zod": "^3.25.76"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@a2ui/react": "0.14.0",
|
|
53
|
+
"@a2ui/web_core": "0.14.0",
|
|
54
|
+
"@types/jsdom": "^30.0.0",
|
|
55
|
+
"axe-core": "4.11.0",
|
|
56
|
+
"esbuild": "0.28.2",
|
|
57
|
+
"jsdom": "^26.1.0",
|
|
58
|
+
"playwright-core": "1.56.1",
|
|
59
|
+
"preact": "^10.29.8",
|
|
60
|
+
"tsx": "4.23.15",
|
|
61
|
+
"zod": "^3.25.76"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "node build.mjs && rm -rf dist && tsc -p tsconfig.build.json",
|
|
68
|
+
"test": "node build.mjs && vitest run",
|
|
69
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
70
|
+
}
|
|
6
71
|
}
|