velloo 0.5.0 → 0.6.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/canvas/assets/index-CnyI3aig.js +29 -0
- package/canvas/assets/index-GNLd0pCG.css +2 -0
- package/canvas/assets/lucide-all-CH8LmVh0.js +1 -0
- package/canvas/index.html +2 -2
- package/{chunk-j7tzbt3d.js → chunk-08mjtg1e.js} +1 -1
- package/{chunk-afe25dpr.js → chunk-1gkeepkm.js} +1 -1
- package/{chunk-5ma8zbty.js → chunk-1td6z6ey.js} +1 -1
- package/{chunk-wkqrb2tz.js → chunk-2ba6edh8.js} +1 -1
- package/chunk-2q561dec.js +2 -0
- package/chunk-2web93qz.js +2 -0
- package/{chunk-6ytv1v2r.js → chunk-3trkmgfr.js} +1 -1
- package/chunk-52m26hh4.js +3 -0
- package/{chunk-3ffs39e8.js → chunk-56vfy0db.js} +1 -1
- package/{chunk-qpv28z8d.js → chunk-56z5xd5b.js} +1 -1
- package/{chunk-d4r0myxr.js → chunk-63y5gdzw.js} +1 -1
- package/{chunk-5y9hem1j.js → chunk-6813v926.js} +257 -3
- package/{chunk-hr3gbak5.js → chunk-69qb8p8s.js} +1 -1
- package/chunk-74yxy75r.js +3 -0
- package/{chunk-4pkw6d9n.js → chunk-8cj4swpr.js} +5 -5
- package/chunk-8grzf8q1.js +2 -0
- package/chunk-8vam2893.js +3 -0
- package/{chunk-675tr675.js → chunk-8vgpffn7.js} +1 -1
- package/{chunk-z78c1kf8.js → chunk-96qhpth5.js} +1 -1
- package/{chunk-tw4kw4ps.js → chunk-ab8fga32.js} +1 -1
- package/{chunk-asvdqkdp.js → chunk-azeqkms7.js} +1 -1
- package/{chunk-df557bn4.js → chunk-bc7pkjcn.js} +1 -1
- package/{chunk-1h3gqy7w.js → chunk-c4spp880.js} +1 -1
- package/{chunk-ngbqd7kb.js → chunk-chtv0yj6.js} +184 -103
- package/{chunk-xzkczmgg.js → chunk-dar6wbf2.js} +1 -1
- package/{chunk-ryga3ny1.js → chunk-e2btp1wd.js} +1 -1
- package/{chunk-rd65xm6r.js → chunk-eqjtq673.js} +1 -1
- package/{chunk-vrvkvb0v.js → chunk-esb7n55g.js} +3 -3
- package/chunk-ey65err5.js +5 -0
- package/{chunk-cqt1dpxd.js → chunk-ff0jfn0v.js} +1 -1
- package/chunk-ff2vt7cz.js +12 -0
- package/{chunk-f45p6kcb.js → chunk-g185gx8a.js} +1 -1
- package/{chunk-d9kx7v12.js → chunk-h85hv05d.js} +1 -1
- package/{chunk-vryadew3.js → chunk-hf4adxe0.js} +2 -2
- package/{chunk-edgwx026.js → chunk-hjc5eg5c.js} +1 -1
- package/{chunk-gn9wcc1b.js → chunk-hsfpbjpc.js} +1 -1
- package/{chunk-4sjvdakg.js → chunk-jsvkqae2.js} +1 -1
- package/{chunk-b3x80jc1.js → chunk-k423dkjz.js} +1 -1
- package/{chunk-0wqxb8r9.js → chunk-mfch0b0t.js} +1 -1
- package/{chunk-cdqah1hy.js → chunk-nx09zqgs.js} +1 -1
- package/chunk-p93x4pn2.js +3 -0
- package/{chunk-ahm4zd56.js → chunk-pxaxgkaq.js} +1 -1
- package/{chunk-5f0xv80x.js → chunk-qwccndcd.js} +1 -1
- package/{chunk-33t0csxs.js → chunk-rjjc9vam.js} +1 -1
- package/{chunk-0wafc9aw.js → chunk-rk238m15.js} +1 -1
- package/chunk-t2x3mztc.js +2 -0
- package/{chunk-42bj6r30.js → chunk-t6qr2ay9.js} +1 -1
- package/chunk-ta84ehgg.js +2 -0
- package/chunk-tac2wvc7.js +2 -0
- package/chunk-tbd6wn3q.js +6 -0
- package/chunk-w2naqxqt.js +2 -0
- package/chunk-wfhv9jg4.js +2 -0
- package/chunk-wnj432fw.js +3 -0
- package/chunk-x02z2ccm.js +2 -0
- package/{chunk-a16mz9zf.js → chunk-xtwx2p9g.js} +1 -1
- package/chunk-yfvc8d2g.js +716 -0
- package/chunk-z73q4dq9.js +2 -0
- package/{chunk-ycrddh47.js → chunk-ztsckexy.js} +2 -2
- package/cli.js +1 -1
- package/package.json +1 -1
- package/pkgs/helpers/src/descriptors.ts +1 -0
- package/pkgs/helpers/src/index.ts +7 -4
- package/pkgs/helpers/src/lowering.ts +0 -24
- package/pkgs/helpers/src/primitive-classes.tsx +43 -0
- package/pkgs/provider-antd/src/intro.ts +3 -3
- package/pkgs/provider-antd/src/manifest.ts +478 -57
- package/pkgs/provider-antd/src/registry.ts +8 -5
- package/pkgs/provider-antd/src/render-pass.ts +10 -4
- package/pkgs/provider-antd/src/reset-css.d.ts +5 -0
- package/pkgs/provider-antd/src/theme.ts +54 -11
- package/pkgs/provider-chakra/src/manifest.ts +5 -1
- package/pkgs/provider-mui/src/index.ts +4 -2
- package/pkgs/provider-mui/src/manifest.ts +10 -1
- package/pkgs/provider-mui/src/render-pass.ts +15 -1
- package/pkgs/provider-mui/src/theme.ts +10 -4
- package/pkgs/provider-none/src/components.tsx +15 -50
- package/pkgs/provider-none/src/manifest.ts +1 -0
- package/pkgs/schema/src/annotation.ts +6 -3
- package/pkgs/schema/src/comment.ts +10 -1
- package/pkgs/schema/src/index.ts +13 -1
- package/pkgs/schema/src/node-identity.ts +92 -0
- package/pkgs/schema/src/node.ts +13 -4
- package/pkgs/schema/src/theme.ts +6 -1
- package/pkgs/schema/src/typeset.ts +24 -17
- package/pkgs/shadcn-snapshot/dist/manifest.json +1 -0
- package/pkgs/shadcn-snapshot/src/components/ui/alert.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/aspect-ratio.tsx +2 -3
- package/pkgs/shadcn-snapshot/src/components/ui/attachment.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/avatar.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/badge.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/breadcrumb.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/bubble.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/button-group.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/button.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/card.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/carousel.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/chart.tsx +1 -1
- package/pkgs/shadcn-snapshot/src/components/ui/direction.tsx +2 -3
- package/pkgs/shadcn-snapshot/src/components/ui/empty.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/field.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/input-group.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/item.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/kbd.tsx +2 -3
- package/pkgs/shadcn-snapshot/src/components/ui/label.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/marker.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/message.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/native-select.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/navigation-menu.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/pagination.tsx +3 -4
- package/pkgs/shadcn-snapshot/src/components/ui/progress.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/radio-group.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/scroll-area.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/separator.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/skeleton.tsx +2 -3
- package/pkgs/shadcn-snapshot/src/components/ui/spinner.tsx +2 -3
- package/pkgs/shadcn-snapshot/src/components/ui/table.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/tabs.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/components/ui/toggle.tsx +2 -4
- package/pkgs/shadcn-snapshot/src/shadcn-tailwind.css +1 -1
- package/pkgs/shadcn-snapshot/src/tailwind-entry.css +1 -1
- package/canvas/assets/index-BDBBHdHH.css +0 -2
- package/canvas/assets/index-QdsLJNY9.js +0 -29
- package/canvas/assets/lucide-all-R8DUyY-B.js +0 -1
- package/chunk-0rdf6trc.js +0 -2
- package/chunk-10hap8kc.js +0 -6
- package/chunk-18eqh3s1.js +0 -2
- package/chunk-19xyzep8.js +0 -2
- package/chunk-49w26e94.js +0 -5
- package/chunk-66qgm91k.js +0 -708
- package/chunk-6cgvzwec.js +0 -3
- package/chunk-75sc3adm.js +0 -2
- package/chunk-7cexzg71.js +0 -2
- package/chunk-98rann8t.js +0 -2
- package/chunk-emv1j50t.js +0 -2
- package/chunk-ewgdcgrk.js +0 -2
- package/chunk-mm8wv3bg.js +0 -3
- package/chunk-nkqtnwzz.js +0 -2
- package/chunk-q3kf8243.js +0 -3
- package/chunk-sb5h27w9.js +0 -3
- package/chunk-vgx47anc.js +0 -3
- package/chunk-w6f40y2x.js +0 -11
- package/chunk-we8hvy6e.js +0 -2
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import { Drawer, Dropdown, Modal, Popover, Tooltip } from "./overlays.ts";
|
|
37
37
|
|
|
38
38
|
const REUSED_HELPER_IDS = [
|
|
39
|
+
"Box",
|
|
39
40
|
"Icon",
|
|
40
41
|
"Image",
|
|
41
42
|
"Placeholder",
|
|
@@ -49,8 +50,9 @@ const REUSED_HELPER_IDS = [
|
|
|
49
50
|
* The runtime registry for antd-native folders: design `$ref` ids → real antd
|
|
50
51
|
* components, SSR'd in-process via the adapter's cssinjs render pass. Dotted
|
|
51
52
|
* antd subcomponents get manifest-friendly flat ids (`Typography.Title` ⇒
|
|
52
|
-
* `TypographyTitle`, `List.Item` ⇒ `ListItem`)
|
|
53
|
-
*
|
|
53
|
+
* `TypographyTitle`, `List.Item` ⇒ `ListItem`) because neither a manifest id nor
|
|
54
|
+
* a `$ref` can carry a dot; the manifest records the dotted export as
|
|
55
|
+
* `nativeExport`, so emitted code names it directly. The overlay surface
|
|
54
56
|
* (Modal/Drawer/Popover/Tooltip/Dropdown) is canvas-safe-wrapped in
|
|
55
57
|
* `overlays.ts` — antd's own overlays portal and render NOTHING in SSR, so the
|
|
56
58
|
* shims render them open + inline instead.
|
|
@@ -97,8 +99,9 @@ export const registry: ComponentRegistry = {
|
|
|
97
99
|
TypographyText: Typography.Text,
|
|
98
100
|
TypographyTitle: Typography.Title,
|
|
99
101
|
// Framework-neutral velloo helpers antd has no equivalent for — chiefly
|
|
100
|
-
// `Icon` (lucide; @ant-design/icons isn't surfaced)
|
|
101
|
-
//
|
|
102
|
-
//
|
|
102
|
+
// `Icon` (lucide; @ant-design/icons isn't surfaced), `Box` (the plain element
|
|
103
|
+
// compose lowers `<div>` and stray text to) + imagery/composition helpers.
|
|
104
|
+
// Reused verbatim from `@velloo/helpers`, like provider-mui. They size via
|
|
105
|
+
// props (`Icon size`), not Tailwind — the JIT is off on an antd folder.
|
|
103
106
|
...helpersRegistry(REUSED_HELPER_IDS),
|
|
104
107
|
};
|
|
@@ -2,8 +2,9 @@ import { createCache, extractStyle, StyleProvider } from "@ant-design/cssinjs";
|
|
|
2
2
|
import type { RenderPass } from "@velloo/provider";
|
|
3
3
|
import type { Theme as VellooTheme } from "@velloo/schema";
|
|
4
4
|
import { ConfigProvider } from "antd";
|
|
5
|
+
import antdResetCss from "antd/dist/reset.css" with { type: "text" };
|
|
5
6
|
import { createElement } from "react";
|
|
6
|
-
import { antdThemeConfig } from "./theme.ts";
|
|
7
|
+
import { antdDocumentCss, antdThemeConfig } from "./theme.ts";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* A fresh cssinjs + antd render pass for one SSR. `wrap` nests the tree under
|
|
@@ -11,8 +12,12 @@ import { antdThemeConfig } from "./theme.ts";
|
|
|
11
12
|
* projected velloo tokens (with `cssVar: true`, so `--ant-*` variables reach
|
|
12
13
|
* inline styles); `css()` extracts the style rules the render registered in
|
|
13
14
|
* the cache. The renderer injects that CSS into a `data-velloo-adapter`
|
|
14
|
-
* <style
|
|
15
|
-
*
|
|
15
|
+
* <style>, led by antd's own `reset.css` — the global sheet every antd app
|
|
16
|
+
* imports, which makes boxes border-box and zeroes body and heading margins,
|
|
17
|
+
* so a sized container measures what it does in the app — and the document
|
|
18
|
+
* baseline (`antdDocumentCss`) that gives plain elements the theme's type
|
|
19
|
+
* and color. Unlike emotion, cssinjs extraction
|
|
20
|
+
* reads the cache directly — the rendered HTML isn't needed.
|
|
16
21
|
*
|
|
17
22
|
* SSR works in-process because antd is a velloo dependency, so it links the
|
|
18
23
|
* shared monorepo React — no dual-React hazard.
|
|
@@ -29,7 +34,8 @@ export function makeRenderPass(theme: VellooTheme, dark = false): RenderPass {
|
|
|
29
34
|
),
|
|
30
35
|
// `plain: true` returns bare CSS text; strip <style> wrappers defensively
|
|
31
36
|
// anyway — the RenderPass contract returns CSS, not markup.
|
|
32
|
-
css: () =>
|
|
37
|
+
css: () =>
|
|
38
|
+
`${antdResetCss}\n${antdDocumentCss(theme, dark)}\n${stripStyleTags(extractStyle(cache, { plain: true }))}`,
|
|
33
39
|
};
|
|
34
40
|
}
|
|
35
41
|
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
type ColorPair,
|
|
4
4
|
DEFAULT_TYPESET_NAME,
|
|
5
5
|
resolveColors,
|
|
6
|
+
sanitizeCssTokenValue,
|
|
7
|
+
TYPESET_DEFAULT,
|
|
6
8
|
typesetScale,
|
|
7
9
|
type Theme as VellooTheme,
|
|
8
10
|
} from "@velloo/schema";
|
|
@@ -18,7 +20,9 @@ import { formatRgb, parse } from "culori";
|
|
|
18
20
|
* theme stays hex (and round-trips identically in codegen).
|
|
19
21
|
*/
|
|
20
22
|
function antdColor(css: string): string {
|
|
21
|
-
|
|
23
|
+
// Theme strings are free text from the design JSON and end up inside
|
|
24
|
+
// cssinjs rules, so nothing that could close a declaration gets through.
|
|
25
|
+
const s = sanitizeCssTokenValue(css);
|
|
22
26
|
if (/^(#|rgb|hsl)/i.test(s)) return s;
|
|
23
27
|
return formatRgb(parse(s)) ?? s;
|
|
24
28
|
}
|
|
@@ -74,17 +78,27 @@ function antdTokens(theme: VellooTheme, dark: boolean): NonNullable<ThemeConfig[
|
|
|
74
78
|
* antd has only five heading tokens (h1–h5), so the velloo `h6` rung has no
|
|
75
79
|
* antd equivalent and is intentionally dropped here; `Prose` regions still get
|
|
76
80
|
* it from the typeset CSS.
|
|
81
|
+
*
|
|
82
|
+
* Line heights are left to antd unless the typeset chose its own leading.
|
|
83
|
+
* antd derives each one from its font size (14px reads 1.5714), and an antd
|
|
84
|
+
* app that sets only sizes gets those; velloo's default leading is a
|
|
85
|
+
* long-form reading rhythm, not something the app asked for, so projecting
|
|
86
|
+
* it would put every component on a rhythm the app does not have. A leading
|
|
87
|
+
* the user changed is a decision, and it reaches every antd line height.
|
|
77
88
|
*/
|
|
78
89
|
function antdTypography(theme: VellooTheme): NonNullable<ThemeConfig["token"]> {
|
|
79
90
|
const typography = theme.typography;
|
|
80
|
-
const
|
|
91
|
+
const typeset = typography.typesets?.[DEFAULT_TYPESET_NAME];
|
|
92
|
+
const scale = typesetScale(typeset, {
|
|
81
93
|
...(typography.fontFamily ? { fontFamily: typography.fontFamily } : {}),
|
|
82
94
|
});
|
|
95
|
+
const ownLeading = typeset?.leading !== undefined && typeset.leading !== TYPESET_DEFAULT.leading;
|
|
83
96
|
return {
|
|
84
|
-
fontFamily:
|
|
97
|
+
fontFamily: sanitizeCssTokenValue(
|
|
85
98
|
scale.body.fontFamily ??
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
typography.fontFamily?.sans ??
|
|
100
|
+
"system-ui, -apple-system, sans-serif",
|
|
101
|
+
),
|
|
88
102
|
fontSize: scale.body.fontSize,
|
|
89
103
|
fontSizeSM: scale.caption.fontSize,
|
|
90
104
|
fontSizeLG: scale.lead.fontSize,
|
|
@@ -93,12 +107,16 @@ function antdTypography(theme: VellooTheme): NonNullable<ThemeConfig["token"]> {
|
|
|
93
107
|
fontSizeHeading3: scale.h3.fontSize,
|
|
94
108
|
fontSizeHeading4: scale.h4.fontSize,
|
|
95
109
|
fontSizeHeading5: scale.h5.fontSize,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
110
|
+
...(ownLeading
|
|
111
|
+
? {
|
|
112
|
+
lineHeight: scale.body.lineHeight,
|
|
113
|
+
lineHeightHeading1: scale.h1.lineHeight,
|
|
114
|
+
lineHeightHeading2: scale.h2.lineHeight,
|
|
115
|
+
lineHeightHeading3: scale.h3.lineHeight,
|
|
116
|
+
lineHeightHeading4: scale.h4.lineHeight,
|
|
117
|
+
lineHeightHeading5: scale.h5.lineHeight,
|
|
118
|
+
}
|
|
119
|
+
: {}),
|
|
102
120
|
};
|
|
103
121
|
}
|
|
104
122
|
|
|
@@ -116,6 +134,31 @@ export function antdThemeConfig(theme: VellooTheme, dark = false): ThemeConfig {
|
|
|
116
134
|
};
|
|
117
135
|
}
|
|
118
136
|
|
|
137
|
+
/**
|
|
138
|
+
* The document rules an antd frame renders on, after antd's own `reset.css`
|
|
139
|
+
* (which a real antd app imports, and which sets box-sizing and margins but
|
|
140
|
+
* no type or color). antd v5 styles only its own components, so a plain
|
|
141
|
+
* element — a `Box`, a `Flex` container's own text — inherits from `<body>`,
|
|
142
|
+
* and the `style` channel skips the JIT that gives other providers a themed
|
|
143
|
+
* body. So `<body>` takes antd's resolved tokens for this theme, the values
|
|
144
|
+
* its components read, and the text between antd components matches them.
|
|
145
|
+
* The seed tokens are already sanitized, so they interpolate as-is.
|
|
146
|
+
*/
|
|
147
|
+
export function antdDocumentCss(theme: VellooTheme, dark = false): string {
|
|
148
|
+
const token = antdTheme.getDesignToken(antdThemeConfig(theme, dark));
|
|
149
|
+
return (
|
|
150
|
+
"html,body{" +
|
|
151
|
+
[
|
|
152
|
+
`background-color:${token.colorBgBase}`,
|
|
153
|
+
`color:${token.colorTextBase}`,
|
|
154
|
+
`font-family:${token.fontFamily}`,
|
|
155
|
+
`font-size:${token.fontSize}px`,
|
|
156
|
+
`line-height:${token.lineHeight}`,
|
|
157
|
+
].join(";") +
|
|
158
|
+
"}"
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
119
162
|
/**
|
|
120
163
|
* The codegen projection (`themeToNative`): the same token mapping, but the
|
|
121
164
|
* algorithm emits as a bare identifier (`antdTheme.darkAlgorithm`) that the
|
|
@@ -96,7 +96,11 @@ function helper(
|
|
|
96
96
|
|
|
97
97
|
export const CHAKRA_MANIFEST: Manifest = [
|
|
98
98
|
// --- layout ---
|
|
99
|
-
ui(
|
|
99
|
+
ui(
|
|
100
|
+
"Box",
|
|
101
|
+
[children, str("as")],
|
|
102
|
+
'Generic layout primitive; style via sx. `as` renders another HTML tag ("span", "section").',
|
|
103
|
+
),
|
|
100
104
|
ui(
|
|
101
105
|
"Flex",
|
|
102
106
|
[children, json("gap", "number | string"), str("align"), str("justify"), str("direction")],
|
|
@@ -82,8 +82,10 @@ export function createProvider(): FrameworkAdapter {
|
|
|
82
82
|
{
|
|
83
83
|
importPath: path,
|
|
84
84
|
exportName: id,
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// A helper is Velloo's own component, not a stand-in
|
|
86
|
+
// for an MUI one: this file is its real render.
|
|
87
|
+
fidelity: "exact" as const,
|
|
88
|
+
note: "Velloo helper, mounted beside the host framework's components.",
|
|
87
89
|
},
|
|
88
90
|
],
|
|
89
91
|
},
|
|
@@ -70,7 +70,16 @@ function helper(
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export const MUI_MANIFEST: Manifest = [
|
|
73
|
-
ui(
|
|
73
|
+
ui(
|
|
74
|
+
"Box",
|
|
75
|
+
[
|
|
76
|
+
children,
|
|
77
|
+
// Compose writes `<span>` as `as="span"` on every library's element; MUI
|
|
78
|
+
// types and documents the override as `component`, so it emits as that.
|
|
79
|
+
{ name: "as", type: "string", optional: true, control: "string", nativeName: "component" },
|
|
80
|
+
],
|
|
81
|
+
'Generic layout primitive; style via sx. `as` renders another HTML tag ("span", "section") and emits as MUI\'s `component`.',
|
|
82
|
+
),
|
|
74
83
|
ui(
|
|
75
84
|
"Stack",
|
|
76
85
|
[children, enumProp("direction", ["row", "column"], "column")],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import createCache from "@emotion/cache";
|
|
2
2
|
import { CacheProvider } from "@emotion/react";
|
|
3
3
|
import createEmotionServer from "@emotion/server/create-instance";
|
|
4
|
+
import CssBaseline from "@mui/material/CssBaseline";
|
|
4
5
|
import { ThemeProvider } from "@mui/material/styles";
|
|
5
6
|
import type { RenderPass } from "@velloo/provider";
|
|
6
7
|
import type { Theme as VellooTheme } from "@velloo/schema";
|
|
@@ -13,6 +14,14 @@ import { muiThemeFrom } from "./theme.ts";
|
|
|
13
14
|
* tokens; `css(html)` extracts exactly the emotion rules the rendered markup
|
|
14
15
|
* uses. The renderer injects that CSS into a `data-velloo-adapter` <style>.
|
|
15
16
|
*
|
|
17
|
+
* The tree renders beside a `CssBaseline`, as a real MUI app's root does: MUI
|
|
18
|
+
* styles only its own components, and the style channel skips the Tailwind
|
|
19
|
+
* JIT, so without it a plain element inherits a bare `<body>` (Times, 16px,
|
|
20
|
+
* black, an 8px margin). The baseline puts the theme's body1 type, text color
|
|
21
|
+
* and background on the body, from the same theme the components get, and
|
|
22
|
+
* `enableColorScheme` gives a dark frame dark native controls. Its rules are
|
|
23
|
+
* emotion globals, which `extractCriticalToChunks` always keeps.
|
|
24
|
+
*
|
|
16
25
|
* SSR works in-process because @mui/material is a velloo dependency, so it links
|
|
17
26
|
* the shared monorepo React — no dual-React hazard.
|
|
18
27
|
*/
|
|
@@ -25,7 +34,12 @@ export function makeRenderPass(theme: VellooTheme, dark = false): RenderPass {
|
|
|
25
34
|
createElement(
|
|
26
35
|
CacheProvider,
|
|
27
36
|
{ value: cache },
|
|
28
|
-
createElement(
|
|
37
|
+
createElement(
|
|
38
|
+
ThemeProvider,
|
|
39
|
+
{ theme: muiTheme },
|
|
40
|
+
createElement(CssBaseline, { enableColorScheme: true }),
|
|
41
|
+
element,
|
|
42
|
+
),
|
|
29
43
|
),
|
|
30
44
|
css: (html) =>
|
|
31
45
|
server
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type ColorPair,
|
|
4
4
|
DEFAULT_TYPESET_NAME,
|
|
5
5
|
resolveColors,
|
|
6
|
+
sanitizeCssTokenValue,
|
|
6
7
|
typesetScale,
|
|
7
8
|
type Theme as VellooTheme,
|
|
8
9
|
} from "@velloo/schema";
|
|
@@ -16,7 +17,9 @@ import { formatRgb, parse } from "culori";
|
|
|
16
17
|
* theme stays hex (and round-trips identically in codegen).
|
|
17
18
|
*/
|
|
18
19
|
function muiColor(css: string): string {
|
|
19
|
-
|
|
20
|
+
// Theme strings are free text from the design JSON and end up inside
|
|
21
|
+
// emotion rules, so nothing that could close a declaration gets through.
|
|
22
|
+
const s = sanitizeCssTokenValue(css);
|
|
20
23
|
if (/^(#|rgb|hsl)/i.test(s)) return s;
|
|
21
24
|
return formatRgb(parse(s)) ?? s;
|
|
22
25
|
}
|
|
@@ -90,15 +93,18 @@ function muiTypography(theme: VellooTheme): NonNullable<ThemeOptions["typography
|
|
|
90
93
|
...(typography.fontFamily ? { fontFamily: typography.fontFamily } : {}),
|
|
91
94
|
});
|
|
92
95
|
// The body face, falling back to the `sans` role then the system stack.
|
|
93
|
-
const bodyFamily =
|
|
94
|
-
scale.body.fontFamily ?? typography.fontFamily?.sans ?? "system-ui, -apple-system, sans-serif"
|
|
96
|
+
const bodyFamily = sanitizeCssTokenValue(
|
|
97
|
+
scale.body.fontFamily ?? typography.fontFamily?.sans ?? "system-ui, -apple-system, sans-serif",
|
|
98
|
+
);
|
|
95
99
|
|
|
96
100
|
const variant = (role: keyof typeof scale) => {
|
|
97
101
|
const r = scale[role];
|
|
98
102
|
return {
|
|
99
103
|
// Only override the family when the typeset names a *different* face for
|
|
100
104
|
// this role; otherwise inherit the theme's own fontFamily.
|
|
101
|
-
...(r.fontFamily && r.fontFamily !== bodyFamily
|
|
105
|
+
...(r.fontFamily && sanitizeCssTokenValue(r.fontFamily) !== bodyFamily
|
|
106
|
+
? { fontFamily: sanitizeCssTokenValue(r.fontFamily) }
|
|
107
|
+
: {}),
|
|
102
108
|
fontSize: `${r.fontSize}px`,
|
|
103
109
|
lineHeight: r.lineHeight,
|
|
104
110
|
letterSpacing: r.letterSpacing,
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BUTTON_BASE_CLASS,
|
|
3
|
+
BUTTON_VARIANT_CLASS,
|
|
4
|
+
CARD_CLASS,
|
|
5
|
+
CONTAINER_WIDTH_CLASS,
|
|
6
|
+
INPUT_CLASS,
|
|
7
|
+
STACK_ALIGN_CLASS,
|
|
8
|
+
STACK_JUSTIFY_CLASS,
|
|
9
|
+
} from "@velloo/helpers";
|
|
1
10
|
import { clsx } from "clsx";
|
|
2
11
|
import * as React from "react";
|
|
3
12
|
|
|
@@ -38,20 +47,6 @@ export interface StackProps extends DivProps {
|
|
|
38
47
|
justify?: "start" | "center" | "end" | "between" | "around";
|
|
39
48
|
}
|
|
40
49
|
|
|
41
|
-
const alignClass: Record<NonNullable<StackProps["align"]>, string> = {
|
|
42
|
-
start: "items-start",
|
|
43
|
-
center: "items-center",
|
|
44
|
-
end: "items-end",
|
|
45
|
-
stretch: "items-stretch",
|
|
46
|
-
};
|
|
47
|
-
const justifyClass: Record<NonNullable<StackProps["justify"]>, string> = {
|
|
48
|
-
start: "justify-start",
|
|
49
|
-
center: "justify-center",
|
|
50
|
-
end: "justify-end",
|
|
51
|
-
between: "justify-between",
|
|
52
|
-
around: "justify-around",
|
|
53
|
-
};
|
|
54
|
-
|
|
55
50
|
export const Stack = React.forwardRef<HTMLDivElement, StackProps>(
|
|
56
51
|
({ direction = "col", gap = 4, align, justify, className, ...rest }, ref) => (
|
|
57
52
|
<div
|
|
@@ -60,8 +55,8 @@ export const Stack = React.forwardRef<HTMLDivElement, StackProps>(
|
|
|
60
55
|
"flex",
|
|
61
56
|
direction === "row" ? "flex-row" : "flex-col",
|
|
62
57
|
`gap-${gap}`,
|
|
63
|
-
align ?
|
|
64
|
-
justify ?
|
|
58
|
+
align ? STACK_ALIGN_CLASS[align] : undefined,
|
|
59
|
+
justify ? STACK_JUSTIFY_CLASS[justify] : undefined,
|
|
65
60
|
className,
|
|
66
61
|
)}
|
|
67
62
|
{...rest}
|
|
@@ -75,19 +70,11 @@ export interface ContainerProps extends DivProps {
|
|
|
75
70
|
size?: "sm" | "md" | "lg" | "xl" | "full";
|
|
76
71
|
}
|
|
77
72
|
|
|
78
|
-
const containerWidth: Record<NonNullable<ContainerProps["size"]>, string> = {
|
|
79
|
-
sm: "max-w-screen-sm",
|
|
80
|
-
md: "max-w-screen-md",
|
|
81
|
-
lg: "max-w-screen-lg",
|
|
82
|
-
xl: "max-w-screen-xl",
|
|
83
|
-
full: "max-w-full",
|
|
84
|
-
};
|
|
85
|
-
|
|
86
73
|
export const Container = React.forwardRef<HTMLDivElement, ContainerProps>(
|
|
87
74
|
({ size = "md", className, ...rest }, ref) => (
|
|
88
75
|
<div
|
|
89
76
|
ref={ref}
|
|
90
|
-
className={clsx("mx-auto w-full px-4",
|
|
77
|
+
className={clsx("mx-auto w-full px-4", CONTAINER_WIDTH_CLASS[size], className)}
|
|
91
78
|
{...rest}
|
|
92
79
|
/>
|
|
93
80
|
),
|
|
@@ -98,23 +85,12 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
|
|
|
98
85
|
variant?: "default" | "ghost" | "outline";
|
|
99
86
|
}
|
|
100
87
|
|
|
101
|
-
const buttonVariant: Record<NonNullable<ButtonProps["variant"]>, string> = {
|
|
102
|
-
default: "bg-foreground text-background hover:bg-foreground/90",
|
|
103
|
-
ghost: "bg-transparent text-foreground hover:bg-muted",
|
|
104
|
-
outline: "bg-transparent text-foreground border border-border hover:bg-muted",
|
|
105
|
-
};
|
|
106
|
-
|
|
107
88
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
108
89
|
({ variant = "default", className, type, ...rest }, ref) => (
|
|
109
90
|
<button
|
|
110
91
|
ref={ref}
|
|
111
92
|
type={type ?? "button"}
|
|
112
|
-
className={clsx(
|
|
113
|
-
"inline-flex items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium",
|
|
114
|
-
"focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
115
|
-
buttonVariant[variant],
|
|
116
|
-
className,
|
|
117
|
-
)}
|
|
93
|
+
className={clsx(BUTTON_BASE_CLASS, BUTTON_VARIANT_CLASS[variant], className)}
|
|
118
94
|
{...rest}
|
|
119
95
|
/>
|
|
120
96
|
),
|
|
@@ -136,11 +112,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
136
112
|
value={value}
|
|
137
113
|
onChange={onChange}
|
|
138
114
|
readOnly={isStatic || rest.readOnly}
|
|
139
|
-
className={clsx(
|
|
140
|
-
"block w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
|
|
141
|
-
"placeholder:text-muted-foreground focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
142
|
-
className,
|
|
143
|
-
)}
|
|
115
|
+
className={clsx(INPUT_CLASS, className)}
|
|
144
116
|
{...rest}
|
|
145
117
|
/>
|
|
146
118
|
);
|
|
@@ -151,13 +123,6 @@ Input.displayName = "Input";
|
|
|
151
123
|
export interface CardProps extends DivProps {}
|
|
152
124
|
|
|
153
125
|
export const Card = React.forwardRef<HTMLDivElement, CardProps>(({ className, ...rest }, ref) => (
|
|
154
|
-
<div
|
|
155
|
-
ref={ref}
|
|
156
|
-
className={clsx(
|
|
157
|
-
"rounded-lg border border-border bg-card p-6 text-card-foreground shadow-sm",
|
|
158
|
-
className,
|
|
159
|
-
)}
|
|
160
|
-
{...rest}
|
|
161
|
-
/>
|
|
126
|
+
<div ref={ref} className={clsx(CARD_CLASS, className)} {...rest} />
|
|
162
127
|
));
|
|
163
128
|
Card.displayName = "Card";
|
|
@@ -100,11 +100,14 @@ export const CanvasNoteSchema = z
|
|
|
100
100
|
*/
|
|
101
101
|
x: z.number().optional(),
|
|
102
102
|
y: z.number().optional(),
|
|
103
|
+
/** User-resizable width. */
|
|
104
|
+
width: z.number().positive(),
|
|
103
105
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
+
* The height the author drew or resized the note to. Absent, the note is
|
|
107
|
+
* exactly as tall as its text; set, it is this tall, and text that runs
|
|
108
|
+
* past it scrolls inside the note.
|
|
106
109
|
*/
|
|
107
|
-
|
|
110
|
+
height: z.number().positive().optional(),
|
|
108
111
|
/** Markdown body. */
|
|
109
112
|
body: z.string(),
|
|
110
113
|
/** Present when the note is about a specific node rather than the board. */
|
|
@@ -116,4 +116,13 @@ export type CommentAnchorState =
|
|
|
116
116
|
| { status: "attached"; resolvedPath: number[] }
|
|
117
117
|
| { status: "stale" };
|
|
118
118
|
|
|
119
|
-
export type CommentThreadView = CommentThread & {
|
|
119
|
+
export type CommentThreadView = CommentThread & {
|
|
120
|
+
anchorState: CommentAnchorState;
|
|
121
|
+
/**
|
|
122
|
+
* False when the viewer can read the thread but not resolve or delete it — a
|
|
123
|
+
* cloud thread on a link they no longer manage. Absent means they can.
|
|
124
|
+
*/
|
|
125
|
+
manage?: false;
|
|
126
|
+
/** The git branch a cloud thread's link was last published from, where known. */
|
|
127
|
+
branch?: string;
|
|
128
|
+
};
|
package/pkgs/schema/src/index.ts
CHANGED
|
@@ -94,6 +94,7 @@ export {
|
|
|
94
94
|
} from "./migrate.ts";
|
|
95
95
|
export {
|
|
96
96
|
type ComponentNode,
|
|
97
|
+
type EmitAsRef,
|
|
97
98
|
isComponentNode,
|
|
98
99
|
isParamRef,
|
|
99
100
|
isRepoNode,
|
|
@@ -110,6 +111,17 @@ export {
|
|
|
110
111
|
repoKey,
|
|
111
112
|
type SnippetInstance,
|
|
112
113
|
} from "./node.ts";
|
|
114
|
+
export {
|
|
115
|
+
type EmitAsNode,
|
|
116
|
+
isNode,
|
|
117
|
+
isSyntheticRef,
|
|
118
|
+
type NodeShape,
|
|
119
|
+
nodeShape,
|
|
120
|
+
PARAM_TAG_REF,
|
|
121
|
+
type RepoNode,
|
|
122
|
+
STATIC_REF,
|
|
123
|
+
type SyntheticRef,
|
|
124
|
+
} from "./node-identity.ts";
|
|
113
125
|
export {
|
|
114
126
|
DesignNameSchema,
|
|
115
127
|
designNameIssue,
|
|
@@ -176,7 +188,7 @@ export {
|
|
|
176
188
|
typesetSizePx,
|
|
177
189
|
typesetThemeTokens,
|
|
178
190
|
typesetUtilityClasses,
|
|
179
|
-
|
|
191
|
+
typesetV3Theme,
|
|
180
192
|
typesetVars,
|
|
181
193
|
} from "./typeset.ts";
|
|
182
194
|
export { type DuplicateId, findDuplicateIds } from "./validate-ids.ts";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ComponentNode,
|
|
3
|
+
type EmitAsRef,
|
|
4
|
+
isComponentNode,
|
|
5
|
+
isParamRef,
|
|
6
|
+
isSnippetInstance,
|
|
7
|
+
type Node,
|
|
8
|
+
type ParamRef,
|
|
9
|
+
type RepoComponentRef,
|
|
10
|
+
type SnippetInstance,
|
|
11
|
+
} from "./node.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A `$ref` the renderer resolves itself rather than through any registry.
|
|
15
|
+
* Synthetic refs are never persisted — a preview synthesizes them — and every
|
|
16
|
+
* provider has to be able to draw them, so they can't be library components
|
|
17
|
+
* (a `none` folder has no `Badge` to draw a param slot with).
|
|
18
|
+
*/
|
|
19
|
+
export const PARAM_TAG_REF = "velloo:param-tag";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The ref a node serializes to when the canvas bundle has no browser source
|
|
23
|
+
* for it and the client must drop in its server render instead.
|
|
24
|
+
*/
|
|
25
|
+
export const STATIC_REF = "velloo:static";
|
|
26
|
+
|
|
27
|
+
export type SyntheticRef = typeof PARAM_TAG_REF | typeof STATIC_REF;
|
|
28
|
+
|
|
29
|
+
const SYNTHETIC_REFS: readonly SyntheticRef[] = [PARAM_TAG_REF, STATIC_REF];
|
|
30
|
+
|
|
31
|
+
export function isSyntheticRef(ref: string): ref is SyntheticRef {
|
|
32
|
+
return (SYNTHETIC_REFS as readonly string[]).includes(ref);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A component node carrying a repository identity (see {@link ComponentNode.$repo}). */
|
|
36
|
+
export type RepoNode = ComponentNode & { $repo: RepoComponentRef };
|
|
37
|
+
|
|
38
|
+
/** A component node carrying an emit-time host facade (see {@link ComponentNode.$emitAs}). */
|
|
39
|
+
export type EmitAsNode = ComponentNode & { $emitAs: EmitAsRef };
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* What a node's own fields say it is. Purely structural: no registry, no
|
|
43
|
+
* catalog, no I/O — a `named` shape is as far as the node alone can go, and
|
|
44
|
+
* `resolveNodeIdentity` in `@velloo/provider` takes it from there.
|
|
45
|
+
*
|
|
46
|
+
* The order of the `$`-keys is the contract, stated here once and nowhere
|
|
47
|
+
* else: `$snippet`, then `$param`, then a component — where an identity
|
|
48
|
+
* (`$repo`) beats an emit-time facade (`$emitAs`) beats a synthetic ref beats
|
|
49
|
+
* a plain name. A malformed value carrying both `$snippet` and `$ref` is a
|
|
50
|
+
* snippet instance, as every walker has always read it.
|
|
51
|
+
*/
|
|
52
|
+
export type NodeShape =
|
|
53
|
+
| { kind: "repo"; node: RepoNode; ref: string; repo: RepoComponentRef }
|
|
54
|
+
| { kind: "emit-as"; node: EmitAsNode; ref: string; emitAs: EmitAsRef }
|
|
55
|
+
| { kind: "synthetic"; node: ComponentNode; ref: SyntheticRef }
|
|
56
|
+
| { kind: "named"; node: ComponentNode; ref: string }
|
|
57
|
+
| { kind: "snippet"; node: SnippetInstance }
|
|
58
|
+
| { kind: "param"; node: ParamRef }
|
|
59
|
+
/** Not a node at all — a raw scalar or object sitting in a node position. */
|
|
60
|
+
| { kind: "invalid"; value: unknown };
|
|
61
|
+
|
|
62
|
+
/** Discriminate a value by its own fields. Accepts `unknown`: node positions are a trust boundary. */
|
|
63
|
+
export function nodeShape(value: unknown): NodeShape {
|
|
64
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
65
|
+
return { kind: "invalid", value };
|
|
66
|
+
}
|
|
67
|
+
const node = value as Node;
|
|
68
|
+
if (isSnippetInstance(node)) return { kind: "snippet", node };
|
|
69
|
+
if (isParamRef(node)) return { kind: "param", node };
|
|
70
|
+
if (isComponentNode(node)) {
|
|
71
|
+
if (node.$repo !== undefined) {
|
|
72
|
+
return { kind: "repo", node: node as RepoNode, ref: node.$ref, repo: node.$repo };
|
|
73
|
+
}
|
|
74
|
+
if (node.$emitAs !== undefined) {
|
|
75
|
+
return { kind: "emit-as", node: node as EmitAsNode, ref: node.$ref, emitAs: node.$emitAs };
|
|
76
|
+
}
|
|
77
|
+
if (isSyntheticRef(node.$ref)) {
|
|
78
|
+
return { kind: "synthetic", node, ref: node.$ref };
|
|
79
|
+
}
|
|
80
|
+
return { kind: "named", node, ref: node.$ref };
|
|
81
|
+
}
|
|
82
|
+
return { kind: "invalid", value };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Whether a raw JSON value is itself a node. The check every walker needs when
|
|
87
|
+
* it meets a *prop* value — a `children` prop carrying inline rich text, a slot
|
|
88
|
+
* prop holding an icon — where a node and a plain value are both legal.
|
|
89
|
+
*/
|
|
90
|
+
export function isNode(value: unknown): value is Node {
|
|
91
|
+
return nodeShape(value).kind !== "invalid";
|
|
92
|
+
}
|
package/pkgs/schema/src/node.ts
CHANGED
|
@@ -46,18 +46,27 @@ export type ComponentNode = {
|
|
|
46
46
|
* subtree — preserving the app's real component identity through
|
|
47
47
|
* capture → design → emit. Absent ⇒ the node emits as itself.
|
|
48
48
|
*/
|
|
49
|
-
$emitAs?:
|
|
49
|
+
$emitAs?: EmitAsRef | undefined;
|
|
50
50
|
/**
|
|
51
51
|
* Repository component identity: this node IS a component the host app
|
|
52
52
|
* imports (a package export such as Mantine's `Tabs`, or the app's own
|
|
53
53
|
* `StatCard`), not a library component. `$ref` stays the JSX name codegen
|
|
54
|
-
* prints; the identity decides rendering and imports.
|
|
55
|
-
*
|
|
56
|
-
* explicit on the node rather than a registry-order accident.
|
|
54
|
+
* prints; the identity decides rendering and imports. Where it sits against
|
|
55
|
+
* the node's other keys is `nodeShape`'s to say (`./node-identity.ts`).
|
|
57
56
|
*/
|
|
58
57
|
$repo?: RepoComponentRef | undefined;
|
|
59
58
|
};
|
|
60
59
|
|
|
60
|
+
/**
|
|
61
|
+
* A host component a node emits *as*, in place of its own subtree. Superseded
|
|
62
|
+
* in practice by {@link RepoComponentRef}, which renders for real; kept because
|
|
63
|
+
* design folders carry it.
|
|
64
|
+
*/
|
|
65
|
+
export type EmitAsRef = {
|
|
66
|
+
name: string;
|
|
67
|
+
importPath: string;
|
|
68
|
+
};
|
|
69
|
+
|
|
61
70
|
/**
|
|
62
71
|
* Where a repository component comes from. The import form is kept exactly as
|
|
63
72
|
* code generation must print it; the resolved file is runtime cache data and
|
package/pkgs/schema/src/theme.ts
CHANGED
|
@@ -131,7 +131,12 @@ export function resolveColors(theme: Theme, dark = false): Colors {
|
|
|
131
131
|
* CSS-safe ident so they can't inject into the emitted stylesheet.
|
|
132
132
|
*/
|
|
133
133
|
const PaletteSchema = z.record(
|
|
134
|
-
z
|
|
134
|
+
z
|
|
135
|
+
.string()
|
|
136
|
+
.regex(
|
|
137
|
+
/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/,
|
|
138
|
+
'palette keys are lowercase kebab-case, letter first — "primary-soft", "success-500", "border-primary-300"',
|
|
139
|
+
),
|
|
135
140
|
z.string().min(1),
|
|
136
141
|
);
|
|
137
142
|
|