@tokenroll/joplo-kit 0.0.0-stage → 0.1.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 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,236 @@
1
+ /**
2
+ * @tokenroll/joplo-kit:Agent 创作 Joplo 项目时调用的 SDK。
3
+ *
4
+ * joplo.config.ts config({...})
5
+ * layouts/<id>.tsx layout(<div>…<Outlet/>…</div>)
6
+ * routes/<path>/page.tsx page({ title }, <…/>) 文件夹路径 = 页面路径
7
+ * features/<f>/feature.ts feature({ name, goal, cases, rules, pick })
8
+ * features/<f>/options/<o>.tsx option({ name, idea, on, routes, state, scenarios })
9
+ *
10
+ * 所有函数只返回数据;画板不会执行这些代码,只消费编译出的 IR。
11
+ */
12
+ import { type Bind, type CondExpr, type JNode } from './node.js';
13
+ export { $, and, empty, eq, Fragment, neq, not, notEmpty, oneOf, or, Outlet, Slot } from './node.js';
14
+ export type { Bind, CondExpr, JNode } from './node.js';
15
+ type Json = string | number | boolean | null | Json[] | {
16
+ [k: string]: Json;
17
+ };
18
+ export type StateInit = {
19
+ ui?: Record<string, Json>;
20
+ data?: Record<string, Json>;
21
+ inputs?: Record<string, Json>;
22
+ };
23
+ export type ConfigDef = {
24
+ kind: 'config';
25
+ name: string;
26
+ description?: string;
27
+ theme?: 'sketch' | 'clean';
28
+ layout?: string;
29
+ home?: string;
30
+ source?: string;
31
+ };
32
+ export declare const config: (c: Omit<ConfigDef, "kind" | "source">) => ConfigDef;
33
+ export type LayoutDef = {
34
+ kind: 'layout';
35
+ tree: JNode;
36
+ source?: string;
37
+ };
38
+ export declare const layout: (tree: JNode) => LayoutDef;
39
+ export type PageDef = {
40
+ kind: 'page';
41
+ title: string;
42
+ layout?: string | false;
43
+ viewport?: 'desktop' | 'mobile';
44
+ tree: JNode;
45
+ source?: string;
46
+ };
47
+ export declare const page: (meta: {
48
+ title: string;
49
+ layout?: string | false;
50
+ viewport?: "desktop" | "mobile";
51
+ }, tree: JNode) => PageDef;
52
+ export type Dimension = 'main' | 'input' | 'permission' | 'lifecycle' | 'async' | 'interrupt' | 'conflict' | 'consistency';
53
+ export type CaseDef = string | {
54
+ title: string;
55
+ dimension?: Dimension;
56
+ expected?: string;
57
+ priority?: 'high' | 'medium' | 'low';
58
+ };
59
+ export type Origin = 'user' | 'project_material' | 'ai_assumption' | 'code_observed' | 'feedback';
60
+ export type RuleDef = {
61
+ kind: 'rule';
62
+ text: string;
63
+ origin: Origin;
64
+ status: 'pending' | 'confirmed' | 'conflict' | 'out_of_scope';
65
+ cases: string[];
66
+ elements: string[];
67
+ source?: string;
68
+ };
69
+ /** 规则。默认 origin 为 ai_assumption、status 为 pending:AI 补的规则不能自称已确认。 */
70
+ export declare const rule: (text: string, opts?: {
71
+ origin?: Origin;
72
+ status?: RuleDef["status"];
73
+ cases?: string[];
74
+ elements?: string[];
75
+ }) => RuleDef;
76
+ export type QuestionDef = {
77
+ kind: 'question';
78
+ text: string;
79
+ severity: 'blocker' | 'major' | 'minor';
80
+ refs: string[];
81
+ source?: string;
82
+ };
83
+ /** 待决问题。 */
84
+ export declare const question: (text: string, opts?: {
85
+ severity?: QuestionDef["severity"];
86
+ refs?: string[];
87
+ }) => QuestionDef;
88
+ export type FeatureDef = {
89
+ kind: 'feature';
90
+ name: string;
91
+ goal: string;
92
+ roles?: string[];
93
+ constraints?: Record<string, string | {
94
+ text: string;
95
+ hard?: boolean;
96
+ }>;
97
+ cases: Record<string, CaseDef>;
98
+ rules?: Record<string, RuleDef>;
99
+ questions?: Record<string, QuestionDef>;
100
+ /** 选定的方案(options/ 下的文件名);build 只发布它。 */
101
+ pick?: string | null;
102
+ source?: string;
103
+ };
104
+ export declare const feature: (f: Omit<FeatureDef, "kind" | "source">) => FeatureDef;
105
+ export type CoverageDecl = {
106
+ kind: 'coverage';
107
+ status: 'undecided' | 'missing' | 'not_applicable' | 'excluded';
108
+ reason?: string;
109
+ };
110
+ export declare const undecided: (reason: string) => CoverageDecl;
111
+ export declare const missing: (reason?: string) => CoverageDecl;
112
+ export declare const notApplicable: (reason: string) => CoverageDecl;
113
+ export declare const excluded: (reason: string) => CoverageDecl;
114
+ export type EffectDef = {
115
+ kind: 'effect';
116
+ type: 'set';
117
+ path: string;
118
+ value: Json;
119
+ } | {
120
+ kind: 'effect';
121
+ type: 'append';
122
+ path: string;
123
+ value: Json;
124
+ } | {
125
+ kind: 'effect';
126
+ type: 'toast';
127
+ text: string;
128
+ tone: 'success' | 'error' | 'info';
129
+ } | {
130
+ kind: 'effect';
131
+ type: 'go';
132
+ route: string;
133
+ } | {
134
+ kind: 'effect';
135
+ type: 'complete';
136
+ text: string;
137
+ outcome: 'success' | 'failure' | 'cancelled';
138
+ } | {
139
+ kind: 'effect';
140
+ type: 'uses';
141
+ rules: string[];
142
+ };
143
+ export declare const set: (target: Bind, value: Json) => EffectDef;
144
+ export declare const push: (target: Bind, value: Json) => EffectDef;
145
+ /** 与 sonner 一致:toast('…')、toast.success('…')、toast.error('…')。 */
146
+ export declare const toast: ((text: string) => EffectDef) & {
147
+ success: (text: string) => EffectDef;
148
+ error: (text: string) => EffectDef;
149
+ info: (text: string) => EffectDef;
150
+ };
151
+ /** 跳转到另一个路径。 */
152
+ export declare const go: (route: string) => EffectDef;
153
+ /** 场景结束。 */
154
+ export declare const done: (text: string, outcome?: "success" | "failure" | "cancelled") => EffectDef;
155
+ /** 这一步依据哪些规则(option 或 feature 中的规则键)。 */
156
+ export declare const uses: (...rules: string[]) => EffectDef;
157
+ /** 步骤参数:字符串是字幕(可省略,省略时自动生成),其余是效果。 */
158
+ export type StepArg = string | EffectDef;
159
+ export type StepDef = {
160
+ kind: 'user' | 'system';
161
+ action: {
162
+ type: 'click';
163
+ target: string;
164
+ } | {
165
+ type: 'type';
166
+ target: string;
167
+ value: string;
168
+ } | {
169
+ type: 'select';
170
+ target: string;
171
+ value: string;
172
+ } | {
173
+ type: 'toggle';
174
+ target: string;
175
+ value: boolean;
176
+ } | {
177
+ type: 'back';
178
+ } | {
179
+ type: 'event';
180
+ name: string;
181
+ };
182
+ caption?: string;
183
+ effects: EffectDef[];
184
+ source?: string;
185
+ };
186
+ export type FlowOpts = {
187
+ /** 覆盖的情况(feature.cases 的键)。 */
188
+ cases?: string[];
189
+ /** 模拟结果标签,例如“模拟失败”。缺省按 outcome 生成。 */
190
+ mock?: string;
191
+ outcome?: 'success' | 'failure' | 'denied' | 'other';
192
+ /** 起始路径;缺省为方案 on/routes 中的第一个路径。 */
193
+ start?: string;
194
+ /** 在 option.state 之上覆盖的初始状态。 */
195
+ state?: StateInit;
196
+ description?: string;
197
+ expected?: string;
198
+ };
199
+ export declare class Flow {
200
+ readonly name: string;
201
+ readonly opts: FlowOpts;
202
+ readonly kind = "flow";
203
+ readonly steps: StepDef[];
204
+ readonly source: string | undefined;
205
+ constructor(name: string, opts: FlowOpts);
206
+ private add;
207
+ click(target: string, ...args: StepArg[]): this;
208
+ type(target: string, value: string, ...args: StepArg[]): this;
209
+ select(target: string, value: string, ...args: StepArg[]): this;
210
+ toggle(target: string, value: boolean, ...args: StepArg[]): this;
211
+ back(...args: StepArg[]): this;
212
+ /** 系统事件,例如“请求成功(模拟)”。 */
213
+ system(name: string, ...args: StepArg[]): this;
214
+ }
215
+ /** 一条演示路径。 */
216
+ export declare const flow: (name: string, opts?: FlowOpts) => Flow;
217
+ export type OptionDef = {
218
+ kind: 'option';
219
+ name: string;
220
+ idea: string;
221
+ /** 这个方案主要改变了哪项设计决策。 */
222
+ focus?: string;
223
+ tradeoffs?: string[];
224
+ /** 堆叠到已有路径:{ '/members': { 'header.actions': <Button/>, overlay: <Dialog/> } } */
225
+ on?: Record<string, Record<string, JNode | JNode[]>>;
226
+ /** 本方案新增的路径。 */
227
+ routes?: Record<string, PageDef>;
228
+ state?: StateInit;
229
+ rules?: Record<string, RuleDef>;
230
+ questions?: Record<string, QuestionDef>;
231
+ coverage?: Record<string, CoverageDecl>;
232
+ scenarios: Record<string, Flow>;
233
+ source?: string;
234
+ };
235
+ export declare const option: (o: Omit<OptionDef, "kind" | "source">) => OptionDef;
236
+ export type { CondExpr as Condition };
package/dist/index.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * @tokenroll/joplo-kit:Agent 创作 Joplo 项目时调用的 SDK。
3
+ *
4
+ * joplo.config.ts config({...})
5
+ * layouts/<id>.tsx layout(<div>…<Outlet/>…</div>)
6
+ * routes/<path>/page.tsx page({ title }, <…/>) 文件夹路径 = 页面路径
7
+ * features/<f>/feature.ts feature({ name, goal, cases, rules, pick })
8
+ * features/<f>/options/<o>.tsx option({ name, idea, on, routes, state, scenarios })
9
+ *
10
+ * 所有函数只返回数据;画板不会执行这些代码,只消费编译出的 IR。
11
+ */
12
+ import { bindPath, callsite, isBind } from "./node.js";
13
+ export { $, and, empty, eq, Fragment, neq, not, notEmpty, oneOf, or, Outlet, Slot } from "./node.js";
14
+ export const config = (c) => ({ kind: 'config', ...c, source: callsite() });
15
+ export const layout = (tree) => ({ kind: 'layout', tree, source: callsite() });
16
+ export const page = (meta, tree) => ({
17
+ kind: 'page',
18
+ ...meta,
19
+ tree,
20
+ source: callsite(),
21
+ });
22
+ /** 规则。默认 origin 为 ai_assumption、status 为 pending:AI 补的规则不能自称已确认。 */
23
+ export const rule = (text, opts = {}) => ({
24
+ kind: 'rule',
25
+ text,
26
+ origin: opts.origin ?? 'ai_assumption',
27
+ status: opts.status ?? 'pending',
28
+ cases: opts.cases ?? [],
29
+ elements: opts.elements ?? [],
30
+ source: callsite(),
31
+ });
32
+ /** 待决问题。 */
33
+ export const question = (text, opts = {}) => ({
34
+ kind: 'question',
35
+ text,
36
+ severity: opts.severity ?? 'major',
37
+ refs: opts.refs ?? [],
38
+ source: callsite(),
39
+ });
40
+ export const feature = (f) => ({ kind: 'feature', ...f, source: callsite() });
41
+ export const undecided = (reason) => ({ kind: 'coverage', status: 'undecided', reason });
42
+ export const missing = (reason) => ({ kind: 'coverage', status: 'missing', ...(reason ? { reason } : {}) });
43
+ export const notApplicable = (reason) => ({ kind: 'coverage', status: 'not_applicable', reason });
44
+ export const excluded = (reason) => ({ kind: 'coverage', status: 'excluded', reason });
45
+ const pathOf = (b) => (isBind(b) ? bindPath(b) : b);
46
+ export const set = (target, value) => ({ kind: 'effect', type: 'set', path: pathOf(target), value });
47
+ export const push = (target, value) => ({ kind: 'effect', type: 'append', path: pathOf(target), value });
48
+ /** 与 sonner 一致:toast('…')、toast.success('…')、toast.error('…')。 */
49
+ export const toast = Object.assign((text) => ({ kind: 'effect', type: 'toast', text, tone: 'info' }), {
50
+ success: (text) => ({ kind: 'effect', type: 'toast', text, tone: 'success' }),
51
+ error: (text) => ({ kind: 'effect', type: 'toast', text, tone: 'error' }),
52
+ info: (text) => ({ kind: 'effect', type: 'toast', text, tone: 'info' }),
53
+ });
54
+ /** 跳转到另一个路径。 */
55
+ export const go = (route) => ({ kind: 'effect', type: 'go', route });
56
+ /** 场景结束。 */
57
+ export const done = (text, outcome = 'success') => ({ kind: 'effect', type: 'complete', text, outcome });
58
+ /** 这一步依据哪些规则(option 或 feature 中的规则键)。 */
59
+ export const uses = (...rules) => ({ kind: 'effect', type: 'uses', rules });
60
+ export class Flow {
61
+ name;
62
+ opts;
63
+ kind = 'flow';
64
+ steps = [];
65
+ source;
66
+ constructor(name, opts) {
67
+ this.name = name;
68
+ this.opts = opts;
69
+ this.source = callsite(4);
70
+ }
71
+ add(kind, action, args) {
72
+ const caption = args.find((a) => typeof a === 'string');
73
+ const effects = args.filter((a) => typeof a !== 'string');
74
+ this.steps.push({ kind, action, ...(caption ? { caption } : {}), effects, source: callsite(4) });
75
+ return this;
76
+ }
77
+ click(target, ...args) {
78
+ return this.add('user', { type: 'click', target }, args);
79
+ }
80
+ type(target, value, ...args) {
81
+ return this.add('user', { type: 'type', target, value }, args);
82
+ }
83
+ select(target, value, ...args) {
84
+ return this.add('user', { type: 'select', target, value }, args);
85
+ }
86
+ toggle(target, value, ...args) {
87
+ return this.add('user', { type: 'toggle', target, value }, args);
88
+ }
89
+ back(...args) {
90
+ return this.add('user', { type: 'back' }, args);
91
+ }
92
+ /** 系统事件,例如“请求成功(模拟)”。 */
93
+ system(name, ...args) {
94
+ return this.add('system', { type: 'event', name }, args);
95
+ }
96
+ }
97
+ /** 一条演示路径。 */
98
+ export const flow = (name, opts = {}) => new Flow(name, opts);
99
+ export const option = (o) => ({ kind: 'option', ...o, source: callsite() });
@@ -0,0 +1,9 @@
1
+ import { Fragment, type JNode } from './node.js';
2
+ export { Fragment };
3
+ export type { JSX } from './jsx-runtime.js';
4
+ /** 开发模式:esbuild/Vite 会传入源位置,用于“选中元素 → 文件:行号”。 */
5
+ export declare function jsxDEV(type: unknown, props: Record<string, unknown> | null, _key?: unknown, _isStatic?: boolean, source?: {
6
+ fileName?: string;
7
+ lineNumber?: number;
8
+ columnNumber?: number;
9
+ }): JNode;
@@ -0,0 +1,6 @@
1
+ import { createNode, Fragment } from "./node.js";
2
+ export { Fragment };
3
+ /** 开发模式:esbuild/Vite 会传入源位置,用于“选中元素 → 文件:行号”。 */
4
+ export function jsxDEV(type, props, _key, _isStatic, source) {
5
+ return createNode(type, props, source);
6
+ }
@@ -0,0 +1,13 @@
1
+ import { Fragment, type JNode } from './node.js';
2
+ export { Fragment };
3
+ export declare const jsx: (type: unknown, props: Record<string, unknown> | null) => JNode;
4
+ export declare const jsxs: (type: unknown, props: Record<string, unknown> | null) => JNode;
5
+ export declare namespace JSX {
6
+ type Element = JNode;
7
+ interface ElementChildrenAttribute {
8
+ children: {};
9
+ }
10
+ interface IntrinsicElements {
11
+ [tag: string]: Record<string, unknown>;
12
+ }
13
+ }
@@ -0,0 +1,4 @@
1
+ import { createNode, Fragment } from "./node.js";
2
+ export { Fragment };
3
+ export const jsx = (type, props) => createNode(type, props);
4
+ export const jsxs = jsx;
package/dist/node.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * JSX 收集:`<Button id="x">文字</Button>` 在这里变成普通数据(JNode),不会作为 React 执行。
3
+ * 作者自己写的函数组件(例如复用的页面骨架)会在收集时直接调用,展开成 JNode。
4
+ */
5
+ export declare const NODE: unique symbol;
6
+ export declare const COMPONENT: unique symbol;
7
+ export declare const BIND: unique symbol;
8
+ export type Bind = {
9
+ readonly [BIND]: string;
10
+ };
11
+ export type CondExpr = {
12
+ readonly $cond: unknown;
13
+ };
14
+ export type JNode = {
15
+ readonly [NODE]: true;
16
+ type: string;
17
+ props: Record<string, unknown>;
18
+ children: JChild[];
19
+ /** jsxDEV 提供的源位置(绝对路径:行:列),编译器会改成项目相对路径。 */
20
+ source?: string;
21
+ };
22
+ export type JChild = JNode | string | number | boolean | null | undefined | Bind | JChild[];
23
+ /** 目录组件的标记函数。 */
24
+ export type ComponentMarker = ((props: Record<string, unknown>) => never) & {
25
+ readonly [COMPONENT]: string;
26
+ };
27
+ export declare function component(name: string): ComponentMarker;
28
+ export declare const isNode: (v: unknown) => v is JNode;
29
+ export declare const isBind: (v: unknown) => v is Bind;
30
+ export declare const bindPath: (b: Bind) => string;
31
+ export declare const Fragment: ComponentMarker;
32
+ /** 布局或页面中的插槽:方案把元素堆叠到这里。 */
33
+ export declare const Slot: (props: {
34
+ name: string;
35
+ }) => JNode;
36
+ /** 布局中页面内容的位置。 */
37
+ export declare const Outlet: (props: Record<string, never>) => JNode;
38
+ type Source = {
39
+ fileName?: string;
40
+ lineNumber?: number;
41
+ columnNumber?: number;
42
+ } | undefined;
43
+ export declare function createNode(type: unknown, rawProps: Record<string, unknown> | null, source?: Source): JNode;
44
+ /** 状态引用:`$.ui.inviteOpen`、`$.data.members`、`$.inputs.in_email`。 */
45
+ export declare const $: {
46
+ ui: Record<string, any>;
47
+ data: Record<string, any>;
48
+ inputs: Record<string, any>;
49
+ };
50
+ type Operand = Bind | CondExpr;
51
+ export declare const not: (x: Operand) => CondExpr;
52
+ export declare const eq: (b: Bind, v: unknown) => CondExpr;
53
+ export declare const neq: (b: Bind, v: unknown) => CondExpr;
54
+ export declare const empty: (b: Bind) => CondExpr;
55
+ export declare const notEmpty: (b: Bind) => CondExpr;
56
+ export declare const oneOf: (b: Bind, values: unknown[]) => CondExpr;
57
+ export declare const and: (...xs: Operand[]) => CondExpr;
58
+ export declare const or: (...xs: Operand[]) => CondExpr;
59
+ export declare const isCond: (v: unknown) => v is CondExpr;
60
+ /** 把 Bind 或条件表达式转成 IR 条件。 */
61
+ export declare const toCondition: (x: unknown) => unknown;
62
+ /** 取调用者位置,用于给 feature/option/flow 步骤记录源位置。 */
63
+ export declare function callsite(depth?: number): string | undefined;
64
+ export {};
package/dist/node.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * JSX 收集:`<Button id="x">文字</Button>` 在这里变成普通数据(JNode),不会作为 React 执行。
3
+ * 作者自己写的函数组件(例如复用的页面骨架)会在收集时直接调用,展开成 JNode。
4
+ */
5
+ export const NODE = Symbol.for('joplo.node');
6
+ export const COMPONENT = Symbol.for('joplo.component');
7
+ export const BIND = Symbol.for('joplo.bind');
8
+ export function component(name) {
9
+ const fn = (() => {
10
+ throw new Error(`${name} 是 Joplo 组件标记,只能在 JSX 中使用`);
11
+ });
12
+ Object.defineProperty(fn, COMPONENT, { value: name });
13
+ Object.defineProperty(fn, 'displayName', { value: name });
14
+ return fn;
15
+ }
16
+ export const isNode = (v) => typeof v === 'object' && v !== null && v[NODE] === true;
17
+ export const isBind = (v) => (typeof v === 'object' || typeof v === 'function') && v !== null && typeof v[BIND] === 'string';
18
+ export const bindPath = (b) => b[BIND];
19
+ export const Fragment = component('#fragment');
20
+ /** 布局或页面中的插槽:方案把元素堆叠到这里。 */
21
+ export const Slot = component('#slot');
22
+ /** 布局中页面内容的位置。 */
23
+ export const Outlet = component('#outlet');
24
+ export function createNode(type, rawProps, source) {
25
+ const props = { ...(rawProps ?? {}) };
26
+ const children = props.children;
27
+ delete props.children;
28
+ const src = source?.fileName ? `${source.fileName}:${source.lineNumber ?? 0}:${source.columnNumber ?? 0}` : undefined;
29
+ if (typeof type === 'string')
30
+ return mk(type, props, children, src);
31
+ if (typeof type === 'function') {
32
+ const name = type[COMPONENT];
33
+ if (name)
34
+ return mk(name, props, children, src);
35
+ // 作者自定义的函数组件:收集时展开
36
+ const out = type({ ...props, children });
37
+ if (isNode(out)) {
38
+ if (!out.source && src)
39
+ out.source = src;
40
+ return out;
41
+ }
42
+ return mk('#fragment', {}, out, src);
43
+ }
44
+ throw new Error(`不支持的 JSX 类型:${String(type)}`);
45
+ }
46
+ function mk(type, props, children, source) {
47
+ const node = { [NODE]: true, type, props, children: children === undefined ? [] : Array.isArray(children) ? children : [children] };
48
+ if (source)
49
+ node.source = source;
50
+ return node;
51
+ }
52
+ // ---------- 状态绑定 $ ----------
53
+ function makeBind(path) {
54
+ const target = function () { };
55
+ return new Proxy(target, {
56
+ get(_t, prop) {
57
+ if (prop === BIND)
58
+ return path;
59
+ if (prop === Symbol.toPrimitive)
60
+ return () => `$${path}`;
61
+ if (prop === 'toString' || prop === 'valueOf')
62
+ return () => `$${path}`;
63
+ if (typeof prop === 'symbol')
64
+ return undefined;
65
+ return makeBind(`${path}.${prop}`);
66
+ },
67
+ has(_t, prop) {
68
+ return prop === BIND;
69
+ },
70
+ });
71
+ }
72
+ /** 状态引用:`$.ui.inviteOpen`、`$.data.members`、`$.inputs.in_email`。 */
73
+ export const $ = {
74
+ ui: makeBind('ui'),
75
+ data: makeBind('data'),
76
+ inputs: makeBind('inputs'),
77
+ };
78
+ const cond = (c) => ({ $cond: c });
79
+ const asCond = (x) => (isBind(x) ? { truthy: bindPath(x) } : x.$cond);
80
+ export const not = (x) => cond(isBind(x) ? { falsy: bindPath(x) } : { not: asCond(x) });
81
+ export const eq = (b, v) => cond({ eq: [bindPath(b), v] });
82
+ export const neq = (b, v) => cond({ neq: [bindPath(b), v] });
83
+ export const empty = (b) => cond({ empty: bindPath(b) });
84
+ export const notEmpty = (b) => cond({ notEmpty: bindPath(b) });
85
+ export const oneOf = (b, values) => cond({ in: [bindPath(b), values] });
86
+ export const and = (...xs) => cond({ and: xs.map(asCond) });
87
+ export const or = (...xs) => cond({ or: xs.map(asCond) });
88
+ export const isCond = (v) => typeof v === 'object' && v !== null && '$cond' in v;
89
+ /** 把 Bind 或条件表达式转成 IR 条件。 */
90
+ export const toCondition = (x) => (isBind(x) ? { truthy: bindPath(x) } : isCond(x) ? x.$cond : x);
91
+ /** 取调用者位置,用于给 feature/option/flow 步骤记录源位置。 */
92
+ export function callsite(depth = 3) {
93
+ const line = new Error().stack?.split('\n')[depth];
94
+ const m = line && /\(?((?:file:\/\/)?[^()\s]+?):(\d+):(\d+)\)?\s*$/.exec(line);
95
+ if (!m)
96
+ return undefined;
97
+ const file = decodeURIComponent(m[1].replace(/^file:\/\//, '').replace(/\?.*$/, ''));
98
+ return `${file}:${m[2]}:${m[3]}`;
99
+ }
package/dist/ui.d.ts ADDED
@@ -0,0 +1,131 @@
1
+ /**
2
+ * 原型组件:名称与属性对齐 Mantine(数据表对齐 mantine-datatable),复合组件用点语法(AppShell.Header、Tabs.Tab)。
3
+ * 布局写成属性:<Stack gap="md">、<Group justify="space-between">、<SimpleGrid cols={{ base: 1, md: 3 }}>,
4
+ * 不需要 className;响应式对象与 visibleFrom / hiddenFrom 按舞台宽度生效。
5
+ *
6
+ * 另可加 Joplo 属性:id(可操作元素必填)、when(可见条件)、disabled(布尔或条件)、disabledReason、unmodeled、note。
7
+ * 行为(点击后发生什么)不写在组件上,写在 option 的 flow 步骤里。浮层用 Modal / Drawer 的 opened={$.ui.x} 控制。
8
+ *
9
+ * 组件清单与 @tokenroll/joplo-core 的 COMPONENT_TYPES 一一对应(core/test/catalog.test.ts 检查)。
10
+ */
11
+ import { type Bind, type CondExpr, type JChild } from './node.js';
12
+ type Responsive<T> = T | {
13
+ base?: T;
14
+ xs?: T;
15
+ sm?: T;
16
+ md?: T;
17
+ lg?: T;
18
+ xl?: T;
19
+ };
20
+ type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
21
+ export type JoploProps = {
22
+ id?: string;
23
+ when?: Bind | CondExpr;
24
+ disabled?: boolean | Bind | CondExpr;
25
+ disabledReason?: string;
26
+ unmodeled?: boolean;
27
+ note?: string;
28
+ visibleFrom?: Size;
29
+ hiddenFrom?: Size;
30
+ /** 逃生口:Tailwind 类名。优先使用布局组件与样式属性。 */
31
+ className?: string;
32
+ children?: JChild;
33
+ [prop: string]: unknown;
34
+ };
35
+ export type { Responsive };
36
+ type C = (props: JoploProps) => any;
37
+ export declare const AppShell: C & {
38
+ Header: C;
39
+ Navbar: C;
40
+ Main: C;
41
+ Aside: C;
42
+ Footer: C;
43
+ };
44
+ export declare const Container: C;
45
+ export declare const Stack: C;
46
+ export declare const Group: C;
47
+ export declare const Flex: C;
48
+ export declare const Grid: C & {
49
+ Col: C;
50
+ };
51
+ export declare const SimpleGrid: C;
52
+ export declare const Center: C;
53
+ export declare const Box: C;
54
+ export declare const Space: C;
55
+ export declare const Paper: C;
56
+ export declare const Card: C & {
57
+ Section: C;
58
+ };
59
+ export declare const Divider: C;
60
+ export declare const ScrollArea: C;
61
+ export declare const Title: C;
62
+ export declare const Text: C;
63
+ export declare const Code: C;
64
+ export declare const Kbd: C;
65
+ export declare const Blockquote: C;
66
+ export declare const List: C & {
67
+ Item: C;
68
+ };
69
+ export declare const Badge: C;
70
+ export declare const Avatar: C;
71
+ export declare const Image: C;
72
+ export declare const ThemeIcon: C;
73
+ export declare const Indicator: C;
74
+ export declare const Timeline: C & {
75
+ Item: C;
76
+ };
77
+ export declare const Table: C & {
78
+ Thead: C;
79
+ Tbody: C;
80
+ Tr: C;
81
+ Th: C;
82
+ Td: C;
83
+ };
84
+ export declare const DataTable: C;
85
+ export declare const Accordion: C & {
86
+ Item: C;
87
+ Control: C;
88
+ Panel: C;
89
+ };
90
+ export declare const Button: C;
91
+ export declare const ActionIcon: C;
92
+ export declare const CloseButton: C;
93
+ export declare const TextInput: C;
94
+ export declare const PasswordInput: C;
95
+ export declare const NumberInput: C;
96
+ export declare const Textarea: C;
97
+ export declare const Select: C;
98
+ export declare const MultiSelect: C;
99
+ export declare const Checkbox: C;
100
+ export declare const Radio: C & {
101
+ Group: C;
102
+ };
103
+ export declare const Switch: C;
104
+ export declare const SegmentedControl: C;
105
+ export declare const FileInput: C;
106
+ export declare const Anchor: C;
107
+ export declare const NavLink: C;
108
+ export declare const Tabs: C & {
109
+ List: C;
110
+ Tab: C;
111
+ Panel: C;
112
+ };
113
+ export declare const Breadcrumbs: C;
114
+ export declare const Pagination: C;
115
+ export declare const Stepper: C & {
116
+ Step: C;
117
+ };
118
+ export declare const Burger: C;
119
+ export declare const Alert: C;
120
+ export declare const Loader: C;
121
+ export declare const Progress: C;
122
+ export declare const RingProgress: C;
123
+ export declare const Skeleton: C;
124
+ export declare const Notification: C;
125
+ export declare const Modal: C;
126
+ export declare const Drawer: C;
127
+ export declare const ChatMessage: C;
128
+ export declare const ToolCall: C;
129
+ export declare const PromptInput: C;
130
+ export declare const Placeholder: C;
131
+ export declare const Custom: C;
package/dist/ui.js ADDED
@@ -0,0 +1,98 @@
1
+ /**
2
+ * 原型组件:名称与属性对齐 Mantine(数据表对齐 mantine-datatable),复合组件用点语法(AppShell.Header、Tabs.Tab)。
3
+ * 布局写成属性:<Stack gap="md">、<Group justify="space-between">、<SimpleGrid cols={{ base: 1, md: 3 }}>,
4
+ * 不需要 className;响应式对象与 visibleFrom / hiddenFrom 按舞台宽度生效。
5
+ *
6
+ * 另可加 Joplo 属性:id(可操作元素必填)、when(可见条件)、disabled(布尔或条件)、disabledReason、unmodeled、note。
7
+ * 行为(点击后发生什么)不写在组件上,写在 option 的 flow 步骤里。浮层用 Modal / Drawer 的 opened={$.ui.x} 控制。
8
+ *
9
+ * 组件清单与 @tokenroll/joplo-core 的 COMPONENT_TYPES 一一对应(core/test/catalog.test.ts 检查)。
10
+ */
11
+ import { component } from "./node.js";
12
+ const c = (name) => component(name);
13
+ // ---------- 布局 ----------
14
+ export const AppShell = Object.assign(c('AppShell'), {
15
+ Header: c('AppShell.Header'),
16
+ Navbar: c('AppShell.Navbar'),
17
+ Main: c('AppShell.Main'),
18
+ Aside: c('AppShell.Aside'),
19
+ Footer: c('AppShell.Footer'),
20
+ });
21
+ export const Container = c('Container');
22
+ export const Stack = c('Stack');
23
+ export const Group = c('Group');
24
+ export const Flex = c('Flex');
25
+ export const Grid = Object.assign(c('Grid'), { Col: c('Grid.Col') });
26
+ export const SimpleGrid = c('SimpleGrid');
27
+ export const Center = c('Center');
28
+ export const Box = c('Box');
29
+ export const Space = c('Space');
30
+ export const Paper = c('Paper');
31
+ export const Card = Object.assign(c('Card'), { Section: c('Card.Section') });
32
+ export const Divider = c('Divider');
33
+ export const ScrollArea = c('ScrollArea');
34
+ // ---------- 文字与展示 ----------
35
+ export const Title = c('Title');
36
+ export const Text = c('Text');
37
+ export const Code = c('Code');
38
+ export const Kbd = c('Kbd');
39
+ export const Blockquote = c('Blockquote');
40
+ export const List = Object.assign(c('List'), { Item: c('List.Item') });
41
+ export const Badge = c('Badge');
42
+ export const Avatar = c('Avatar');
43
+ export const Image = c('Image');
44
+ export const ThemeIcon = c('ThemeIcon');
45
+ export const Indicator = c('Indicator');
46
+ export const Timeline = Object.assign(c('Timeline'), { Item: c('Timeline.Item') });
47
+ export const Table = Object.assign(c('Table'), {
48
+ Thead: c('Table.Thead'),
49
+ Tbody: c('Table.Tbody'),
50
+ Tr: c('Table.Tr'),
51
+ Th: c('Table.Th'),
52
+ Td: c('Table.Td'),
53
+ });
54
+ export const DataTable = c('DataTable');
55
+ export const Accordion = Object.assign(c('Accordion'), {
56
+ Item: c('Accordion.Item'),
57
+ Control: c('Accordion.Control'),
58
+ Panel: c('Accordion.Panel'),
59
+ });
60
+ // ---------- 输入 ----------
61
+ export const Button = c('Button');
62
+ export const ActionIcon = c('ActionIcon');
63
+ export const CloseButton = c('CloseButton');
64
+ export const TextInput = c('TextInput');
65
+ export const PasswordInput = c('PasswordInput');
66
+ export const NumberInput = c('NumberInput');
67
+ export const Textarea = c('Textarea');
68
+ export const Select = c('Select');
69
+ export const MultiSelect = c('MultiSelect');
70
+ export const Checkbox = c('Checkbox');
71
+ export const Radio = Object.assign(c('Radio'), { Group: c('Radio.Group') });
72
+ export const Switch = c('Switch');
73
+ export const SegmentedControl = c('SegmentedControl');
74
+ export const FileInput = c('FileInput');
75
+ // ---------- 导航 ----------
76
+ export const Anchor = c('Anchor');
77
+ export const NavLink = c('NavLink');
78
+ export const Tabs = Object.assign(c('Tabs'), { List: c('Tabs.List'), Tab: c('Tabs.Tab'), Panel: c('Tabs.Panel') });
79
+ export const Breadcrumbs = c('Breadcrumbs');
80
+ export const Pagination = c('Pagination');
81
+ export const Stepper = Object.assign(c('Stepper'), { Step: c('Stepper.Step') });
82
+ export const Burger = c('Burger');
83
+ // ---------- 反馈与浮层 ----------
84
+ export const Alert = c('Alert');
85
+ export const Loader = c('Loader');
86
+ export const Progress = c('Progress');
87
+ export const RingProgress = c('RingProgress');
88
+ export const Skeleton = c('Skeleton');
89
+ export const Notification = c('Notification');
90
+ export const Modal = c('Modal');
91
+ export const Drawer = c('Drawer');
92
+ // ---------- AI 交互(Joplo 自有) ----------
93
+ export const ChatMessage = c('ChatMessage');
94
+ export const ToolCall = c('ToolCall');
95
+ export const PromptInput = c('PromptInput');
96
+ // ---------- Joplo ----------
97
+ export const Placeholder = c('Placeholder');
98
+ export const Custom = c('Custom');
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "@tokenroll/joplo-kit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Joplo 创作 SDK:组件、feature/option/flow 与 JSX 运行时(收集成数据,不作为 React 执行)",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./ui": {
13
+ "types": "./dist/ui.d.ts",
14
+ "default": "./dist/ui.js"
15
+ },
16
+ "./jsx-runtime": {
17
+ "types": "./dist/jsx-runtime.d.ts",
18
+ "default": "./dist/jsx-runtime.js"
19
+ },
20
+ "./jsx-dev-runtime": {
21
+ "types": "./dist/jsx-dev-runtime.d.ts",
22
+ "default": "./dist/jsx-dev-runtime.js"
23
+ },
24
+ "./node": {
25
+ "types": "./dist/node.d.ts",
26
+ "default": "./dist/node.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "engines": {
34
+ "node": ">=22"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/TokenRollAI/Joplo.git",
42
+ "directory": "packages/kit"
43
+ },
44
+ "homepage": "https://github.com/TokenRollAI/Joplo#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/TokenRollAI/Joplo/issues"
47
+ },
48
+ "scripts": {
49
+ "build": "node ../../scripts/build-package.mjs"
50
+ }
6
51
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.