@joplo-app/core 0.0.0-stage → 0.1.1

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 ADDED
@@ -0,0 +1,55 @@
1
+ Apache License
2
+
3
+ Version 2.0, January 2004
4
+
5
+ http://www.apache.org/licenses/
6
+
7
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
8
+
9
+ 1. Definitions.
10
+
11
+ "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
16
+
17
+ "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
18
+
19
+ "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
20
+
21
+ "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
22
+
23
+ "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
24
+
25
+ "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
26
+
27
+ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
28
+
29
+ "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
30
+
31
+ 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
32
+
33
+ 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
34
+
35
+ 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
36
+
37
+ You must give any other recipients of the Work or Derivative Works a copy of this License; and
38
+
39
+ You must cause any modified files to carry prominent notices stating that You changed the files; and
40
+
41
+ You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
42
+
43
+ If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
44
+
45
+ 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
46
+
47
+ 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
48
+
49
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
50
+
51
+ 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
52
+
53
+ 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
54
+
55
+ END OF TERMS AND CONDITIONS
@@ -0,0 +1,94 @@
1
+ /**
2
+ * 组件目录 v3(D35):名称与属性对齐 Mantine(数据表对齐 mantine-datatable),另加少量 AI 交互组件、
3
+ * HTML 兜底标签和 Joplo 专用组件。复合组件用点语法:`<AppShell.Header>` 编译出的节点 type 就是 'AppShell.Header'。
4
+ *
5
+ * 目录是唯一来源:@joplo-app/kit/ui 的导出、渲染器(@joplo-app/web)的实现、COMPONENT_DOCS 都必须与 COMPONENT_TYPES 一致
6
+ * (packages/core/test/catalog.test.ts 与渲染器的类型检查保证)。目录之外的名称在编译时报错,不会被静默渲染。
7
+ * 只换词汇,不换渲染:运行时不引入 Mantine,仍由 Joplo 渲染成低保真风格。
8
+ */
9
+ export declare const COMPONENT_TYPES: readonly ["AppShell", "AppShell.Header", "AppShell.Navbar", "AppShell.Main", "AppShell.Aside", "AppShell.Footer", "Container", "Stack", "Group", "Flex", "Grid", "Grid.Col", "SimpleGrid", "Center", "Box", "Space", "Paper", "Card", "Card.Section", "Divider", "ScrollArea", "Title", "Text", "Code", "Kbd", "Blockquote", "List", "List.Item", "Badge", "Avatar", "Image", "ThemeIcon", "Indicator", "Timeline", "Timeline.Item", "Table", "Table.Thead", "Table.Tbody", "Table.Tr", "Table.Th", "Table.Td", "DataTable", "Accordion", "Accordion.Item", "Accordion.Panel", "Radio", "Tabs.List", "Tabs.Panel", "Breadcrumbs", "Stepper", "Stepper.Step", "Alert", "Loader", "Progress", "RingProgress", "Skeleton", "Notification", "Modal", "Drawer", "ChatMessage", "ToolCall", "Terminal", "Terminal.Command", "Terminal.Output", "Phone", "Diagram", "Diagram.Group", "Diagram.Node", "Diagram.Edge", "div", "span", "p", "h1", "h2", "h3", "h4", "main", "section", "header", "footer", "nav", "aside", "ul", "ol", "li", "strong", "em", "small", "code", "Placeholder", "Custom", "#text", "Button", "ActionIcon", "CloseButton", "Anchor", "a", "NavLink", "Burger", "TextInput", "PasswordInput", "NumberInput", "Textarea", "Select", "MultiSelect", "Checkbox", "Radio.Group", "Switch", "SegmentedControl", "FileInput", "Tabs", "Tabs.Tab", "Accordion.Control", "Pagination", "PromptInput", "Terminal.Input"];
10
+ export type ComponentType = (typeof COMPONENT_TYPES)[number];
11
+ export declare const INTERACTIVE_TYPES: ReadonlySet<string>;
12
+ /** 可接收输入值;type/select/toggle 动作写入 state.inputs[nodeId]。 */
13
+ export declare const VALUE_TYPES: ReadonlySet<string>;
14
+ /** 只有 opened(兼容旧写法 open)为真时自身和子节点才可见的浮层。 */
15
+ export declare const GATED_CONTAINERS: ReadonlySet<string>;
16
+ /** Joplo 在 Mantine 属性之外追加的属性,编译器会从 props 中取出,放到节点字段上。 */
17
+ export declare const JOPLO_PROPS: readonly ["id", "when", "disabledReason", "unmodeled", "note"];
18
+ /**
19
+ * 每个组件一行常用属性提示(`joplo skill components` 由此生成)。所有组件另外都接受:
20
+ * 样式属性 m/mt/mb/ml/mr/mx/my、p/pt/pb/pl/pr/px/py、w/h/miw/maw/mih/mah、c/bg/fz/fw/ta/ff/lh/opacity,
21
+ * 响应式写成 { base, xs, sm, md, lg, xl };visibleFrom / hiddenFrom;以及 Joplo 属性 id/when/disabled/disabledReason/unmodeled/note。
22
+ */
23
+ export declare const COMPONENT_DOCS: Record<ComponentType, string>;
24
+ /** Mantine 默认断点:36/48/62/75/88em,按 16px 换算。 */
25
+ export declare const BREAKPOINTS: {
26
+ readonly xs: 576;
27
+ readonly sm: 768;
28
+ readonly md: 992;
29
+ readonly lg: 1200;
30
+ readonly xl: 1408;
31
+ };
32
+ export type Breakpoint = keyof typeof BREAKPOINTS;
33
+ declare const BP_KEYS: readonly ["base", "xs", "sm", "md", "lg", "xl"];
34
+ /** 页面 viewport 对应的舞台尺寸。 */
35
+ export declare const VIEWPORT_SIZE: {
36
+ readonly desktop: {
37
+ readonly width: 1280;
38
+ readonly height: 800;
39
+ };
40
+ readonly mobile: {
41
+ readonly width: 390;
42
+ readonly height: 844;
43
+ };
44
+ };
45
+ /**
46
+ * 字幕区:画面下方、播放控制上方的一块固定高度区域,最多两行:一行本步说明(大字),一行 explain(小一号)。
47
+ * 演示页、发布站、renderStep 截图、导出图片与视频都把它放在舞台正下方,宽度与舞台(手机含外框)相同。
48
+ */
49
+ export declare const SUBTITLE_HEIGHT = 84;
50
+ /**
51
+ * 内置主题(D51):每个主题是一个只写 CSS 变量(--jp-*)的样式表,CSS 在 web 包里(renderer/themes.ts)。
52
+ * 作者可以在项目根目录 themes/<name>.css 写自己的主题;与内置同名时用作者的。
53
+ */
54
+ export declare const BUILTIN_THEMES: readonly ["sketch", "clean", "blueprint", "nord", "dracula", "solarized-light", "terminal", "kraft", "contrast"];
55
+ /** 主题名:字母开头,字母、数字、连字符、下划线(也是 themes/ 下的文件名)。 */
56
+ export declare const THEME_NAME: RegExp;
57
+ /** 字幕的“字数”:中文(及其他全角字符)一个算 1,英文、数字、标点按 0.5 算;空白不算。check 用它提示说明与 explain 过长。 */
58
+ export declare function captionLength(s: string): number;
59
+ /** 说明 / explain 的建议上限(超过时 check 报 CAPTION_TOO_LONG / EXPLAIN_TOO_LONG)。 */
60
+ export declare const CAPTION_MAX = 24;
61
+ export declare const EXPLAIN_MAX = 48;
62
+ /** 没有声明 layers 的方案:镜头作用在路径页面上,记为这个伪层级(不出现在层级栏)。作者的层级键不能以 @ 开头。 */
63
+ export declare const PAGE_LAYER = "@page";
64
+ /** shot()(不带 id)在有层级的方案里:作用于“当时导演所在的层”,让它回到整体。 */
65
+ export declare const DIRECTOR_LAYER = "@director";
66
+ /** Diagram.Node 的 kind 与 Node / Edge 的 state(D41)。 */
67
+ export declare const DIAGRAM_NODE_KINDS: readonly ["client", "service", "model", "database", "queue", "storage", "external", "tool", "user"];
68
+ export declare const DIAGRAM_STATES: readonly ["idle", "active", "ok", "error", "dim"];
69
+ /** 子节点排版用的宽度:Phone 内部按手机宽度(响应式取值、visibleFrom / hiddenFrom、AppShell 收起都按它算),其余节点沿用外部宽度。 */
70
+ export declare function childWidth(type: string, width: number): number;
71
+ /** 是否为响应式对象 `{ base, sm, md }`:普通对象,至少一个键,且全部键都是断点名。 */
72
+ export declare function isResponsive(v: unknown): v is Partial<Record<(typeof BP_KEYS)[number], unknown>>;
73
+ /** 按舞台宽度取响应式属性的值:取不超过宽度的最大断点;都不满足时取 base;非响应式值原样返回。 */
74
+ export declare function atWidth(v: unknown, width: number): unknown;
75
+ /** 断点名、数字(px)或 `40em` / `600px` 字符串 → 像素;无法识别返回 null。 */
76
+ export declare function breakpointPx(bp: unknown): number | null;
77
+ /** visibleFrom / hiddenFrom:在给定舞台宽度下是否被隐藏。 */
78
+ export declare function hiddenAtWidth(props: Record<string, unknown> | undefined, width: number): boolean;
79
+ export type Choice = {
80
+ value: string;
81
+ label: string;
82
+ };
83
+ /** Mantine 的 data:string[]、{ value, label }[],或分组 { group, items }[]。 */
84
+ export declare function choicesFromData(data: unknown): Choice[];
85
+ /** 按 value 或 label 匹配一个选项。 */
86
+ export declare function matchChoice(choices: Choice[], v: string): Choice | null;
87
+ type LabelNode = {
88
+ type: string;
89
+ props?: Record<string, unknown>;
90
+ children?: LabelNode[];
91
+ };
92
+ /** 元素的人类可读标签:优先 label/title/aria-label/placeholder 等属性(Mantine 输入组件直接带 label),其次子文本。 */
93
+ export declare function nodeLabel(node: LabelNode): string;
94
+ export {};
@@ -0,0 +1,310 @@
1
+ /**
2
+ * 组件目录 v3(D35):名称与属性对齐 Mantine(数据表对齐 mantine-datatable),另加少量 AI 交互组件、
3
+ * HTML 兜底标签和 Joplo 专用组件。复合组件用点语法:`<AppShell.Header>` 编译出的节点 type 就是 'AppShell.Header'。
4
+ *
5
+ * 目录是唯一来源:@joplo-app/kit/ui 的导出、渲染器(@joplo-app/web)的实现、COMPONENT_DOCS 都必须与 COMPONENT_TYPES 一致
6
+ * (packages/core/test/catalog.test.ts 与渲染器的类型检查保证)。目录之外的名称在编译时报错,不会被静默渲染。
7
+ * 只换词汇,不换渲染:运行时不引入 Mantine,仍由 Joplo 渲染成低保真风格。
8
+ */
9
+ /** 布局、展示与容器:不可操作。 */
10
+ const STRUCTURE = [
11
+ // 布局
12
+ 'AppShell', 'AppShell.Header', 'AppShell.Navbar', 'AppShell.Main', 'AppShell.Aside', 'AppShell.Footer',
13
+ 'Container', 'Stack', 'Group', 'Flex', 'Grid', 'Grid.Col', 'SimpleGrid', 'Center', 'Box', 'Space',
14
+ 'Paper', 'Card', 'Card.Section', 'Divider', 'ScrollArea',
15
+ // 文字与展示
16
+ 'Title', 'Text', 'Code', 'Kbd', 'Blockquote', 'List', 'List.Item', 'Badge', 'Avatar', 'Image', 'ThemeIcon', 'Indicator',
17
+ 'Timeline', 'Timeline.Item',
18
+ 'Table', 'Table.Thead', 'Table.Tbody', 'Table.Tr', 'Table.Th', 'Table.Td', 'DataTable',
19
+ 'Accordion', 'Accordion.Item', 'Accordion.Panel',
20
+ 'Radio',
21
+ // 导航
22
+ 'Tabs.List', 'Tabs.Panel', 'Breadcrumbs', 'Stepper', 'Stepper.Step',
23
+ // 反馈与浮层
24
+ 'Alert', 'Loader', 'Progress', 'RingProgress', 'Skeleton', 'Notification', 'Modal', 'Drawer',
25
+ // AI 交互(Joplo 自有)
26
+ 'ChatMessage', 'ToolCall',
27
+ // 设备外框(Joplo 自有):终端与手机
28
+ 'Terminal', 'Terminal.Command', 'Terminal.Output', 'Phone',
29
+ // 架构图(Joplo 自有,D41):节点与连线的 state 绑定 $.ui,由 flow 步骤驱动
30
+ 'Diagram', 'Diagram.Group', 'Diagram.Node', 'Diagram.Edge',
31
+ // HTML 兜底
32
+ 'div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'main', 'section', 'header', 'footer', 'nav', 'aside', 'ul', 'ol', 'li', 'strong', 'em', 'small', 'code',
33
+ // Joplo
34
+ 'Placeholder', // props: label, h —— 图片/图表等占位
35
+ 'Custom', // props: kind —— 逃生口:目录没有的组件,渲染为带说明的占位框
36
+ '#text', // 编译器生成的文本节点:props.text 为字面量或 `$ui.x` 绑定
37
+ ];
38
+ /** 看起来可操作:必须有演示行为、明确禁用并说明原因,或标记 unmodeled。 */
39
+ const INTERACTIVE = [
40
+ 'Button', 'ActionIcon', 'CloseButton', 'Anchor', 'a', 'NavLink', 'Burger',
41
+ 'TextInput', 'PasswordInput', 'NumberInput', 'Textarea', 'Select', 'MultiSelect', 'Checkbox', 'Radio.Group', 'Switch',
42
+ 'SegmentedControl', 'FileInput', 'Tabs', 'Tabs.Tab', 'Accordion.Control', 'Pagination', 'PromptInput', 'Terminal.Input',
43
+ ];
44
+ export const COMPONENT_TYPES = [...STRUCTURE, ...INTERACTIVE];
45
+ export const INTERACTIVE_TYPES = new Set(INTERACTIVE);
46
+ /** 可接收输入值;type/select/toggle 动作写入 state.inputs[nodeId]。 */
47
+ export const VALUE_TYPES = new Set([
48
+ 'TextInput', 'PasswordInput', 'NumberInput', 'Textarea', 'Select', 'MultiSelect', 'Checkbox', 'Radio.Group', 'Switch',
49
+ 'SegmentedControl', 'FileInput', 'Tabs', 'Accordion', 'Pagination', 'PromptInput', 'Terminal.Input',
50
+ ]);
51
+ /** 只有 opened(兼容旧写法 open)为真时自身和子节点才可见的浮层。 */
52
+ export const GATED_CONTAINERS = new Set(['Modal', 'Drawer']);
53
+ /** Joplo 在 Mantine 属性之外追加的属性,编译器会从 props 中取出,放到节点字段上。 */
54
+ export const JOPLO_PROPS = ['id', 'when', 'disabledReason', 'unmodeled', 'note'];
55
+ /**
56
+ * 每个组件一行常用属性提示(`joplo skill components` 由此生成)。所有组件另外都接受:
57
+ * 样式属性 m/mt/mb/ml/mr/mx/my、p/pt/pb/pl/pr/px/py、w/h/miw/maw/mih/mah、c/bg/fz/fw/ta/ff/lh/opacity,
58
+ * 响应式写成 { base, xs, sm, md, lg, xl };visibleFrom / hiddenFrom;以及 Joplo 属性 id/when/disabled/disabledReason/unmodeled/note。
59
+ */
60
+ export const COMPONENT_DOCS = {
61
+ AppShell: 'header={{ height }}, navbar={{ width, breakpoint, collapsed: { mobile, desktop } }}, aside={{ width, breakpoint, collapsed }}, footer={{ height }}, padding;窄于 breakpoint 时导航收起并显示 Burger',
62
+ 'AppShell.Header': '顶栏;p, px',
63
+ 'AppShell.Navbar': '侧边导航;p;放 NavLink',
64
+ 'AppShell.Main': '主内容区;放 <Outlet />',
65
+ 'AppShell.Aside': '右侧栏;p',
66
+ 'AppShell.Footer': '底栏;p',
67
+ Container: 'size: xs|sm|md|lg|xl|number(默认 md), fluid',
68
+ Stack: 'gap(默认 md), align, justify —— 纵向排列',
69
+ Group: 'gap(默认 md), justify, align(默认 center), wrap, grow —— 横向排列',
70
+ Flex: 'direction, gap, rowGap, columnGap, align, justify, wrap —— 均可响应式',
71
+ Grid: 'columns(默认 12), gutter, justify, align, grow;子元素用 Grid.Col',
72
+ 'Grid.Col': 'span: number|"auto"|"content"|{ base, md }, offset',
73
+ SimpleGrid: 'cols={{ base: 1, md: 3 }}, spacing, verticalSpacing',
74
+ Center: 'inline —— 内容居中',
75
+ Box: '通用容器,只用样式属性',
76
+ Space: 'h, w —— 空白间隔',
77
+ Paper: 'p, withBorder, shadow, radius',
78
+ Card: 'padding(默认 md), withBorder, shadow, radius;Card.Section 贴边分区',
79
+ 'Card.Section': 'withBorder, inheritPadding',
80
+ Divider: 'label, labelPosition: left|center|right, orientation, my',
81
+ ScrollArea: 'h, mah',
82
+ Title: 'order: 1-6;文字写在子节点',
83
+ Text: 'size: xs|sm|md|lg|xl, c: dimmed|red|green|…, fw, ta, truncate, lineClamp, span;保留换行',
84
+ Code: 'block(多行代码块), color',
85
+ Kbd: '键盘按键',
86
+ Blockquote: 'cite, color',
87
+ List: 'type: ordered|unordered, spacing;子元素用 List.Item',
88
+ 'List.Item': '列表项',
89
+ Badge: 'color, variant: filled|light|outline|dot|default, size',
90
+ Avatar: 'name(显示首字), src, color, radius, size',
91
+ Image: 'src, alt, h, w, radius —— 渲染为带 alt 的占位',
92
+ ThemeIcon: 'color, variant, size, radius;子节点放一个字符',
93
+ Indicator: 'label, color, disabled;包裹一个元素,在角上显示标记',
94
+ Timeline: 'active(已完成到第几项), bulletSize;子元素用 Timeline.Item',
95
+ 'Timeline.Item': 'title, bullet;子节点是说明',
96
+ Table: 'data={{ head: [...], body: [[...]] }} 或 Table.Thead/Tbody/Tr/Th/Td;striped, withTableBorder',
97
+ 'Table.Thead': '表头',
98
+ 'Table.Tbody': '表体',
99
+ 'Table.Tr': '行',
100
+ 'Table.Th': '表头单元格',
101
+ 'Table.Td': '单元格',
102
+ DataTable: 'records(可绑定 $.data.x), columns: [{ accessor, title }], noRecordsText —— 对齐 mantine-datatable',
103
+ Accordion: 'value / defaultValue(展开项);select 动作写入展开项;子元素用 Accordion.Item',
104
+ 'Accordion.Item': 'value;内含 Accordion.Control 与 Accordion.Panel',
105
+ 'Accordion.Panel': '展开后显示的内容',
106
+ Radio: 'value, label —— 放在 Radio.Group 里',
107
+ 'Tabs.List': '标签栏;放 Tabs.Tab',
108
+ 'Tabs.Panel': 'value —— 当前标签的内容',
109
+ Breadcrumbs: 'separator;子节点放 Anchor 或文字',
110
+ Stepper: 'active(从 0 开始);子元素用 Stepper.Step',
111
+ 'Stepper.Step': 'label, description;子节点是该步内容(只显示当前步)',
112
+ Alert: 'title, color(默认 blue), variant;子节点是说明',
113
+ Loader: 'size, color, type;可带子文字',
114
+ Progress: 'value 0-100, color, size, striped',
115
+ RingProgress: 'sections: [{ value, color }], label, size, thickness',
116
+ Skeleton: 'h, w, circle, radius, visible',
117
+ Notification: 'title, color, loading, withCloseButton —— 页面内的通知条(临时提示用步骤里的 toast)',
118
+ Modal: 'opened(门控,常写 opened={$.ui.open}), title, size, centered, fullScreen',
119
+ Drawer: 'opened(门控), title, position: left|right|top|bottom(默认 left), size',
120
+ ChatMessage: "from: 'user'|'assistant', name;子节点是消息内容(保留换行)",
121
+ ToolCall: "title, state: 'running'|'done'|'error';子节点是调用详情",
122
+ Terminal: 'title —— 终端窗口(命令行原型),子节点是 Terminal.Command / Terminal.Output / Terminal.Input;内容超出 h 或 mah 时内部滚动',
123
+ Diagram: "direction: 'right'|'down'(默认 right), h(高度,默认 420)—— 架构图,子节点是 Diagram.Group / Diagram.Node / Diagram.Edge;布局自动生成且确定(state 变化不移动节点);整图缩放放进容器(不滚动),看细节用演示页统一的缩放平移(滚轮 / 捏合 / 拖动)或镜头推近;连线正交走线、出入口与平行段间距 ≥ 10px;shot 同一张图里 2 个及以上节点(或一条连线)时,这些节点移到中央排成紧凑的聚焦小图;节点多、宽度有限时用 direction='down'。逐段出现:给 Group / Node / Edge 写 when={$.ui.x},flow 里 set 为真时出现(布局按全部元素预先算好,已有元素不动);调用链用步骤里的 trace() 描述,节点与连线的 state 自动推算",
124
+ 'Diagram.Group': 'label —— 带标题的分组框,子节点是 Diagram.Node 或嵌套的 Diagram.Group',
125
+ 'Diagram.Node': "id(必填,连线与 trace 靠它), label, description(第二行小字), kind: 'client'|'service'|'model'|'database'|'queue'|'storage'|'external'|'tool'|'user', state(可选): 'idle'|'active'|'ok'|'error'|'dim'——不写时由步骤里的 trace() 自动推算;写了就以它为准",
126
+ 'Diagram.Edge': "from, to(同一个 Diagram 里的 Node id), label, async(虚线), state(可选): 'idle'|'active'|'ok'|'error'|'dim';id 缺省为 \"from->to\"。演示调用链:步骤里写 trace('app', 'biz')(走这条线,自动变 active,之后 ok)、trace.fail(...)(error)、trace.skip(...)(dim),不用手写 state;同一方向有多条连线时 trace('连线 id')",
127
+ 'Terminal.Command': 'prompt(默认 "$");子节点是命令文字',
128
+ 'Terminal.Output': "tone: 'plain'|'ok'|'warn'|'error'(默认 plain);子节点是输出文字(保留换行)",
129
+ Phone: 'scale(视觉缩放,默认 1), h(屏幕高度,默认 844)—— 手机外框,子节点按手机宽度 390 排版,响应式断点按这个宽度计算;页面写 viewport: "mobile" 时舞台自动套上,不用写它',
130
+ div: 'HTML 兜底,建议改用 Box/Stack/Group',
131
+ span: 'HTML 兜底,建议改用 Text span',
132
+ p: 'HTML 兜底,建议改用 Text',
133
+ h1: 'HTML 兜底,建议改用 Title order={1}',
134
+ h2: 'HTML 兜底,建议改用 Title order={2}',
135
+ h3: 'HTML 兜底,建议改用 Title order={3}',
136
+ h4: 'HTML 兜底,建议改用 Title order={4}',
137
+ main: 'HTML 兜底,建议改用 AppShell.Main',
138
+ section: 'HTML 兜底,建议改用 Box',
139
+ header: 'HTML 兜底,建议改用 AppShell.Header',
140
+ footer: 'HTML 兜底,建议改用 AppShell.Footer',
141
+ nav: 'HTML 兜底,建议改用 AppShell.Navbar',
142
+ aside: 'HTML 兜底,建议改用 AppShell.Aside',
143
+ ul: 'HTML 兜底,建议改用 List',
144
+ ol: 'HTML 兜底,建议改用 List type="ordered"',
145
+ li: 'HTML 兜底,建议改用 List.Item',
146
+ strong: 'HTML 兜底,建议改用 Text fw={700} span',
147
+ em: 'HTML 兜底,建议改用 Text fs="italic" span',
148
+ small: 'HTML 兜底,建议改用 Text size="xs"',
149
+ code: 'HTML 兜底,建议改用 Code',
150
+ Placeholder: 'label, h —— 图片、图表等占位',
151
+ Custom: 'kind —— 目录没有的组件,渲染为带说明的占位框',
152
+ '#text': '编译器生成的文本节点(不要直接使用)',
153
+ Button: 'variant: filled|light|outline|subtle|default|white, color, size, fullWidth, loading, leftSection(字符)',
154
+ ActionIcon: "图标按钮:aria-label(必写,用作名称), variant, color, size;子节点放一个字符如 '+'",
155
+ CloseButton: 'aria-label, size',
156
+ Anchor: 'href(看板内路径,不会真正跳转), c, underline',
157
+ a: 'HTML 兜底,建议改用 Anchor',
158
+ NavLink: 'label, description, href(等于当前路径时高亮), active, leftSection(字符);可嵌套 NavLink',
159
+ Burger: 'opened, aria-label —— 窄屏导航开关,常配 hiddenFrom="sm"',
160
+ TextInput: 'label, placeholder, description, error, withAsterisk, type —— type 动作写入',
161
+ PasswordInput: 'label, placeholder, description, error —— type 动作写入,显示为圆点',
162
+ NumberInput: 'label, placeholder, min, max, suffix, prefix —— type 动作写入',
163
+ Textarea: 'label, placeholder, description, error, minRows, autosize —— type 动作写入',
164
+ Select: 'label, data: string[] | { value, label }[], placeholder, description —— select 动作接受 value 或 label',
165
+ MultiSelect: 'label, data, placeholder —— 每次 select 动作切换一个选项',
166
+ Checkbox: 'label, description, checked —— toggle 动作写入',
167
+ 'Radio.Group': 'label, value;子元素用 Radio value/label —— select 动作接受 value 或 label',
168
+ Switch: 'label, description, checked —— toggle 动作写入',
169
+ SegmentedControl: 'data: string[] | { value, label }[], fullWidth —— select 动作接受 value 或 label',
170
+ FileInput: 'label, placeholder, accept —— type 动作写入文件名',
171
+ Tabs: 'value / defaultValue;Tabs.List 内放 Tabs.Tab,Tabs.Panel 放内容 —— select 动作切换标签',
172
+ 'Tabs.Tab': 'value, leftSection(字符)',
173
+ 'Accordion.Control': '可点击的标题(需要 id)',
174
+ Pagination: 'total, value / defaultValue —— select 动作写入页码',
175
+ PromptInput: 'placeholder —— AI 输入框,type 动作写入;发送按钮用 ActionIcon 或 Button',
176
+ 'Terminal.Input': 'prompt(默认 "$"), placeholder —— 终端里的输入行(需要 id),type 动作写入',
177
+ };
178
+ // ---------- 响应式(Mantine 默认断点,按舞台宽度取值) ----------
179
+ /** Mantine 默认断点:36/48/62/75/88em,按 16px 换算。 */
180
+ export const BREAKPOINTS = { xs: 576, sm: 768, md: 992, lg: 1200, xl: 1408 };
181
+ const BP_KEYS = ['base', 'xs', 'sm', 'md', 'lg', 'xl'];
182
+ /** 页面 viewport 对应的舞台尺寸。 */
183
+ export const VIEWPORT_SIZE = { desktop: { width: 1280, height: 800 }, mobile: { width: 390, height: 844 } };
184
+ /**
185
+ * 字幕区:画面下方、播放控制上方的一块固定高度区域,最多两行:一行本步说明(大字),一行 explain(小一号)。
186
+ * 演示页、发布站、renderStep 截图、导出图片与视频都把它放在舞台正下方,宽度与舞台(手机含外框)相同。
187
+ */
188
+ export const SUBTITLE_HEIGHT = 84;
189
+ /**
190
+ * 内置主题(D51):每个主题是一个只写 CSS 变量(--jp-*)的样式表,CSS 在 web 包里(renderer/themes.ts)。
191
+ * 作者可以在项目根目录 themes/<name>.css 写自己的主题;与内置同名时用作者的。
192
+ */
193
+ export const BUILTIN_THEMES = ['sketch', 'clean', 'blueprint', 'nord', 'dracula', 'solarized-light', 'terminal', 'kraft', 'contrast'];
194
+ /** 主题名:字母开头,字母、数字、连字符、下划线(也是 themes/ 下的文件名)。 */
195
+ export const THEME_NAME = /^[A-Za-z][\w-]*$/;
196
+ /** 字幕的“字数”:中文(及其他全角字符)一个算 1,英文、数字、标点按 0.5 算;空白不算。check 用它提示说明与 explain 过长。 */
197
+ export function captionLength(s) {
198
+ let n = 0;
199
+ for (const ch of s) {
200
+ if (/\s/.test(ch))
201
+ continue;
202
+ n += (ch.codePointAt(0) ?? 0) > 0x2e80 ? 1 : 0.5;
203
+ }
204
+ return n;
205
+ }
206
+ /** 说明 / explain 的建议上限(超过时 check 报 CAPTION_TOO_LONG / EXPLAIN_TOO_LONG)。 */
207
+ export const CAPTION_MAX = 24;
208
+ export const EXPLAIN_MAX = 48;
209
+ /** 没有声明 layers 的方案:镜头作用在路径页面上,记为这个伪层级(不出现在层级栏)。作者的层级键不能以 @ 开头。 */
210
+ export const PAGE_LAYER = '@page';
211
+ /** shot()(不带 id)在有层级的方案里:作用于“当时导演所在的层”,让它回到整体。 */
212
+ export const DIRECTOR_LAYER = '@director';
213
+ /** Diagram.Node 的 kind 与 Node / Edge 的 state(D41)。 */
214
+ export const DIAGRAM_NODE_KINDS = ['client', 'service', 'model', 'database', 'queue', 'storage', 'external', 'tool', 'user'];
215
+ export const DIAGRAM_STATES = ['idle', 'active', 'ok', 'error', 'dim'];
216
+ /** 子节点排版用的宽度:Phone 内部按手机宽度(响应式取值、visibleFrom / hiddenFrom、AppShell 收起都按它算),其余节点沿用外部宽度。 */
217
+ export function childWidth(type, width) {
218
+ return type === 'Phone' ? VIEWPORT_SIZE.mobile.width : width;
219
+ }
220
+ /** 是否为响应式对象 `{ base, sm, md }`:普通对象,至少一个键,且全部键都是断点名。 */
221
+ export function isResponsive(v) {
222
+ if (!v || typeof v !== 'object' || Array.isArray(v))
223
+ return false;
224
+ const keys = Object.keys(v);
225
+ return keys.length > 0 && keys.every((k) => BP_KEYS.includes(k));
226
+ }
227
+ /** 按舞台宽度取响应式属性的值:取不超过宽度的最大断点;都不满足时取 base;非响应式值原样返回。 */
228
+ export function atWidth(v, width) {
229
+ if (!isResponsive(v))
230
+ return v;
231
+ let out = undefined;
232
+ for (const k of BP_KEYS) {
233
+ if (!(k in v))
234
+ continue;
235
+ const min = k === 'base' ? 0 : BREAKPOINTS[k];
236
+ if (width >= min)
237
+ out = v[k];
238
+ }
239
+ return out;
240
+ }
241
+ /** 断点名、数字(px)或 `40em` / `600px` 字符串 → 像素;无法识别返回 null。 */
242
+ export function breakpointPx(bp) {
243
+ if (typeof bp === 'number' && Number.isFinite(bp))
244
+ return bp;
245
+ if (typeof bp !== 'string')
246
+ return null;
247
+ if (bp in BREAKPOINTS)
248
+ return BREAKPOINTS[bp];
249
+ const m = /^(\d+(?:\.\d+)?)(px|em|rem)?$/.exec(bp.trim());
250
+ if (!m)
251
+ return null;
252
+ return m[2] === 'em' || m[2] === 'rem' ? Number(m[1]) * 16 : Number(m[1]);
253
+ }
254
+ /** visibleFrom / hiddenFrom:在给定舞台宽度下是否被隐藏。 */
255
+ export function hiddenAtWidth(props, width) {
256
+ if (!props)
257
+ return false;
258
+ const from = breakpointPx(props.visibleFrom);
259
+ if (from !== null && width < from)
260
+ return true;
261
+ const until = breakpointPx(props.hiddenFrom);
262
+ if (until !== null && width >= until)
263
+ return true;
264
+ return false;
265
+ }
266
+ /** Mantine 的 data:string[]、{ value, label }[],或分组 { group, items }[]。 */
267
+ export function choicesFromData(data) {
268
+ if (!Array.isArray(data))
269
+ return [];
270
+ const out = [];
271
+ for (const d of data) {
272
+ if (typeof d === 'string' || typeof d === 'number')
273
+ out.push({ value: String(d), label: String(d) });
274
+ else if (d && typeof d === 'object') {
275
+ const o = d;
276
+ if (Array.isArray(o.items))
277
+ out.push(...choicesFromData(o.items));
278
+ else if (o.value !== undefined && o.value !== null)
279
+ out.push({ value: String(o.value), label: typeof o.label === 'string' ? o.label : String(o.value) });
280
+ }
281
+ }
282
+ return out;
283
+ }
284
+ /** 按 value 或 label 匹配一个选项。 */
285
+ export function matchChoice(choices, v) {
286
+ return choices.find((c) => c.value === v) ?? choices.find((c) => c.label === v) ?? null;
287
+ }
288
+ // ---------- 标签 ----------
289
+ const IS_BINDING = /^\$(?:ui|data|inputs)(?:\.|$)/;
290
+ /** 元素的人类可读标签:优先 label/title/aria-label/placeholder 等属性(Mantine 输入组件直接带 label),其次子文本。 */
291
+ export function nodeLabel(node) {
292
+ const p = node.props ?? {};
293
+ for (const k of ['label', 'title', 'aria-label', 'placeholder', 'name', 'kind', 'alt']) {
294
+ const v = p[k];
295
+ if (typeof v === 'string' && v && !IS_BINDING.test(v))
296
+ return v;
297
+ }
298
+ // 没有 label 的连线:「from → to」
299
+ if (node.type === 'Diagram.Edge' && typeof p.from === 'string' && typeof p.to === 'string')
300
+ return `${p.from} → ${p.to}`;
301
+ const text = collectText(node).trim();
302
+ return text || node.type;
303
+ }
304
+ function collectText(node) {
305
+ if (node.type === '#text') {
306
+ const t = node.props?.text;
307
+ return typeof t === 'string' && !IS_BINDING.test(t) ? t : '';
308
+ }
309
+ return (node.children ?? []).map(collectText).join('');
310
+ }
@@ -0,0 +1,32 @@
1
+ import type { ConfigDef, FeatureDef, LayoutDef, OptionDef, PageDef } from '@joplo-app/kit';
2
+ import { Project } from './schema.js';
3
+ import type { Diagnostic } from './result.js';
4
+ export type AuthoringProject = {
5
+ /** 项目根目录绝对路径,用于把源位置改成相对路径。 */
6
+ root: string;
7
+ config: ConfigDef & {
8
+ file: string;
9
+ };
10
+ layouts: Record<string, LayoutDef & {
11
+ file: string;
12
+ }>;
13
+ routes: Record<string, PageDef & {
14
+ file: string;
15
+ }>;
16
+ features: Record<string, {
17
+ def: FeatureDef & {
18
+ file: string;
19
+ };
20
+ options: Record<string, OptionDef & {
21
+ file: string;
22
+ }>;
23
+ }>;
24
+ /** 作者主题(D51):themes/<name>.css 的原文。 */
25
+ themes?: Record<string, string>;
26
+ };
27
+ export type CompileResult = {
28
+ project: Project | null;
29
+ diagnostics: Diagnostic[];
30
+ };
31
+ export declare function compile(src: AuthoringProject, mode: 'dev' | 'publish'): CompileResult;
32
+ export declare function relSource(root: string, s: string): string;