@epoch32/visage 0.0.0-stage → 0.0.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.
Files changed (138) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +110 -2
  3. package/dist/bundle.dev.js +4875 -0
  4. package/dist/index.js +9633 -0
  5. package/dist/src/api/index.js +4002 -0
  6. package/dist/src/charts/index.js +2717 -0
  7. package/dist/src/devtools/index.js +4677 -0
  8. package/dist/src/dom/index.js +1550 -0
  9. package/dist/src/form/index.js +1415 -0
  10. package/dist/src/index.js +11826 -0
  11. package/dist/src/jsx-runtime/index.js +1630 -0
  12. package/dist/src/query/index.js +2422 -0
  13. package/dist/src/router/index.js +1856 -0
  14. package/dist/src/rpc/index.js +3484 -0
  15. package/dist/src/server/index.js +1173 -0
  16. package/dist/src/signals/index.js +1077 -0
  17. package/dist/src/style/index.js +2340 -0
  18. package/dist/src/template/index.js +1149 -0
  19. package/dist/src/ui/index.js +4155 -0
  20. package/dist/src/virtual/index.js +1955 -0
  21. package/dist/src/vite/index.js +984 -0
  22. package/dist/vite/index.js +707 -0
  23. package/package.json +140 -4
  24. package/src/api/client.ts +371 -0
  25. package/src/api/file-router.ts +183 -0
  26. package/src/api/handler.ts +326 -0
  27. package/src/api/index.ts +71 -0
  28. package/src/api/middleware.ts +54 -0
  29. package/src/api/pragma.ts +155 -0
  30. package/src/api/procedure.ts +172 -0
  31. package/src/api/types.ts +249 -0
  32. package/src/charts/Area.tsx +111 -0
  33. package/src/charts/Axis.tsx +126 -0
  34. package/src/charts/Bar.tsx +86 -0
  35. package/src/charts/CartesianGrid.tsx +72 -0
  36. package/src/charts/Chart.tsx +269 -0
  37. package/src/charts/Cursor.tsx +42 -0
  38. package/src/charts/Legend.tsx +56 -0
  39. package/src/charts/Line.tsx +106 -0
  40. package/src/charts/Pie.tsx +88 -0
  41. package/src/charts/Tooltip.tsx +127 -0
  42. package/src/charts/context.ts +30 -0
  43. package/src/charts/index.ts +14 -0
  44. package/src/charts/path.ts +155 -0
  45. package/src/charts/scale.ts +133 -0
  46. package/src/charts/types.ts +50 -0
  47. package/src/devtools/DomTab.tsx +134 -0
  48. package/src/devtools/GraphInspector.tsx +196 -0
  49. package/src/devtools/GraphTab.tsx +315 -0
  50. package/src/devtools/QueryTab.tsx +200 -0
  51. package/src/devtools/RpcTab.tsx +190 -0
  52. package/src/devtools/SignalsTab.tsx +129 -0
  53. package/src/devtools/component.tsx +243 -0
  54. package/src/devtools/highlighter.ts +134 -0
  55. package/src/devtools/hub.ts +175 -0
  56. package/src/devtools/index.ts +20 -0
  57. package/src/devtools/styles.ts +313 -0
  58. package/src/devtools/types.ts +45 -0
  59. package/src/dom/async.ts +232 -0
  60. package/src/dom/control-flow.ts +353 -0
  61. package/src/dom/index.ts +32 -0
  62. package/src/dom/lazy.ts +82 -0
  63. package/src/dom/render.ts +286 -0
  64. package/src/dom/resource.ts +120 -0
  65. package/src/form/form.ts +218 -0
  66. package/src/form/index.ts +17 -0
  67. package/src/form/schema.ts +299 -0
  68. package/src/index.ts +413 -0
  69. package/src/jsx-dev-runtime/index.ts +1 -0
  70. package/src/jsx-runtime/index.ts +112 -0
  71. package/src/jsx-runtime/jsx.d.ts +278 -0
  72. package/src/query/create-mutation.ts +64 -0
  73. package/src/query/create-query.ts +426 -0
  74. package/src/query/focus-manager.ts +68 -0
  75. package/src/query/index.ts +63 -0
  76. package/src/query/mutation-cache.ts +138 -0
  77. package/src/query/online-manager.ts +67 -0
  78. package/src/query/query-cache.ts +369 -0
  79. package/src/query/query-client.ts +248 -0
  80. package/src/query/types.ts +221 -0
  81. package/src/query/utils.ts +132 -0
  82. package/src/router/index.ts +19 -0
  83. package/src/router/router.tsx +352 -0
  84. package/src/rpc/client.ts +317 -0
  85. package/src/rpc/error.ts +70 -0
  86. package/src/rpc/index.ts +52 -0
  87. package/src/rpc/procedure.ts +88 -0
  88. package/src/rpc/router.ts +105 -0
  89. package/src/rpc/server.ts +511 -0
  90. package/src/rpc/types.ts +328 -0
  91. package/src/server/hydrate.ts +21 -0
  92. package/src/server/index.ts +10 -0
  93. package/src/server/render-to-stream.ts +31 -0
  94. package/src/server/render-to-string.ts +125 -0
  95. package/src/signals/computed.ts +217 -0
  96. package/src/signals/context.ts +89 -0
  97. package/src/signals/effect.ts +161 -0
  98. package/src/signals/index.ts +57 -0
  99. package/src/signals/scope.ts +132 -0
  100. package/src/signals/signal.ts +203 -0
  101. package/src/signals/store.ts +163 -0
  102. package/src/signals/system.ts +124 -0
  103. package/src/signals/types.ts +64 -0
  104. package/src/style/component.tsx +122 -0
  105. package/src/style/create-style.ts +229 -0
  106. package/src/style/cx.ts +75 -0
  107. package/src/style/hash.ts +19 -0
  108. package/src/style/index.ts +8 -0
  109. package/src/style/layout.tsx +406 -0
  110. package/src/style/manager.ts +87 -0
  111. package/src/style/parser.ts +257 -0
  112. package/src/style/scale.ts +237 -0
  113. package/src/template/index.ts +4 -0
  114. package/src/template/template.ts +126 -0
  115. package/src/ui/accordion.tsx +286 -0
  116. package/src/ui/command-dialog.tsx +49 -0
  117. package/src/ui/command.tsx +400 -0
  118. package/src/ui/data-table.tsx +322 -0
  119. package/src/ui/dialog.tsx +362 -0
  120. package/src/ui/dropdown-menu.tsx +381 -0
  121. package/src/ui/icon.tsx +69 -0
  122. package/src/ui/index.ts +138 -0
  123. package/src/ui/layout.tsx +13 -0
  124. package/src/ui/popover.tsx +249 -0
  125. package/src/ui/switch.tsx +114 -0
  126. package/src/ui/tabs.tsx +297 -0
  127. package/src/ui/toast.tsx +280 -0
  128. package/src/ui/tooltip.tsx +262 -0
  129. package/src/ui/utils.ts +110 -0
  130. package/src/virtual/index.ts +12 -0
  131. package/src/virtual/virtual-list.tsx +185 -0
  132. package/src/virtual/virtualizer.ts +274 -0
  133. package/src/vite/hmr.ts +38 -0
  134. package/src/vite/index.ts +3 -0
  135. package/src/vite/lucide.ts +124 -0
  136. package/src/vite/plugin.ts +102 -0
  137. package/src/vite/rpc-middleware.ts +82 -0
  138. package/src/vite/types.ts +65 -0
@@ -0,0 +1,172 @@
1
+ import type { InferSchemaType, SchemaLike, StandardSchemaV1 } from "../form/schema";
2
+ import type {
3
+ ApiProcedure,
4
+ MiddlewareDefinition,
5
+ MiddlewareFn,
6
+ MiddlewarePragma,
7
+ ProcedureOptions
8
+ } from "./types";
9
+
10
+ /**
11
+ * Define a type-safe Query procedure
12
+ */
13
+ export function query<
14
+ TSchema extends SchemaLike<any> | StandardSchemaV1<any>,
15
+ TOutput = any,
16
+ TCtx = any
17
+ >(options: {
18
+ input: TSchema;
19
+ output?: SchemaLike<TOutput> | StandardSchemaV1<TOutput>;
20
+ resolve: (args: {
21
+ input: InferSchemaType<TSchema>;
22
+ ctx: TCtx;
23
+ req: Request;
24
+ params: Record<string, string>;
25
+ }) => Promise<TOutput> | TOutput | AsyncGenerator<any, void, unknown>;
26
+ }): ApiProcedure<InferSchemaType<TSchema>, TOutput, TCtx>;
27
+ export function query<TInput = void, TOutput = any, TCtx = any>(
28
+ options: ProcedureOptions<TInput, TOutput, TCtx>
29
+ ): ApiProcedure<TInput, TOutput, TCtx>;
30
+ export function query(options: any): ApiProcedure<any, any, any> {
31
+ return {
32
+ type: "query",
33
+ options
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Define a type-safe Mutation procedure
39
+ */
40
+ export function mutation<
41
+ TSchema extends SchemaLike<any> | StandardSchemaV1<any>,
42
+ TOutput = any,
43
+ TCtx = any
44
+ >(options: {
45
+ input: TSchema;
46
+ output?: SchemaLike<TOutput> | StandardSchemaV1<TOutput>;
47
+ resolve: (args: {
48
+ input: InferSchemaType<TSchema>;
49
+ ctx: TCtx;
50
+ req: Request;
51
+ params: Record<string, string>;
52
+ }) => Promise<TOutput> | TOutput | AsyncGenerator<any, void, unknown>;
53
+ }): ApiProcedure<InferSchemaType<TSchema>, TOutput, TCtx>;
54
+ export function mutation<TInput = void, TOutput = any, TCtx = any>(
55
+ options: ProcedureOptions<TInput, TOutput, TCtx>
56
+ ): ApiProcedure<TInput, TOutput, TCtx>;
57
+ export function mutation(options: any): ApiProcedure<any, any, any> {
58
+ return {
59
+ type: "mutation",
60
+ options
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Define a real-time SSE / Stream Subscription procedure
66
+ */
67
+ export function subscription<TOutput = any, TCtx = any>(
68
+ options: Omit<ProcedureOptions<void, TOutput, TCtx>, "input">
69
+ ): ApiProcedure<void, TOutput, TCtx> {
70
+ return {
71
+ type: "subscription",
72
+ options: {
73
+ ...options,
74
+ resolve: options.resolve as any
75
+ }
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Helper to define a middleware function with optional pragma metadata
81
+ */
82
+ export function defineMiddleware<TCtxIn = any, TCtxOut = any>(
83
+ fn: MiddlewareFn<TCtxIn, TCtxOut>,
84
+ pragma: MiddlewarePragma = { cascade: true, match: ["*"] }
85
+ ): MiddlewareDefinition {
86
+ return {
87
+ fn,
88
+ pragma
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Composable Procedure Builder (Fluent Style)
94
+ */
95
+ export class ProcedureBuilder<TCtx = any, TInput = any> {
96
+ private _middlewares: MiddlewareFn[] = [];
97
+ private _inputSchema?: SchemaLike<TInput> | StandardSchemaV1<TInput>;
98
+
99
+ constructor(middlewares: MiddlewareFn[] = [], inputSchema?: SchemaLike<TInput>) {
100
+ this._middlewares = middlewares;
101
+ this._inputSchema = inputSchema;
102
+ }
103
+
104
+ use<TNextCtx = Record<string, unknown>>(
105
+ middleware: MiddlewareFn<TCtx, TNextCtx>
106
+ ): ProcedureBuilder<TCtx & TNextCtx, TInput> {
107
+ return new ProcedureBuilder<TCtx & TNextCtx, TInput>(
108
+ [...this._middlewares, middleware],
109
+ this._inputSchema as any
110
+ );
111
+ }
112
+
113
+ input<TNewInput>(
114
+ schema: SchemaLike<TNewInput> | StandardSchemaV1<TNewInput>
115
+ ): ProcedureBuilder<TCtx, TNewInput> {
116
+ return new ProcedureBuilder<TCtx, TNewInput>(this._middlewares, schema as any);
117
+ }
118
+
119
+ query<TOutput>(
120
+ resolve: (args: {
121
+ input: TInput;
122
+ ctx: TCtx;
123
+ req: Request;
124
+ params: Record<string, string>;
125
+ }) => Promise<TOutput> | TOutput
126
+ ): ApiProcedure<TInput, TOutput, TCtx> {
127
+ return {
128
+ type: "query",
129
+ options: {
130
+ input: this._inputSchema,
131
+ resolve
132
+ },
133
+ middlewares: this._middlewares
134
+ };
135
+ }
136
+
137
+ mutation<TOutput>(
138
+ resolve: (args: {
139
+ input: TInput;
140
+ ctx: TCtx;
141
+ req: Request;
142
+ params: Record<string, string>;
143
+ }) => Promise<TOutput> | TOutput
144
+ ): ApiProcedure<TInput, TOutput, TCtx> {
145
+ return {
146
+ type: "mutation",
147
+ options: {
148
+ input: this._inputSchema,
149
+ resolve
150
+ },
151
+ middlewares: this._middlewares
152
+ };
153
+ }
154
+
155
+ subscription<TOutput>(
156
+ resolve: (args: {
157
+ ctx: TCtx;
158
+ req: Request;
159
+ params: Record<string, string>;
160
+ }) => AsyncGenerator<TOutput, void, unknown>
161
+ ): ApiProcedure<void, TOutput, TCtx> {
162
+ return {
163
+ type: "subscription",
164
+ options: {
165
+ resolve: resolve as any
166
+ },
167
+ middlewares: this._middlewares
168
+ };
169
+ }
170
+ }
171
+
172
+ export const procedure = new ProcedureBuilder<Record<string, unknown>>();
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Visage API & File-Based RPC Routing System Types
3
+ */
4
+ import type { SchemaLike, StandardSchemaV1 } from "../form/schema";
5
+ import type { RPCErrorCode } from "../rpc/types";
6
+
7
+ export type { RPCErrorCode };
8
+
9
+ export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS" | "HEAD";
10
+
11
+ export interface MiddlewarePragma {
12
+ /**
13
+ * Whether this middleware cascades to child subdirectories.
14
+ * Default: true
15
+ */
16
+ cascade?: boolean;
17
+
18
+ /**
19
+ * Route name / path globs to include.
20
+ * Default: ["*"]
21
+ */
22
+ match?: string[];
23
+
24
+ /**
25
+ * Route name / path globs to exclude.
26
+ * Default: []
27
+ */
28
+ exclude?: string[];
29
+
30
+ /**
31
+ * Execution priority within the same directory (higher runs earlier).
32
+ * Default: 0
33
+ */
34
+ priority?: number;
35
+ }
36
+
37
+ export type NextFn<TCtxIn = any, TCtxOut = any> = (
38
+ enriched?: Partial<TCtxOut>
39
+ ) => Promise<Response | (TCtxIn & TCtxOut)>;
40
+
41
+ export interface MiddlewareContext<TCtx = any> {
42
+ req: Request;
43
+ ctx: TCtx;
44
+ url: URL;
45
+ params: Record<string, string>;
46
+ next: NextFn<TCtx, any>;
47
+ }
48
+
49
+ export type MiddlewareFn<TCtxIn = any, _TCtxOut = any> = (
50
+ args: MiddlewareContext<TCtxIn>
51
+ ) => Promise<Response | void | any>;
52
+
53
+ export interface MiddlewareDefinition {
54
+ fn: MiddlewareFn;
55
+ pragma: MiddlewarePragma;
56
+ filePath?: string;
57
+ directory?: string;
58
+ }
59
+
60
+ export type ProcedureType = "query" | "mutation" | "subscription";
61
+
62
+ export interface ProcedureOptions<TInput = any, TOutput = any, TCtx = any> {
63
+ input?: SchemaLike<TInput> | StandardSchemaV1<TInput>;
64
+ output?: SchemaLike<TOutput> | StandardSchemaV1<TOutput>;
65
+ resolve: (args: {
66
+ input: TInput;
67
+ ctx: TCtx;
68
+ req: Request;
69
+ params: Record<string, string>;
70
+ }) => Promise<TOutput> | TOutput | AsyncGenerator<any, void, unknown>;
71
+ }
72
+
73
+ export interface ApiProcedure<TInput = any, TOutput = any, TCtx = any> {
74
+ type: ProcedureType;
75
+ options: ProcedureOptions<TInput, TOutput, TCtx>;
76
+ middlewares?: MiddlewareFn[];
77
+ }
78
+
79
+ export interface RouteMatch {
80
+ pattern: RegExp;
81
+ paramNames: string[];
82
+ routePath: string;
83
+ filePath: string;
84
+ directory: string;
85
+ procedures: Record<string, ApiProcedure>;
86
+ middlewares: MiddlewareDefinition[];
87
+ handler?: (req: Request, ctx: any) => Promise<Response> | Response;
88
+ }
89
+
90
+ export interface FileRouteModule {
91
+ default?: ApiProcedure | ((req: Request, ctx?: any) => Promise<Response> | Response);
92
+ middleware?: MiddlewareFn;
93
+ get?: ApiProcedure;
94
+ post?: ApiProcedure;
95
+ put?: ApiProcedure;
96
+ del?: ApiProcedure;
97
+ patch?: ApiProcedure;
98
+ [key: string]: any;
99
+ }
100
+
101
+ export interface ApiRouterConfig {
102
+ prefix?: string;
103
+ cors?: {
104
+ origin?: string | string[] | ((origin: string) => boolean);
105
+ methods?: string[];
106
+ allowedHeaders?: string[];
107
+ credentials?: boolean;
108
+ maxAge?: number;
109
+ };
110
+ onError?: (err: Error, req: Request) => Response | Promise<Response>;
111
+ }
112
+
113
+ export type ApiClientHeadersRule =
114
+ | Record<string, string>
115
+ | (() => Promise<Record<string, string>> | Record<string, string>);
116
+
117
+ export interface ApiClientOptions {
118
+ baseUrl: string;
119
+ fetch?: typeof fetch;
120
+ headers?:
121
+ | Record<string, string>
122
+ | Record<string, ApiClientHeadersRule>
123
+ | (() => Promise<Record<string, string>> | Record<string, string>);
124
+ }
125
+
126
+ export interface ApiCallOptions {
127
+ signal?: AbortSignal;
128
+ headers?: Record<string, string>;
129
+ }
130
+
131
+ export interface ApiSubscriptionCallbacks<TData = any> {
132
+ onData: (data: TData) => void;
133
+ onError?: (error: any) => void;
134
+ onComplete?: () => void;
135
+ }
136
+
137
+ export interface ApiSubscriptionHandle {
138
+ unsubscribe: () => void;
139
+ }
140
+
141
+ export type InferApiInput<P> =
142
+ P extends ApiProcedure<infer TInput, any, any>
143
+ ? TInput
144
+ : P extends (...args: any[]) => any
145
+ ? any
146
+ : void;
147
+
148
+ export type InferApiOutput<P> =
149
+ P extends ApiProcedure<any, infer TOutput, any>
150
+ ? TOutput
151
+ : P extends (...args: any[]) => Promise<infer TOutput>
152
+ ? TOutput
153
+ : unknown;
154
+
155
+ export type ApiMethodCaller<P> =
156
+ InferApiInput<P> extends void | undefined
157
+ ? (input?: void, options?: ApiCallOptions) => Promise<InferApiOutput<P>>
158
+ : (input: InferApiInput<P>, options?: ApiCallOptions) => Promise<InferApiOutput<P>>;
159
+
160
+ export type ApiSubscriptionCaller<P> =
161
+ InferApiInput<P> extends void | undefined
162
+ ? (
163
+ input: void | undefined,
164
+ callbacks: ApiSubscriptionCallbacks<InferApiOutput<P>>
165
+ ) => ApiSubscriptionHandle
166
+ : (
167
+ input: InferApiInput<P>,
168
+ callbacks: ApiSubscriptionCallbacks<InferApiOutput<P>>
169
+ ) => ApiSubscriptionHandle;
170
+
171
+ export type ApiEndpointMethods<T> = {
172
+ get: T extends { GET: infer P }
173
+ ? ApiMethodCaller<P>
174
+ : T extends { get: infer P }
175
+ ? ApiMethodCaller<P>
176
+ : T extends { default: infer P }
177
+ ? ApiMethodCaller<P>
178
+ : (input?: any, options?: ApiCallOptions) => Promise<any>;
179
+ post: T extends { POST: infer P }
180
+ ? ApiMethodCaller<P>
181
+ : T extends { post: infer P }
182
+ ? ApiMethodCaller<P>
183
+ : (input?: any, options?: ApiCallOptions) => Promise<any>;
184
+ put: T extends { PUT: infer P }
185
+ ? ApiMethodCaller<P>
186
+ : T extends { put: infer P }
187
+ ? ApiMethodCaller<P>
188
+ : (input?: any, options?: ApiCallOptions) => Promise<any>;
189
+ patch: T extends { PATCH: infer P }
190
+ ? ApiMethodCaller<P>
191
+ : T extends { patch: infer P }
192
+ ? ApiMethodCaller<P>
193
+ : (input?: any, options?: ApiCallOptions) => Promise<any>;
194
+ del: T extends { DELETE: infer P }
195
+ ? ApiMethodCaller<P>
196
+ : T extends { del: infer P }
197
+ ? ApiMethodCaller<P>
198
+ : (input?: any, options?: ApiCallOptions) => Promise<any>;
199
+ delete: T extends { DELETE: infer P }
200
+ ? ApiMethodCaller<P>
201
+ : T extends { del: infer P }
202
+ ? ApiMethodCaller<P>
203
+ : (input?: any, options?: ApiCallOptions) => Promise<any>;
204
+ subscribe: T extends { GET: infer P }
205
+ ? ApiSubscriptionCaller<P>
206
+ : T extends { subscription: infer P }
207
+ ? ApiSubscriptionCaller<P>
208
+ : (input: any, callbacks: ApiSubscriptionCallbacks) => ApiSubscriptionHandle;
209
+
210
+ queryKey: (input?: any) => unknown[];
211
+ queryOptions: (input?: any, extra?: any) => any;
212
+ createQuery: (input?: any, extra?: any) => any;
213
+ useQuery: (input?: any, extra?: any) => any;
214
+
215
+ mutationKey: () => unknown[];
216
+ mutationOptions: (extra?: any) => any;
217
+ createMutation: (extra?: any) => any;
218
+ useMutation: (extra?: any) => any;
219
+ };
220
+
221
+ export type DynamicApiClient = {
222
+ get: (input?: any, options?: ApiCallOptions) => Promise<any>;
223
+ post: (input?: any, options?: ApiCallOptions) => Promise<any>;
224
+ put: (input?: any, options?: ApiCallOptions) => Promise<any>;
225
+ patch: (input?: any, options?: ApiCallOptions) => Promise<any>;
226
+ del: (input?: any, options?: ApiCallOptions) => Promise<any>;
227
+ delete: (input?: any, options?: ApiCallOptions) => Promise<any>;
228
+ subscribe: (input: any, callbacks: ApiSubscriptionCallbacks) => ApiSubscriptionHandle;
229
+ queryKey: (input?: any) => unknown[];
230
+ queryOptions: (input?: any, extra?: any) => any;
231
+ createQuery: (input?: any, extra?: any) => any;
232
+ useQuery: (input?: any, extra?: any) => any;
233
+ mutationKey: () => unknown[];
234
+ mutationOptions: (extra?: any) => any;
235
+ createMutation: (extra?: any) => any;
236
+ useMutation: (extra?: any) => any;
237
+ (param: string | number): DynamicApiClient;
238
+ } & Record<string, any>;
239
+
240
+ export type ApiClient<TRoutes = any> = 0 extends 1 & TRoutes
241
+ ? DynamicApiClient
242
+ : [TRoutes] extends [Record<string, any>]
243
+ ? {
244
+ [K in keyof TRoutes]-?: ((param: string | number) => ApiClient<TRoutes[K]>) &
245
+ ApiEndpointMethods<TRoutes[K]> &
246
+ ApiClient<TRoutes[K]>;
247
+ } & ((param: string | number) => any) &
248
+ ApiEndpointMethods<any>
249
+ : DynamicApiClient;
@@ -0,0 +1,111 @@
1
+ import type { Child } from "../dom";
2
+ import { onCleanup } from "../signals";
3
+ import { useChartContext } from "./context";
4
+ import { pathArea, pathLine } from "./path";
5
+ import type { CurveType, Point } from "./types";
6
+
7
+ export interface AreaProps {
8
+ dataKey: string;
9
+ stroke?: string;
10
+ strokeWidth?: number | string;
11
+ fill?: string;
12
+ fillOpacity?: number | string;
13
+ curve?: CurveType;
14
+ dot?: boolean | { r?: number; fill?: string; stroke?: string };
15
+ name?: string;
16
+ opacity?: number | string;
17
+ }
18
+
19
+ export function Area(props: AreaProps): Child {
20
+ return () => {
21
+ const ctx = useChartContext();
22
+
23
+ const stroke = props.stroke ?? "#a855f7";
24
+ const strokeWidth = props.strokeWidth ?? 2;
25
+ const fill = props.fill ?? "rgba(168, 85, 247, 0.15)";
26
+ const fillOpacity = props.fillOpacity ?? 1;
27
+ const curve = props.curve ?? "smooth";
28
+
29
+ const id = `area-${props.dataKey}-${Math.random().toString(36).slice(2, 7)}`;
30
+
31
+ ctx.registerSeries({
32
+ id,
33
+ dataKey: props.dataKey,
34
+ type: "area",
35
+ color: stroke,
36
+ name: props.name ?? props.dataKey
37
+ });
38
+
39
+ onCleanup(() => {
40
+ ctx.unregisterSeries(id);
41
+ });
42
+
43
+ return (
44
+ <g class={`visage-area-${props.dataKey}`} opacity={props.opacity ?? 1}>
45
+ {() => {
46
+ const dataArr = ctx.data();
47
+ if (dataArr.length === 0) return null;
48
+
49
+ const xScale = ctx.xScale();
50
+ const yScale = ctx.yScale();
51
+ const baselineY = ctx.innerHeight();
52
+
53
+ const points: Point[] = [];
54
+ for (let i = 0; i < dataArr.length; i++) {
55
+ const item = dataArr[i];
56
+ const val = item[props.dataKey];
57
+ if (typeof val !== "number" || Number.isNaN(val)) continue;
58
+
59
+ let x = 0;
60
+ if (xScale.type === "band") {
61
+ x = xScale(i) + xScale.bandwidth() / 2;
62
+ } else {
63
+ x = xScale(i);
64
+ }
65
+
66
+ const y = yScale(val);
67
+ points.push({ x, y });
68
+ }
69
+
70
+ const areaD = pathArea(points, baselineY, { curve });
71
+ const lineD = pathLine(points, { curve });
72
+
73
+ const dots: Child[] = [];
74
+ if (props.dot && points.length > 0) {
75
+ const dotCfg = typeof props.dot === "object" ? props.dot : {};
76
+ const r = dotCfg.r ?? 3.5;
77
+ const dotFill = dotCfg.fill ?? "#ffffff";
78
+ const dotStroke = dotCfg.stroke ?? stroke;
79
+
80
+ for (let i = 0; i < points.length; i++) {
81
+ const p = points[i]!;
82
+ dots.push(
83
+ <circle
84
+ cx={p.x}
85
+ cy={p.y}
86
+ r={r}
87
+ fill={dotFill}
88
+ stroke={dotStroke}
89
+ stroke-width={2}
90
+ />
91
+ );
92
+ }
93
+ }
94
+
95
+ return [
96
+ <path d={areaD} fill={fill} fill-opacity={fillOpacity} />,
97
+ <path
98
+ d={lineD}
99
+ fill="none"
100
+ stroke={stroke}
101
+ stroke-width={strokeWidth}
102
+ stroke-linejoin="round"
103
+ stroke-linecap="round"
104
+ />,
105
+ ...dots
106
+ ];
107
+ }}
108
+ </g>
109
+ );
110
+ };
111
+ }
@@ -0,0 +1,126 @@
1
+ import type { Child } from "../dom";
2
+ import { onCleanup } from "../signals";
3
+ import { useChartContext } from "./context";
4
+
5
+ export interface XAxisProps {
6
+ dataKey?: string;
7
+ tickFormat?: (val: any, index: number) => string;
8
+ tickCount?: number;
9
+ stroke?: string;
10
+ fontSize?: number | string;
11
+ fontFamily?: string;
12
+ hideLine?: boolean;
13
+ }
14
+
15
+ export function XAxis(props: XAxisProps): Child {
16
+ return () => {
17
+ const ctx = useChartContext();
18
+
19
+ if (props.dataKey) {
20
+ ctx.setXDataKey(props.dataKey);
21
+ onCleanup(() => {
22
+ if (ctx.xDataKey() === props.dataKey) {
23
+ ctx.setXDataKey("");
24
+ }
25
+ });
26
+ }
27
+
28
+ const stroke = props.stroke ?? "#94a3b8";
29
+ const fontSize = props.fontSize ?? 11;
30
+ const hideLine = props.hideLine ?? false;
31
+
32
+ const w = ctx.innerWidth();
33
+ const h = ctx.innerHeight();
34
+ const xScale = ctx.xScale();
35
+ const dataArr = ctx.data();
36
+
37
+ const elements: Child[] = [];
38
+
39
+ if (!hideLine) {
40
+ elements.push(<line x1={0} y1={h} x2={w} y2={h} stroke={stroke} stroke-width={1} />);
41
+ }
42
+
43
+ if (xScale.type === "band") {
44
+ const ticks = xScale.ticks();
45
+ const bw = xScale.bandwidth();
46
+ for (let i = 0; i < ticks.length; i++) {
47
+ const val = ticks[i];
48
+ const x = xScale(i) + bw / 2;
49
+ const text = props.tickFormat ? props.tickFormat(val, i) : String(val);
50
+ elements.push(
51
+ <text x={x} y={h + 16} text-anchor="middle" font-size={fontSize} fill={stroke}>
52
+ {text}
53
+ </text>
54
+ );
55
+ }
56
+ } else {
57
+ const count = props.tickCount ?? 5;
58
+ const ticks = xScale.ticks(count);
59
+ for (let i = 0; i < ticks.length; i++) {
60
+ const tickIdx = ticks[i]!;
61
+ const x = xScale(tickIdx);
62
+ const idx = Math.max(0, Math.min(dataArr.length - 1, Math.round(tickIdx)));
63
+ const rawItem = dataArr[idx];
64
+ const rawVal = props.dataKey && rawItem ? rawItem[props.dataKey] : idx;
65
+ const text = props.tickFormat ? props.tickFormat(rawVal, i) : String(rawVal ?? idx);
66
+
67
+ elements.push(
68
+ <text x={x} y={h + 16} text-anchor="middle" font-size={fontSize} fill={stroke}>
69
+ {text}
70
+ </text>
71
+ );
72
+ }
73
+ }
74
+
75
+ return <g class="visage-x-axis">{elements}</g>;
76
+ };
77
+ }
78
+
79
+ export interface YAxisProps {
80
+ dataKey?: string;
81
+ tickFormat?: (val: any, index: number) => string;
82
+ unit?: string;
83
+ tickCount?: number;
84
+ stroke?: string;
85
+ fontSize?: number | string;
86
+ hideLine?: boolean;
87
+ }
88
+
89
+ export function YAxis(props: YAxisProps): Child {
90
+ return () => {
91
+ const ctx = useChartContext();
92
+
93
+ const stroke = props.stroke ?? "#94a3b8";
94
+ const fontSize = props.fontSize ?? 11;
95
+ const hideLine = props.hideLine ?? true;
96
+ const unit = props.unit ?? "";
97
+
98
+ const h = ctx.innerHeight();
99
+ const yScale = ctx.yScale();
100
+ const ticks = yScale.ticks(props.tickCount ?? 5);
101
+
102
+ const elements: Child[] = [];
103
+
104
+ if (!hideLine) {
105
+ elements.push(<line x1={0} y1={0} x2={0} y2={h} stroke={stroke} stroke-width={1} />);
106
+ }
107
+
108
+ for (let i = 0; i < ticks.length; i++) {
109
+ const val = ticks[i]!;
110
+ const y = yScale(val);
111
+ const formatted = props.tickFormat
112
+ ? props.tickFormat(val, i)
113
+ : unit
114
+ ? `${val}${unit}`
115
+ : String(val);
116
+
117
+ elements.push(
118
+ <text x={-8} y={y + 4} text-anchor="end" font-size={fontSize} fill={stroke}>
119
+ {formatted}
120
+ </text>
121
+ );
122
+ }
123
+
124
+ return <g class="visage-y-axis">{elements}</g>;
125
+ };
126
+ }