@json-render/svelte 0.6.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 +201 -0
- package/dist/CatalogRenderer.svelte +55 -0
- package/dist/CatalogRenderer.svelte.d.ts +20 -0
- package/dist/CatalogRenderer.svelte.d.ts.map +1 -0
- package/dist/ConfirmDialog.svelte +98 -0
- package/dist/ConfirmDialog.svelte.d.ts +10 -0
- package/dist/ConfirmDialog.svelte.d.ts.map +1 -0
- package/dist/ConfirmDialogManager.svelte +15 -0
- package/dist/ConfirmDialogManager.svelte.d.ts +4 -0
- package/dist/ConfirmDialogManager.svelte.d.ts.map +1 -0
- package/dist/ElementRenderer.svelte +147 -0
- package/dist/ElementRenderer.svelte.d.ts +13 -0
- package/dist/ElementRenderer.svelte.d.ts.map +1 -0
- package/dist/JsonUIProvider.svelte +68 -0
- package/dist/JsonUIProvider.svelte.d.ts +37 -0
- package/dist/JsonUIProvider.svelte.d.ts.map +1 -0
- package/dist/Renderer.svelte +34 -0
- package/dist/Renderer.svelte.d.ts +19 -0
- package/dist/Renderer.svelte.d.ts.map +1 -0
- package/dist/RendererWithProvider.test.svelte +37 -0
- package/dist/RendererWithProvider.test.svelte.d.ts +14 -0
- package/dist/RendererWithProvider.test.svelte.d.ts.map +1 -0
- package/dist/RepeatChildren.svelte +50 -0
- package/dist/RepeatChildren.svelte.d.ts +13 -0
- package/dist/RepeatChildren.svelte.d.ts.map +1 -0
- package/dist/TestButton.svelte +11 -0
- package/dist/TestButton.svelte.d.ts +9 -0
- package/dist/TestButton.svelte.d.ts.map +1 -0
- package/dist/TestContainer.svelte +19 -0
- package/dist/TestContainer.svelte.d.ts +11 -0
- package/dist/TestContainer.svelte.d.ts.map +1 -0
- package/dist/TestText.svelte +9 -0
- package/dist/TestText.svelte.d.ts +9 -0
- package/dist/TestText.svelte.d.ts.map +1 -0
- package/dist/catalog-types.d.ts +63 -0
- package/dist/catalog-types.d.ts.map +1 -0
- package/dist/catalog-types.js +1 -0
- package/dist/contexts/ActionProvider.svelte +344 -0
- package/dist/contexts/ActionProvider.svelte.d.ts +62 -0
- package/dist/contexts/ActionProvider.svelte.d.ts.map +1 -0
- package/dist/contexts/FunctionsContextProvider.svelte +44 -0
- package/dist/contexts/FunctionsContextProvider.svelte.d.ts +21 -0
- package/dist/contexts/FunctionsContextProvider.svelte.d.ts.map +1 -0
- package/dist/contexts/RepeatScopeProvider.svelte +49 -0
- package/dist/contexts/RepeatScopeProvider.svelte.d.ts +26 -0
- package/dist/contexts/RepeatScopeProvider.svelte.d.ts.map +1 -0
- package/dist/contexts/StateProvider.svelte +182 -0
- package/dist/contexts/StateProvider.svelte.d.ts +47 -0
- package/dist/contexts/StateProvider.svelte.d.ts.map +1 -0
- package/dist/contexts/ValidationProvider.svelte +198 -0
- package/dist/contexts/ValidationProvider.svelte.d.ts +57 -0
- package/dist/contexts/ValidationProvider.svelte.d.ts.map +1 -0
- package/dist/contexts/VisibilityProvider.svelte +82 -0
- package/dist/contexts/VisibilityProvider.svelte.d.ts +29 -0
- package/dist/contexts/VisibilityProvider.svelte.d.ts.map +1 -0
- package/dist/contexts/actions.test.js +156 -0
- package/dist/contexts/state.test.js +118 -0
- package/dist/contexts/visibility.test.js +71 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/renderer.d.ts +128 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +124 -0
- package/dist/renderer.test.js +168 -0
- package/dist/schema.d.ts +57 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +81 -0
- package/dist/streaming.svelte.d.ts +75 -0
- package/dist/streaming.svelte.d.ts.map +1 -0
- package/dist/streaming.svelte.js +404 -0
- package/dist/utils.svelte.d.ts +26 -0
- package/dist/utils.svelte.d.ts.map +1 -0
- package/dist/utils.svelte.js +92 -0
- package/dist/utils.test.js +307 -0
- package/package.json +70 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { mount, unmount } from "svelte";
|
|
3
|
+
import StateProvider, { getStateContext } from "./StateProvider.svelte";
|
|
4
|
+
import ActionProvider, { getActionContext } from "./ActionProvider.svelte";
|
|
5
|
+
import ValidationProvider, { getValidationContext, } from "./ValidationProvider.svelte";
|
|
6
|
+
function component(runTest, options = {}) {
|
|
7
|
+
return async () => {
|
|
8
|
+
let promise;
|
|
9
|
+
const c = mount(((_anchor) => {
|
|
10
|
+
StateProvider(_anchor, {
|
|
11
|
+
initialState: options.initialState ?? {},
|
|
12
|
+
children: ((_inner) => {
|
|
13
|
+
if (options.withValidation) {
|
|
14
|
+
ValidationProvider(_inner, {
|
|
15
|
+
children: ((__inner) => {
|
|
16
|
+
ActionProvider(__inner, {
|
|
17
|
+
handlers: options.handlers ?? {},
|
|
18
|
+
children: (() => {
|
|
19
|
+
promise = runTest();
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
ActionProvider(_inner, {
|
|
27
|
+
handlers: options.handlers ?? {},
|
|
28
|
+
children: (() => {
|
|
29
|
+
promise = runTest();
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
}), { target: document.body });
|
|
35
|
+
await promise; // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
36
|
+
unmount(c);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
describe("createActionContext", () => {
|
|
40
|
+
it("executes built-in setState action", component(async () => {
|
|
41
|
+
const stateCtx = getStateContext();
|
|
42
|
+
const actionCtx = getActionContext();
|
|
43
|
+
await actionCtx.execute({
|
|
44
|
+
action: "setState",
|
|
45
|
+
params: { statePath: "/count", value: 5 },
|
|
46
|
+
});
|
|
47
|
+
expect(stateCtx.state.count).toBe(5);
|
|
48
|
+
}, { initialState: { count: 0 } }));
|
|
49
|
+
it("executes built-in pushState action", component(async () => {
|
|
50
|
+
const stateCtx = getStateContext();
|
|
51
|
+
const actionCtx = getActionContext();
|
|
52
|
+
await actionCtx.execute({
|
|
53
|
+
action: "pushState",
|
|
54
|
+
params: { statePath: "/items", value: "c" },
|
|
55
|
+
});
|
|
56
|
+
expect(stateCtx.state.items).toEqual(["a", "b", "c"]);
|
|
57
|
+
}, { initialState: { items: ["a", "b"] } }));
|
|
58
|
+
it("pushState creates array if missing", component(async () => {
|
|
59
|
+
const stateCtx = getStateContext();
|
|
60
|
+
const actionCtx = getActionContext();
|
|
61
|
+
await actionCtx.execute({
|
|
62
|
+
action: "pushState",
|
|
63
|
+
params: { statePath: "/newList", value: "first" },
|
|
64
|
+
});
|
|
65
|
+
expect(stateCtx.get("/newList")).toEqual(["first"]);
|
|
66
|
+
}));
|
|
67
|
+
it("executes built-in removeState action", component(async () => {
|
|
68
|
+
const stateCtx = getStateContext();
|
|
69
|
+
const actionCtx = getActionContext();
|
|
70
|
+
await actionCtx.execute({
|
|
71
|
+
action: "removeState",
|
|
72
|
+
params: { statePath: "/items", index: 1 },
|
|
73
|
+
});
|
|
74
|
+
expect(stateCtx.state.items).toEqual(["a", "c"]);
|
|
75
|
+
}, { initialState: { items: ["a", "b", "c"] } }));
|
|
76
|
+
it("executes push navigation action", component(async () => {
|
|
77
|
+
const stateCtx = getStateContext();
|
|
78
|
+
const actionCtx = getActionContext();
|
|
79
|
+
await actionCtx.execute({
|
|
80
|
+
action: "push",
|
|
81
|
+
params: { screen: "settings" },
|
|
82
|
+
});
|
|
83
|
+
expect(stateCtx.get("/currentScreen")).toBe("settings");
|
|
84
|
+
expect(stateCtx.get("/navStack")).toEqual(["home"]);
|
|
85
|
+
}, { initialState: { currentScreen: "home" } }));
|
|
86
|
+
it("executes pop navigation action", component(async () => {
|
|
87
|
+
const stateCtx = getStateContext();
|
|
88
|
+
const actionCtx = getActionContext();
|
|
89
|
+
await actionCtx.execute({ action: "pop" });
|
|
90
|
+
expect(stateCtx.get("/currentScreen")).toBe("home");
|
|
91
|
+
expect(stateCtx.get("/navStack")).toEqual([]);
|
|
92
|
+
}, { initialState: { currentScreen: "settings", navStack: ["home"] } }));
|
|
93
|
+
it("executes custom handlers", (() => {
|
|
94
|
+
const customHandler = vi.fn().mockResolvedValue(undefined);
|
|
95
|
+
return component(async () => {
|
|
96
|
+
const actionCtx = getActionContext();
|
|
97
|
+
await actionCtx.execute({
|
|
98
|
+
action: "myAction",
|
|
99
|
+
params: { foo: "bar" },
|
|
100
|
+
});
|
|
101
|
+
expect(customHandler).toHaveBeenCalledWith({ foo: "bar" });
|
|
102
|
+
}, { handlers: { myAction: customHandler } });
|
|
103
|
+
})());
|
|
104
|
+
it("warns when no handler registered", component(async () => {
|
|
105
|
+
const actionCtx = getActionContext();
|
|
106
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
107
|
+
await actionCtx.execute({ action: "unknownAction" });
|
|
108
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("unknownAction"));
|
|
109
|
+
warnSpy.mockRestore();
|
|
110
|
+
}));
|
|
111
|
+
it("tracks loading state for actions", (() => {
|
|
112
|
+
let resolveHandler;
|
|
113
|
+
const slowHandler = vi.fn(() => new Promise((resolve) => {
|
|
114
|
+
resolveHandler = resolve;
|
|
115
|
+
}));
|
|
116
|
+
return component(async () => {
|
|
117
|
+
const actionCtx = getActionContext();
|
|
118
|
+
const executePromise = actionCtx.execute({ action: "slowAction" });
|
|
119
|
+
expect(actionCtx.loadingActions.has("slowAction")).toBe(true);
|
|
120
|
+
resolveHandler();
|
|
121
|
+
await executePromise;
|
|
122
|
+
expect(actionCtx.loadingActions.has("slowAction")).toBe(false);
|
|
123
|
+
}, {
|
|
124
|
+
handlers: {
|
|
125
|
+
slowAction: slowHandler,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
})());
|
|
129
|
+
it("allows registering handlers dynamically", component(async () => {
|
|
130
|
+
const actionCtx = getActionContext();
|
|
131
|
+
const dynamicHandler = vi.fn();
|
|
132
|
+
actionCtx.registerHandler("dynamicAction", dynamicHandler);
|
|
133
|
+
await actionCtx.execute({ action: "dynamicAction", params: { x: 1 } });
|
|
134
|
+
expect(dynamicHandler).toHaveBeenCalledWith({ x: 1 });
|
|
135
|
+
}));
|
|
136
|
+
it("executes validateForm and writes result to /formValidation", component(async () => {
|
|
137
|
+
const stateCtx = getStateContext();
|
|
138
|
+
const actionCtx = getActionContext();
|
|
139
|
+
const validationCtx = getValidationContext();
|
|
140
|
+
validationCtx.registerField("/form/email", {
|
|
141
|
+
checks: [{ type: "required", message: "Required" }],
|
|
142
|
+
});
|
|
143
|
+
await actionCtx.execute({ action: "validateForm" });
|
|
144
|
+
expect(stateCtx.get("/formValidation")).toEqual({
|
|
145
|
+
valid: false,
|
|
146
|
+
errors: { "/form/email": ["Required"] },
|
|
147
|
+
});
|
|
148
|
+
}, { withValidation: true }));
|
|
149
|
+
it("validateForm defaults to warning when validation context is missing", component(async () => {
|
|
150
|
+
const actionCtx = getActionContext();
|
|
151
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
152
|
+
await actionCtx.execute({ action: "validateForm" });
|
|
153
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("validateForm action was dispatched"));
|
|
154
|
+
warnSpy.mockRestore();
|
|
155
|
+
}));
|
|
156
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { mount, unmount } from "svelte";
|
|
3
|
+
import { createStateStore } from "@json-render/core";
|
|
4
|
+
import StateProvider, { getStateContext } from "./StateProvider.svelte";
|
|
5
|
+
function component(runTest, props = {}) {
|
|
6
|
+
return () => {
|
|
7
|
+
const c = mount(((_anchor) => {
|
|
8
|
+
StateProvider(_anchor, {
|
|
9
|
+
...props,
|
|
10
|
+
children: (() => {
|
|
11
|
+
runTest();
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
}), { target: document.body });
|
|
15
|
+
unmount(c);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
describe("StateProvider", () => {
|
|
19
|
+
it("provides initial state to consumers", component(() => {
|
|
20
|
+
const ctx = getStateContext();
|
|
21
|
+
expect(ctx.state).toEqual({ user: { name: "John" } });
|
|
22
|
+
}, { initialState: { user: { name: "John" } } }));
|
|
23
|
+
it("provides empty object when no initial state", component(() => {
|
|
24
|
+
const ctx = getStateContext();
|
|
25
|
+
expect(ctx.state).toEqual({});
|
|
26
|
+
}));
|
|
27
|
+
});
|
|
28
|
+
describe("StateContext.get", () => {
|
|
29
|
+
it("retrieves values by path", component(() => {
|
|
30
|
+
const ctx = getStateContext();
|
|
31
|
+
expect(ctx.get("/user/name")).toBe("John");
|
|
32
|
+
expect(ctx.get("/user/age")).toBe(30);
|
|
33
|
+
}, { initialState: { user: { name: "John", age: 30 } } }));
|
|
34
|
+
it("returns undefined for missing path", component(() => {
|
|
35
|
+
const ctx = getStateContext();
|
|
36
|
+
expect(ctx.get("/user/email")).toBeUndefined();
|
|
37
|
+
expect(ctx.get("/nonexistent")).toBeUndefined();
|
|
38
|
+
}, { initialState: { user: { name: "John" } } }));
|
|
39
|
+
});
|
|
40
|
+
describe("StateContext.set", () => {
|
|
41
|
+
it("updates values at path", component(() => {
|
|
42
|
+
const ctx = getStateContext();
|
|
43
|
+
ctx.set("/count", 5);
|
|
44
|
+
expect(ctx.state.count).toBe(5);
|
|
45
|
+
}, { initialState: { count: 0 } }));
|
|
46
|
+
it("creates nested paths", component(() => {
|
|
47
|
+
const ctx = getStateContext();
|
|
48
|
+
ctx.set("/user/name", "Jane");
|
|
49
|
+
expect(ctx.get("/user/name")).toBe("Jane");
|
|
50
|
+
}));
|
|
51
|
+
it("calls onStateChange callback with change entries", component(() => {
|
|
52
|
+
const ctx = getStateContext();
|
|
53
|
+
ctx.set("/value", 2);
|
|
54
|
+
}, {
|
|
55
|
+
initialState: { value: 1 },
|
|
56
|
+
onStateChange: vi.fn((changes) => {
|
|
57
|
+
expect(changes).toEqual([{ path: "/value", value: 2 }]);
|
|
58
|
+
}),
|
|
59
|
+
}));
|
|
60
|
+
});
|
|
61
|
+
describe("StateContext.update", () => {
|
|
62
|
+
it("handles multiple values at once", component(() => {
|
|
63
|
+
const ctx = getStateContext();
|
|
64
|
+
ctx.update({ "/a": 10, "/b": 20 });
|
|
65
|
+
expect(ctx.state.a).toBe(10);
|
|
66
|
+
expect(ctx.state.b).toBe(20);
|
|
67
|
+
}, { initialState: { a: 1, b: 2 } }));
|
|
68
|
+
it("calls onStateChange once with all changed updates", component(() => {
|
|
69
|
+
const ctx = getStateContext();
|
|
70
|
+
ctx.update({ "/x": 1, "/y": 2 });
|
|
71
|
+
}, {
|
|
72
|
+
initialState: { x: 0, y: 0 },
|
|
73
|
+
onStateChange: vi.fn((changes) => {
|
|
74
|
+
expect(changes).toEqual([
|
|
75
|
+
{ path: "/x", value: 1 },
|
|
76
|
+
{ path: "/y", value: 2 },
|
|
77
|
+
]);
|
|
78
|
+
}),
|
|
79
|
+
}));
|
|
80
|
+
});
|
|
81
|
+
describe("StateContext nested paths", () => {
|
|
82
|
+
it("handles deeply nested state paths", component(() => {
|
|
83
|
+
const ctx = getStateContext();
|
|
84
|
+
expect(ctx.get("/app/settings/theme")).toBe("light");
|
|
85
|
+
expect(ctx.get("/app/settings/notifications/enabled")).toBe(true);
|
|
86
|
+
ctx.set("/app/settings/theme", "dark");
|
|
87
|
+
expect(ctx.get("/app/settings/theme")).toBe("dark");
|
|
88
|
+
}, {
|
|
89
|
+
initialState: {
|
|
90
|
+
app: {
|
|
91
|
+
settings: {
|
|
92
|
+
theme: "light",
|
|
93
|
+
notifications: { enabled: true },
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
}));
|
|
98
|
+
it("handles array indices in paths", component(() => {
|
|
99
|
+
const ctx = getStateContext();
|
|
100
|
+
expect(ctx.get("/items/0")).toBe("a");
|
|
101
|
+
expect(ctx.get("/items/1")).toBe("b");
|
|
102
|
+
ctx.set("/items/1", "B");
|
|
103
|
+
expect(ctx.get("/items/1")).toBe("B");
|
|
104
|
+
}, { initialState: { items: ["a", "b", "c"] } }));
|
|
105
|
+
});
|
|
106
|
+
describe("controlled mode", () => {
|
|
107
|
+
it("reads and writes through external StateStore", (() => {
|
|
108
|
+
const store = createStateStore({ count: 1 });
|
|
109
|
+
const onStateChange = vi.fn();
|
|
110
|
+
return component(() => {
|
|
111
|
+
const ctx = getStateContext();
|
|
112
|
+
expect(ctx.get("/count")).toBe(1);
|
|
113
|
+
ctx.set("/count", 2);
|
|
114
|
+
expect(store.get("/count")).toBe(2);
|
|
115
|
+
expect(onStateChange).not.toHaveBeenCalled();
|
|
116
|
+
}, { store, onStateChange });
|
|
117
|
+
})());
|
|
118
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { mount, unmount } from "svelte";
|
|
3
|
+
import StateProvider, { getStateContext } from "./StateProvider.svelte";
|
|
4
|
+
import VisibilityProvider, { getVisibilityContext, } from "./VisibilityProvider.svelte";
|
|
5
|
+
function component(runTest, initialState = {}) {
|
|
6
|
+
return () => {
|
|
7
|
+
const c = mount(((_anchor) => {
|
|
8
|
+
StateProvider(_anchor, {
|
|
9
|
+
initialState,
|
|
10
|
+
children: ((_inner) => {
|
|
11
|
+
VisibilityProvider(_inner, {
|
|
12
|
+
children: (() => {
|
|
13
|
+
runTest();
|
|
14
|
+
}),
|
|
15
|
+
});
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
}), { target: document.body });
|
|
19
|
+
unmount(c);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
describe("VisibilityProvider", () => {
|
|
23
|
+
it("provides isVisible function", component(() => {
|
|
24
|
+
const visCtx = getVisibilityContext();
|
|
25
|
+
expect(typeof visCtx.isVisible).toBe("function");
|
|
26
|
+
}));
|
|
27
|
+
it("provides visibility context", component(() => {
|
|
28
|
+
const visCtx = getVisibilityContext();
|
|
29
|
+
expect(visCtx.ctx).toBeDefined();
|
|
30
|
+
expect(visCtx.ctx.stateModel).toEqual({ value: true });
|
|
31
|
+
}, { value: true }));
|
|
32
|
+
});
|
|
33
|
+
describe("isVisible", () => {
|
|
34
|
+
it("returns true for undefined condition", component(() => {
|
|
35
|
+
const visCtx = getVisibilityContext();
|
|
36
|
+
expect(visCtx.isVisible(undefined)).toBe(true);
|
|
37
|
+
}));
|
|
38
|
+
it("returns true for true condition", component(() => {
|
|
39
|
+
const visCtx = getVisibilityContext();
|
|
40
|
+
expect(visCtx.isVisible(true)).toBe(true);
|
|
41
|
+
}));
|
|
42
|
+
it("returns false for false condition", component(() => {
|
|
43
|
+
const visCtx = getVisibilityContext();
|
|
44
|
+
expect(visCtx.isVisible(false)).toBe(false);
|
|
45
|
+
}));
|
|
46
|
+
it("evaluates $state conditions against data", component(() => {
|
|
47
|
+
const stateCtx = getStateContext();
|
|
48
|
+
const visCtx = getVisibilityContext();
|
|
49
|
+
expect(visCtx.isVisible({ $state: "/isLoggedIn" })).toBe(true);
|
|
50
|
+
stateCtx.set("/isLoggedIn", false);
|
|
51
|
+
expect(visCtx.isVisible({ $state: "/isLoggedIn" })).toBe(false);
|
|
52
|
+
}, { isLoggedIn: true }));
|
|
53
|
+
it("evaluates equality conditions", component(() => {
|
|
54
|
+
const visCtx = getVisibilityContext();
|
|
55
|
+
expect(visCtx.isVisible({ $state: "/tab", eq: "home" })).toBe(true);
|
|
56
|
+
expect(visCtx.isVisible({ $state: "/tab", eq: "settings" })).toBe(false);
|
|
57
|
+
}, { tab: "home" }));
|
|
58
|
+
it("evaluates array conditions (implicit AND)", component(() => {
|
|
59
|
+
const visCtx = getVisibilityContext();
|
|
60
|
+
expect(visCtx.isVisible([{ $state: "/a" }, { $state: "/b" }])).toBe(true);
|
|
61
|
+
expect(visCtx.isVisible([{ $state: "/a" }, { $state: "/c" }])).toBe(false);
|
|
62
|
+
}, { a: true, b: true, c: false }));
|
|
63
|
+
it("evaluates $and conditions", component(() => {
|
|
64
|
+
const visCtx = getVisibilityContext();
|
|
65
|
+
expect(visCtx.isVisible({ $and: [{ $state: "/x" }, { $state: "/y" }] })).toBe(false);
|
|
66
|
+
}, { x: true, y: false }));
|
|
67
|
+
it("evaluates $or conditions", component(() => {
|
|
68
|
+
const visCtx = getVisibilityContext();
|
|
69
|
+
expect(visCtx.isVisible({ $or: [{ $state: "/x" }, { $state: "/y" }] })).toBe(true);
|
|
70
|
+
}, { x: true, y: false }));
|
|
71
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { default as StateProvider, getStateContext, getStateValue, getBoundProp, type StateContext, } from "./contexts/StateProvider.svelte";
|
|
2
|
+
export { default as VisibilityProvider, getVisibilityContext, isVisible, type VisibilityContext, } from "./contexts/VisibilityProvider.svelte";
|
|
3
|
+
export { default as ActionProvider, getActionContext, getAction, type ActionContext, type PendingConfirmation, } from "./contexts/ActionProvider.svelte";
|
|
4
|
+
export { default as ValidationProvider, getValidationContext, getOptionalValidationContext, getFieldValidation, type ValidationContext, type FieldValidationState, } from "./contexts/ValidationProvider.svelte";
|
|
5
|
+
export { default as RepeatScopeProvider, getRepeatScope, type RepeatScopeValue, } from "./contexts/RepeatScopeProvider.svelte";
|
|
6
|
+
export { default as FunctionsContextProvider, getFunctions, type FunctionsContext, } from "./contexts/FunctionsContextProvider.svelte";
|
|
7
|
+
export { schema, type SvelteSchema, type SvelteSpec } from "./schema.js";
|
|
8
|
+
export type { EventHandle, BaseComponentProps, SetState, StateModel, ComponentContext, ComponentFn, Components, ActionFn, Actions, } from "./catalog-types.js";
|
|
9
|
+
export { flatToTree, buildSpecFromParts, getTextFromParts, type DataPart, } from "./utils.svelte.js";
|
|
10
|
+
export { createUIStream, createChatUI, type UIStreamOptions, type UIStreamReturn, type UIStreamState, type ChatUIOptions, type ChatUIReturn, type ChatMessage, type TokenUsage, } from "./streaming.svelte.js";
|
|
11
|
+
export { defineRegistry, createRenderer, type DefineRegistryResult, type ComponentRenderer, type ComponentRegistry, } from "./renderer.js";
|
|
12
|
+
export { default as Renderer, type RendererProps } from "./Renderer.svelte";
|
|
13
|
+
export { default as CatalogRenderer, type CatalogRendererProps, } from "./CatalogRenderer.svelte";
|
|
14
|
+
export { default as JsonUIProvider, type JSONUIProviderProps, } from "./JsonUIProvider.svelte";
|
|
15
|
+
export { default as ConfirmDialog } from "./ConfirmDialog.svelte";
|
|
16
|
+
export { default as ConfirmDialogManager } from "./ConfirmDialogManager.svelte";
|
|
17
|
+
export type { Spec, UIElement, ActionBinding, ActionHandler, } from "@json-render/core";
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,OAAO,IAAI,aAAa,EACxB,eAAe,EACf,aAAa,EACb,YAAY,EACZ,KAAK,YAAY,GAClB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EACL,OAAO,IAAI,kBAAkB,EAC7B,oBAAoB,EACpB,SAAS,EACT,KAAK,iBAAiB,GACvB,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EACL,OAAO,IAAI,cAAc,EACzB,gBAAgB,EAChB,SAAS,EACT,KAAK,aAAa,EAClB,KAAK,mBAAmB,GACzB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,OAAO,IAAI,kBAAkB,EAC7B,oBAAoB,EACpB,4BAA4B,EAC5B,kBAAkB,EAClB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,GAC1B,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EACL,OAAO,IAAI,mBAAmB,EAC9B,cAAc,EACd,KAAK,gBAAgB,GACtB,MAAM,uCAAuC,CAAC;AAE/C,OAAO,EACL,OAAO,IAAI,wBAAwB,EACnC,YAAY,EACZ,KAAK,gBAAgB,GACtB,MAAM,4CAA4C,CAAC;AAMpD,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAMzE,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,QAAQ,EACR,UAAU,EACV,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,QAAQ,EACR,OAAO,GACR,MAAM,oBAAoB,CAAC;AAM5B,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,QAAQ,GACd,MAAM,mBAAmB,CAAC;AAM3B,OAAO,EACL,cAAc,EACd,YAAY,EACZ,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,UAAU,GAChB,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EACL,cAAc,EACd,cAAc,EACd,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACvB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EACL,OAAO,IAAI,eAAe,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,OAAO,IAAI,cAAc,EACzB,KAAK,mBAAmB,GACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAMhF,YAAY,EACV,IAAI,EACJ,SAAS,EACT,aAAa,EACb,aAAa,GACd,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Contexts
|
|
3
|
+
// =============================================================================
|
|
4
|
+
export { default as StateProvider, getStateContext, getStateValue, getBoundProp, } from "./contexts/StateProvider.svelte";
|
|
5
|
+
export { default as VisibilityProvider, getVisibilityContext, isVisible, } from "./contexts/VisibilityProvider.svelte";
|
|
6
|
+
export { default as ActionProvider, getActionContext, getAction, } from "./contexts/ActionProvider.svelte";
|
|
7
|
+
export { default as ValidationProvider, getValidationContext, getOptionalValidationContext, getFieldValidation, } from "./contexts/ValidationProvider.svelte";
|
|
8
|
+
export { default as RepeatScopeProvider, getRepeatScope, } from "./contexts/RepeatScopeProvider.svelte";
|
|
9
|
+
export { default as FunctionsContextProvider, getFunctions, } from "./contexts/FunctionsContextProvider.svelte";
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Schema
|
|
12
|
+
// =============================================================================
|
|
13
|
+
export { schema } from "./schema.js";
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Utilities
|
|
16
|
+
// =============================================================================
|
|
17
|
+
export { flatToTree, buildSpecFromParts, getTextFromParts, } from "./utils.svelte.js";
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Streaming
|
|
20
|
+
// =============================================================================
|
|
21
|
+
export { createUIStream, createChatUI, } from "./streaming.svelte.js";
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Registry
|
|
24
|
+
// =============================================================================
|
|
25
|
+
export { defineRegistry, createRenderer, } from "./renderer.js";
|
|
26
|
+
export { default as Renderer } from "./Renderer.svelte";
|
|
27
|
+
export { default as CatalogRenderer, } from "./CatalogRenderer.svelte";
|
|
28
|
+
export { default as JsonUIProvider, } from "./JsonUIProvider.svelte";
|
|
29
|
+
export { default as ConfirmDialog } from "./ConfirmDialog.svelte";
|
|
30
|
+
export { default as ConfirmDialogManager } from "./ConfirmDialogManager.svelte";
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { Catalog, ComputedFunction, SchemaDefinition, Spec, StateStore, UIElement } from "@json-render/core";
|
|
2
|
+
import type { Component, Snippet } from "svelte";
|
|
3
|
+
import type { BaseComponentProps, EventHandle, SetState, StateModel } from "./catalog-types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Props passed to component renderers
|
|
6
|
+
*/
|
|
7
|
+
export interface ComponentRenderProps<P = Record<string, unknown>> {
|
|
8
|
+
/** The element being rendered */
|
|
9
|
+
element: UIElement<string, P>;
|
|
10
|
+
/** Rendered children snippet */
|
|
11
|
+
children?: Snippet;
|
|
12
|
+
/** Emit a named event. The renderer resolves the event to action binding(s) from the element's `on` field. */
|
|
13
|
+
emit: (event: string) => void;
|
|
14
|
+
/** Get an event handle with metadata */
|
|
15
|
+
on: (event: string) => EventHandle;
|
|
16
|
+
/**
|
|
17
|
+
* Two-way binding paths resolved from `$bindState` / `$bindItem` expressions.
|
|
18
|
+
* Maps prop name → absolute state path for write-back.
|
|
19
|
+
*/
|
|
20
|
+
bindings?: Record<string, string>;
|
|
21
|
+
/** Whether the parent is loading */
|
|
22
|
+
loading?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Component renderer type - a Svelte component that receives ComponentRenderProps
|
|
26
|
+
*/
|
|
27
|
+
export type ComponentRenderer<P = Record<string, unknown>> = Component<ComponentRenderProps<P>>;
|
|
28
|
+
/**
|
|
29
|
+
* Registry of component renderers.
|
|
30
|
+
* Maps component type names to Svelte components.
|
|
31
|
+
*/
|
|
32
|
+
export type ComponentRegistry = Record<string, ComponentRenderer<any>>;
|
|
33
|
+
/**
|
|
34
|
+
* Action handler function for defineRegistry
|
|
35
|
+
*/
|
|
36
|
+
type DefineRegistryActionFn = (params: Record<string, unknown> | undefined, setState: SetState, state: StateModel) => Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Result returned by defineRegistry
|
|
39
|
+
*/
|
|
40
|
+
export interface DefineRegistryResult {
|
|
41
|
+
/** Component registry for Renderer */
|
|
42
|
+
registry: ComponentRegistry;
|
|
43
|
+
/**
|
|
44
|
+
* Create ActionProvider-compatible handlers.
|
|
45
|
+
*/
|
|
46
|
+
handlers: (getSetState: () => SetState | undefined, getState: () => StateModel) => Record<string, (params: Record<string, unknown>) => Promise<void>>;
|
|
47
|
+
/**
|
|
48
|
+
* Execute an action by name imperatively
|
|
49
|
+
*/
|
|
50
|
+
executeAction: (actionName: string, params: Record<string, unknown> | undefined, setState: SetState, state?: StateModel) => Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a registry from a catalog with Svelte components and/or actions.
|
|
54
|
+
*
|
|
55
|
+
* Components must accept `BaseComponentProps` as their props interface.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* import { defineRegistry } from "@json-render/svelte";
|
|
60
|
+
* import Card from "./components/Card.svelte";
|
|
61
|
+
* import Button from "./components/Button.svelte";
|
|
62
|
+
* import { myCatalog } from "./catalog";
|
|
63
|
+
*
|
|
64
|
+
* const { registry, handlers } = defineRegistry(myCatalog, {
|
|
65
|
+
* components: {
|
|
66
|
+
* Card,
|
|
67
|
+
* Button,
|
|
68
|
+
* },
|
|
69
|
+
* actions: {
|
|
70
|
+
* submit: async (params, setState) => {
|
|
71
|
+
* // handle action
|
|
72
|
+
* },
|
|
73
|
+
* },
|
|
74
|
+
* });
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export declare function defineRegistry<C extends Catalog, TComponents extends Record<string, Component<BaseComponentProps<any>>>>(_catalog: C, options: {
|
|
78
|
+
/** Svelte components that accept BaseComponentProps */
|
|
79
|
+
components?: TComponents;
|
|
80
|
+
/** Action handlers */
|
|
81
|
+
actions?: Record<string, DefineRegistryActionFn>;
|
|
82
|
+
}): DefineRegistryResult;
|
|
83
|
+
/**
|
|
84
|
+
* Props for renderers created with createRenderer
|
|
85
|
+
*/
|
|
86
|
+
export interface CreateRendererProps {
|
|
87
|
+
spec: Spec | null;
|
|
88
|
+
store?: StateStore;
|
|
89
|
+
state?: Record<string, unknown>;
|
|
90
|
+
onAction?: (actionName: string, params?: Record<string, unknown>) => void;
|
|
91
|
+
onStateChange?: (changes: Array<{
|
|
92
|
+
path: string;
|
|
93
|
+
value: unknown;
|
|
94
|
+
}>) => void;
|
|
95
|
+
/** Named functions for `$computed` expressions in props */
|
|
96
|
+
functions?: Record<string, ComputedFunction>;
|
|
97
|
+
loading?: boolean;
|
|
98
|
+
fallback?: Component;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Component map type — maps component names to Vue components
|
|
102
|
+
*/
|
|
103
|
+
export type ComponentMap<TComponents extends Record<string, {
|
|
104
|
+
props: unknown;
|
|
105
|
+
}>> = {
|
|
106
|
+
[K in keyof TComponents]: Component<any, any, string>;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Create a renderer from a catalog
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* const DashboardRenderer = createRenderer(dashboardCatalog, {
|
|
114
|
+
* Card,
|
|
115
|
+
* Metric,
|
|
116
|
+
* });
|
|
117
|
+
*
|
|
118
|
+
* // Usage in template
|
|
119
|
+
* <DashboardRenderer spec={aiGeneratedSpec} {state} />
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare function createRenderer<TDef extends SchemaDefinition, TCatalog extends {
|
|
123
|
+
components: Record<string, {
|
|
124
|
+
props: unknown;
|
|
125
|
+
}>;
|
|
126
|
+
}>(_catalog: Catalog<TDef, TCatalog>, components: ComponentMap<TCatalog["components"]>): Component<CreateRendererProps>;
|
|
127
|
+
export {};
|
|
128
|
+
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"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"}
|
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import CatalogRenderer from "./CatalogRenderer.svelte";
|
|
2
|
+
/**
|
|
3
|
+
* Create a registry from a catalog with Svelte components and/or actions.
|
|
4
|
+
*
|
|
5
|
+
* Components must accept `BaseComponentProps` as their props interface.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { defineRegistry } from "@json-render/svelte";
|
|
10
|
+
* import Card from "./components/Card.svelte";
|
|
11
|
+
* import Button from "./components/Button.svelte";
|
|
12
|
+
* import { myCatalog } from "./catalog";
|
|
13
|
+
*
|
|
14
|
+
* const { registry, handlers } = defineRegistry(myCatalog, {
|
|
15
|
+
* components: {
|
|
16
|
+
* Card,
|
|
17
|
+
* Button,
|
|
18
|
+
* },
|
|
19
|
+
* actions: {
|
|
20
|
+
* submit: async (params, setState) => {
|
|
21
|
+
* // handle action
|
|
22
|
+
* },
|
|
23
|
+
* },
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function defineRegistry(_catalog, options) {
|
|
28
|
+
const registry = {};
|
|
29
|
+
if (options.components) {
|
|
30
|
+
for (const [name, componentFn] of Object.entries(options.components)) {
|
|
31
|
+
registry[name] = (_, props) => componentFn(_, {
|
|
32
|
+
get props() {
|
|
33
|
+
return props.element.props;
|
|
34
|
+
},
|
|
35
|
+
get children() {
|
|
36
|
+
return props.children;
|
|
37
|
+
},
|
|
38
|
+
get emit() {
|
|
39
|
+
return props.emit;
|
|
40
|
+
},
|
|
41
|
+
get on() {
|
|
42
|
+
return props.on;
|
|
43
|
+
},
|
|
44
|
+
get bindings() {
|
|
45
|
+
return props.bindings;
|
|
46
|
+
},
|
|
47
|
+
get loading() {
|
|
48
|
+
return props.loading;
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Build action helpers
|
|
54
|
+
const actionMap = options.actions
|
|
55
|
+
? Object.entries(options.actions)
|
|
56
|
+
: [];
|
|
57
|
+
const handlers = (getSetState, getState) => {
|
|
58
|
+
const result = {};
|
|
59
|
+
for (const [name, actionFn] of actionMap) {
|
|
60
|
+
result[name] = async (params) => {
|
|
61
|
+
const setState = getSetState();
|
|
62
|
+
const state = getState();
|
|
63
|
+
if (setState) {
|
|
64
|
+
await actionFn(params, setState, state);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
const executeAction = async (actionName, params, setState, state = {}) => {
|
|
71
|
+
const entry = actionMap.find(([name]) => name === actionName);
|
|
72
|
+
if (entry) {
|
|
73
|
+
await entry[1](params, setState, state);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.warn(`Unknown action: ${actionName}`);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
return { registry, handlers, executeAction };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Create a renderer from a catalog
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const DashboardRenderer = createRenderer(dashboardCatalog, {
|
|
87
|
+
* Card,
|
|
88
|
+
* Metric,
|
|
89
|
+
* });
|
|
90
|
+
*
|
|
91
|
+
* // Usage in template
|
|
92
|
+
* <DashboardRenderer spec={aiGeneratedSpec} {state} />
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export function createRenderer(_catalog, components) {
|
|
96
|
+
const registry = components;
|
|
97
|
+
return (_, props) => CatalogRenderer(_, {
|
|
98
|
+
registry,
|
|
99
|
+
get spec() {
|
|
100
|
+
return props.spec;
|
|
101
|
+
},
|
|
102
|
+
get store() {
|
|
103
|
+
return props.store;
|
|
104
|
+
},
|
|
105
|
+
get state() {
|
|
106
|
+
return props.state;
|
|
107
|
+
},
|
|
108
|
+
get onAction() {
|
|
109
|
+
return props.onAction;
|
|
110
|
+
},
|
|
111
|
+
get onStateChange() {
|
|
112
|
+
return props.onStateChange;
|
|
113
|
+
},
|
|
114
|
+
get functions() {
|
|
115
|
+
return props.functions;
|
|
116
|
+
},
|
|
117
|
+
get loading() {
|
|
118
|
+
return props.loading;
|
|
119
|
+
},
|
|
120
|
+
get fallback() {
|
|
121
|
+
return props.fallback;
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|