revojs 0.0.3 → 0.0.4

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.
@@ -1,23 +1,9 @@
1
- import type { Attributes, ComponentConstructor, Events, Template } from "../html";
1
+ import { type Template } from "../html";
2
2
  import type { Middleware } from "../http";
3
- import type { Route } from "../runtime";
4
3
  export type NestedPartial<T> = T extends any[] ? T : T extends Record<string, any> ? {
5
4
  [P in keyof T]?: NestedPartial<T[P]>;
6
5
  } : T;
7
- export type Content<T> = string & {
8
- key: string;
9
- };
10
- export type InferContent<T> = T extends Content<infer U> ? U : unknown;
11
- export type StaticContent = {
12
- type: "static";
13
- source: string;
14
- include: Array<string>;
15
- };
16
- export type AssetContent = {
17
- type: "asset";
18
- source: string;
19
- include: Array<string>;
20
- };
6
+ export type Environment = "CLIENT" | "SERVER";
21
7
  export type ServerEntry = "revojs/presets/node" | "revojs/presets/deno" | "revojs/presets/bun" | "revojs/presets/cloudflare" | (string & {});
22
8
  export type ClientConfig = {
23
9
  entry: string;
@@ -31,17 +17,13 @@ export type DevelopmentConfig<T> = {
31
17
  export type Config<T> = {
32
18
  client: ClientConfig;
33
19
  server: false | ServerConfig;
34
- content: Record<string, StaticContent | AssetContent>;
35
20
  markdown: Record<string, Template>;
36
21
  dev: DevelopmentConfig<T>;
37
22
  };
38
23
  export type App<T> = {
39
24
  config: Config<T>;
40
- virtuals: Record<string, () => string>;
25
+ virtuals: Record<string, (environment: Environment) => string>;
41
26
  };
42
- export declare const defineContent: <T>(key: string) => Content<T>;
43
- export declare const getContent: <T>(key: string | Content<T>) => Promise<Record<string, () => Promise<T>>>;
27
+ export declare const getRoutes: <T>() => Promise<Record<string, () => Promise<T>>>;
28
+ export declare const getAssets: <T>() => Promise<Record<string, () => Promise<T>>>;
44
29
  export declare const createApp: <T>(config?: NestedPartial<Config<T>>) => App<T>;
45
- export declare const STATIC_CONTENT: Content<string>;
46
- export declare const PAGES_CONTENT: Content<ComponentConstructor<Events, Attributes>>;
47
- export declare const ROUTES_CONTENT: Content<Route<unknown>>;
@@ -1,8 +1,9 @@
1
- import { type Descriptor } from "../html";
1
+ import { Scope } from "../signals";
2
+ import { type Descriptor } from "../types";
2
3
  export declare class Hooks {
3
- hooks: Map<string, Set<(value: any) => unknown>>;
4
+ readonly hooks: Map<string, Set<(value: any) => unknown>>;
4
5
  constructor();
5
- on: <T>(input: string | Descriptor<T>, invoke: (value: T) => unknown) => void;
6
+ on: <T>(scope: Scope, input: string | Descriptor<T>, invoke: (value: T) => unknown) => number;
6
7
  dispatch: <T>(input: string | Descriptor<T>, value: T) => void;
7
8
  }
8
9
  export declare const defineHook: <T>(description?: string | number) => Descriptor<T>;
@@ -1,24 +1,22 @@
1
+ import { Hooks } from "../hooks";
1
2
  import { type HtmlAttributes } from "../jsx";
2
- import { type State, type Value } from "../signals";
3
- export type Descriptor<T> = {
4
- key: symbol;
5
- };
3
+ import { Scope, type State, type Value } from "../signals";
4
+ import { type Descriptor } from "../types";
6
5
  export type TypeOf<T> = {
7
6
  (): T;
8
7
  };
9
8
  export type Infer<T> = T extends TypeOf<infer U> ? U : unknown;
10
- export type Primitiv = string | number | bigint | boolean | symbol | undefined | object | Function;
11
- export type Slot = void | Primitiv | Template | Array<Slot> | (() => Slot);
9
+ export type Slot = unknown | Template | Array<Slot> | (() => Slot);
12
10
  export type Template = {
13
11
  tag: string;
14
- attributes: Record<string, Primitiv>;
12
+ attributes: Record<string, unknown>;
15
13
  children: Array<Slot>;
16
14
  };
17
15
  export type EventInput<T extends Events> = {
18
16
  [K in keyof T]?: (event: Infer<T[K]["type"]> extends Event ? Infer<T[K]["type"]> : CustomEvent<Infer<T[K]["type"]>>) => void;
19
17
  };
20
18
  export type AttributeInput<T extends Attributes> = {
21
- [K in keyof T]?: Value<Infer<T[K]["type"]>>;
19
+ [K in keyof T]?: Value<Infer<T[K]["type"]> | undefined>;
22
20
  };
23
21
  export type Input<TEvents extends Events, TAttributes extends Attributes> = EventInput<TEvents> & AttributeInput<TAttributes> & HtmlAttributes;
24
22
  export type EventOutput<T extends Events> = {
@@ -37,8 +35,8 @@ export type AttributeOptions<T> = {
37
35
  type: T;
38
36
  default?: Infer<T>;
39
37
  };
40
- export type Events = Record<string, EventOptions<Primitiv>>;
41
- export type Attributes = Record<string, AttributeOptions<Primitiv>>;
38
+ export type Events = Record<string, EventOptions<unknown>>;
39
+ export type Attributes = Record<string, AttributeOptions<unknown>>;
42
40
  export interface ComponentOptions<TEvents extends Events, TAttributes extends Attributes> {
43
41
  name: string;
44
42
  events?: TEvents;
@@ -47,19 +45,23 @@ export interface ComponentOptions<TEvents extends Events, TAttributes extends At
47
45
  setup: (component: Component<TEvents, TAttributes>) => Slot | Promise<Slot>;
48
46
  }
49
47
  export interface Component<TEvents extends Events, TAttributes extends Attributes> {
48
+ readonly scope: Scope;
49
+ readonly hooks: Hooks;
50
50
  readonly events: EventOutput<TEvents>;
51
51
  readonly attributes: State<AttributeOutput<TAttributes>>;
52
52
  readonly shadowRoot: false | ShadowRootInit;
53
53
  readonly context: Record<string, unknown>;
54
+ readonly host?: CustomElement<TEvents, TAttributes>;
54
55
  getContext: <T>(input: string | Descriptor<T>) => T;
55
56
  setContext: <T>(input: string | Descriptor<T>, value: T) => void;
57
+ onMounted: (invoke: (input: HTMLElement) => void | Promise<void>) => void;
56
58
  setup: () => Slot | Promise<Slot>;
57
59
  }
58
60
  export interface ComponentConstructor<TEvents extends Events, TAttributes extends Attributes> {
59
61
  $name: string;
60
62
  $events: TEvents;
61
63
  $attributes: TAttributes;
62
- new (input?: Input<TEvents, TAttributes>, context?: Record<string, unknown>): Component<TEvents, TAttributes>;
64
+ new (input?: Input<TEvents, TAttributes>, context?: Record<string, unknown>, host?: CustomElement<TEvents, TAttributes>): Component<TEvents, TAttributes>;
63
65
  }
64
66
  export interface CustomElement<TEvents extends Events, TAttributes extends Attributes> extends HTMLElement {
65
67
  readonly component: Component<TEvents, TAttributes>;
@@ -68,23 +70,21 @@ export interface CustomElementConstructor<TEvents extends Events, TAttributes ex
68
70
  new (): CustomElement<TEvents, TAttributes>;
69
71
  }
70
72
  export declare const isTemplate: (value: object) => value is Template;
71
- export declare const descriptor: <T>(descriptor: string | Descriptor<T>) => string;
72
73
  export declare const defineContext: <T>(description?: string | number) => Descriptor<T>;
73
74
  export declare const createElement: <TEvents extends Events, TAttributes extends Attributes>(input: string | ComponentConstructor<TEvents, TAttributes>, attributes?: AttributeInput<TAttributes>, ...children: Array<Slot>) => Slot;
74
75
  export declare const toString: (slot: Slot) => string;
75
76
  export declare const toFragment: (nodes: Array<Node>) => DocumentFragment;
76
77
  export declare const renderToString: (slot: Slot, context: Record<string, unknown>) => Promise<string>;
77
- export declare const renderToNode: (slot: Slot) => Promise<Node>;
78
+ export declare const renderToNode: (scope: Scope, slot: Slot) => Promise<Node>;
78
79
  export declare const defineComponent: <TEvents extends Events = {}, TAttributes extends Attributes = {}>(options: ComponentOptions<TEvents, TAttributes>) => ComponentConstructor<TEvents, TAttributes>;
79
80
  export declare const toCustomElement: <TEvents extends Events = {}, TAttributes extends Attributes = {}>(component: ComponentConstructor<TEvents, TAttributes>) => CustomElementConstructor<TEvents, TAttributes>;
80
81
  export declare const registerComponent: <TEvents extends Events = {}, TAttributes extends Attributes = {}>(component: ComponentConstructor<TEvents, TAttributes>) => ComponentConstructor<TEvents, TAttributes>;
81
- export declare const isClient: () => boolean;
82
- export declare const isServer: () => boolean;
83
- export declare const addStyles: (...styles: Array<CSSStyleSheet>) => void;
84
82
  export declare const getGlobalStyles: () => CSSStyleSheet[];
85
83
  export declare const getCustomElement: (node: Node | null) => CustomElement<Events, Attributes> | undefined;
84
+ export declare const isClient: () => boolean;
85
+ export declare const isServer: () => boolean;
86
86
  export declare const preventDefault: (event: Event) => void;
87
87
  export declare const stopPropagation: (event: Event) => void;
88
88
  export declare const stopImmediatePropagation: (event: Event) => void;
89
- export declare let activeElement: CustomElement<Events, Attributes> | undefined;
89
+ export declare const MOUNTED_HOOK: Descriptor<HTMLElement>;
90
90
  export declare const components: Map<string, ComponentConstructor<Events, Attributes>>;
@@ -21,7 +21,7 @@ export type Set = {
21
21
  message?: string;
22
22
  headers: Headers;
23
23
  };
24
- export type Event<T> = {
24
+ export type Event<T = Record<string, unknown>> = {
25
25
  request: Request;
26
26
  response: Set;
27
27
  context: Context<T>;
package/dist/index.d.ts CHANGED
@@ -7,3 +7,4 @@ export * from "./radix";
7
7
  export * from "./router";
8
8
  export * from "./runtime";
9
9
  export * from "./signals";
10
+ export * from "./types";
package/dist/index.js CHANGED
@@ -2,57 +2,52 @@ import { defu } from "defu";
2
2
  import { h } from "revojs/jsx-runtime";
3
3
 
4
4
  //#region src/app/index.ts
5
- const defineContent = (key) => {
6
- return Object.assign(String(key), { key });
5
+ const getRoutes = async () => {
6
+ return await import("#virtual/routes").then((module) => module.index);
7
7
  };
8
- const getContent = async (key) => {
9
- return await import("#virtual/content").then((module) => module.index[key.toString()]);
8
+ const getAssets = async () => {
9
+ return await import("#virtual/assets").then((module) => module.index);
10
10
  };
11
11
  const createApp = (config) => {
12
12
  return {
13
13
  config: defu(config, {
14
14
  client: { entry: "./index.html" },
15
15
  server: { entry: "revojs/presets/node" },
16
- content: {
17
- [STATIC_CONTENT]: {
18
- type: "static",
19
- source: "./dist/client",
20
- include: ["**/*.*"]
21
- },
22
- [PAGES_CONTENT]: {
23
- type: "asset",
24
- source: "./routes",
25
- include: [
26
- "**/*.js",
27
- "**/*.ts",
28
- "**/*.jsx",
29
- "**/*.tsx",
30
- "!**/*.*.js",
31
- "!**/*.*.ts",
32
- "!**/*.*.jsx",
33
- "!**/*.*.tsx"
34
- ]
35
- },
36
- [ROUTES_CONTENT]: {
37
- type: "asset",
38
- source: "./routes",
39
- include: [
40
- "**/*.*.js",
41
- "**/*.*.ts",
42
- "**/*.*.jsx",
43
- "**/*.*.tsx"
44
- ]
45
- }
46
- },
47
16
  markdown: {},
48
17
  dev: { middleware: [] }
49
18
  }),
50
19
  virtuals: {}
51
20
  };
52
21
  };
53
- const STATIC_CONTENT = defineContent("STATIC");
54
- const PAGES_CONTENT = defineContent("PAGES");
55
- const ROUTES_CONTENT = defineContent("ROUTES");
22
+
23
+ //#endregion
24
+ //#region src/types/index.ts
25
+ const descriptor = (descriptor$1) => {
26
+ return (typeof descriptor$1 === "object" ? descriptor$1.key : descriptor$1).toString();
27
+ };
28
+
29
+ //#endregion
30
+ //#region src/hooks/index.ts
31
+ var Hooks = class {
32
+ hooks;
33
+ constructor() {
34
+ this.hooks = new Map();
35
+ }
36
+ on = (scope, input, invoke) => {
37
+ const key = descriptor(input);
38
+ const invokes = this.hooks.get(key) ?? new Set();
39
+ invokes.add(invoke);
40
+ this.hooks.set(key, invokes);
41
+ return scope.dispose.push(() => invokes.delete(invoke));
42
+ };
43
+ dispatch = (input, value) => {
44
+ const invokes = this.hooks.get(descriptor(input));
45
+ if (invokes) for (const invoke of invokes) invoke(value);
46
+ };
47
+ };
48
+ const defineHook = (description) => {
49
+ return { key: Symbol(description) };
50
+ };
56
51
 
57
52
  //#endregion
58
53
  //#region src/jsx/index.ts
@@ -146,29 +141,21 @@ var Scope = class {
146
141
  constructor() {
147
142
  this.dispose = new Array();
148
143
  }
149
- run(invoke) {
150
- let previous = activeScope;
151
- activeScope = this;
152
- try {
153
- return invoke();
154
- } finally {
155
- activeScope = previous;
156
- }
157
- }
158
144
  stop() {
159
145
  while (this.dispose.length) this.dispose.pop()?.(this);
160
146
  }
161
147
  };
162
- var Compute = class {
163
- invoke;
148
+ var Compute = class extends Scope {
164
149
  scope;
165
- constructor(invoke) {
150
+ invoke;
151
+ constructor(scope, invoke) {
152
+ super();
153
+ this.scope = scope;
166
154
  this.invoke = invoke;
167
- this.scope = new Scope();
168
155
  }
169
156
  run() {
170
- this.scope.stop();
171
- return this.scope.run(this.invoke);
157
+ this.stop();
158
+ return this.invoke(this);
172
159
  }
173
160
  };
174
161
  var Handler = class Handler {
@@ -179,8 +166,8 @@ var Handler = class Handler {
179
166
  const set = computes.get(key) ?? new Set();
180
167
  computes.set(key, set.add(compute));
181
168
  targets.set(target, computes);
182
- onCleanUp(() => {
183
- compute.scope.stop();
169
+ compute.scope.dispose.push(() => {
170
+ compute.stop();
184
171
  set.delete(compute);
185
172
  });
186
173
  }
@@ -200,18 +187,18 @@ var Handler = class Handler {
200
187
  function createState(value) {
201
188
  return new Proxy({ value }, new Handler());
202
189
  }
203
- function createCompute(invoke) {
190
+ function createCompute(scope, invoke) {
204
191
  let previous = activeCompute;
205
- activeCompute = new Compute(invoke);
192
+ activeCompute = new Compute(scope, invoke);
206
193
  try {
207
- return invoke();
194
+ return invoke(activeCompute);
208
195
  } finally {
209
196
  activeCompute = previous;
210
197
  }
211
198
  }
212
- function createMemo(invoke) {
199
+ function createMemo(scope, invoke) {
213
200
  let state;
214
- const compute = createCompute(() => {
201
+ const compute = createCompute(scope, () => {
215
202
  const value = invoke();
216
203
  if (typeof state === "object") state.value = value;
217
204
  return value;
@@ -219,17 +206,10 @@ function createMemo(invoke) {
219
206
  state = createState(compute);
220
207
  return state;
221
208
  }
222
- function onCleanUp(invoke) {
223
- activeScope?.dispose.push(invoke);
224
- }
225
209
  function fromValue(value) {
226
- if (value) {
227
- if (value instanceof Function) return fromValue(value());
228
- if (value instanceof Object) return value.value;
229
- }
210
+ if (value instanceof Function) return fromValue(value());
230
211
  return value;
231
212
  }
232
- let activeScope;
233
213
  let activeCompute;
234
214
  const targets = new WeakMap();
235
215
 
@@ -238,9 +218,6 @@ const targets = new WeakMap();
238
218
  const isTemplate = (value) => {
239
219
  return "tag" in value && "attributes" in value && "children" in value;
240
220
  };
241
- const descriptor = (descriptor$1) => {
242
- return (typeof descriptor$1 === "object" ? descriptor$1.key : descriptor$1).toString();
243
- };
244
221
  const defineContext = (description) => {
245
222
  return { key: Symbol(description) };
246
223
  };
@@ -269,79 +246,102 @@ const toFragment = (nodes) => {
269
246
  return fragment;
270
247
  };
271
248
  const renderToString = async (slot, context) => {
272
- if (typeof slot === "number" || typeof slot === "bigint" || typeof slot === "boolean" || typeof slot === "string" || typeof slot === "symbol") return slot.toString();
273
- if (typeof slot === "function") return await renderToString(await slot(), context);
274
- if (typeof slot === "object") {
275
- if (Array.isArray(slot)) return await Promise.all(slot.map((slot$1) => renderToString(slot$1, context))).then((chunks) => chunks.join(""));
276
- if (isTemplate(slot)) {
277
- const customElement = components.get(slot.tag);
278
- const prefix = Object.entries(slot.attributes).reduce((chunks, [name, value]) => {
279
- if (!name.startsWith("on")) chunks.push(`${name}='${toString(value)}'`);
280
- return chunks;
281
- }, [slot.tag]).join(" ");
282
- const children = await renderToString(slot.children, context);
283
- if (customElement) {
284
- const element = new customElement(slot.attributes, context);
285
- const template = await renderToString(await element.setup(), context);
286
- if (element.shadowRoot) {
287
- const shadow = await renderToString({
288
- tag: "template",
289
- attributes: { shadowRootMode: element.shadowRoot.mode },
290
- children: [template]
291
- }, context);
292
- return `<${prefix}>` + shadow + children + `</${slot.tag}>`;
249
+ if (slot) {
250
+ if (typeof slot === "number" || typeof slot === "bigint" || typeof slot === "boolean" || typeof slot === "string" || typeof slot === "symbol") return slot.toString();
251
+ if (typeof slot === "function") return await renderToString(await slot(), context);
252
+ if (typeof slot === "object") {
253
+ if (Array.isArray(slot)) return await Promise.all(slot.map((slot$1) => renderToString(slot$1, context))).then((chunks) => chunks.join(""));
254
+ if (isTemplate(slot)) {
255
+ const customElement = components.get(slot.tag);
256
+ const prefix = Object.entries(slot.attributes).reduce((chunks, [name, value]) => {
257
+ if (!name.startsWith("on")) chunks.push(`${name}='${toString(value)}'`);
258
+ return chunks;
259
+ }, [slot.tag]).join(" ");
260
+ const children = await renderToString(slot.children, context);
261
+ if (customElement) {
262
+ const element = new customElement(slot.attributes, context);
263
+ const template = await renderToString(await element.setup(), context);
264
+ if (element.shadowRoot) {
265
+ const shadow = await renderToString({
266
+ tag: "template",
267
+ attributes: { shadowRootMode: element.shadowRoot.mode },
268
+ children: [template]
269
+ }, context);
270
+ return `<${prefix}>` + shadow + children + `</${slot.tag}>`;
271
+ }
272
+ return `<${prefix}>` + template + children + `</${slot.tag}>`;
293
273
  }
294
- return `<${prefix}>` + template + children + `</${slot.tag}>`;
274
+ return `<${prefix}>` + children + `</${slot.tag}>`;
295
275
  }
296
- return `<${prefix}>` + children + `</${slot.tag}>`;
276
+ return JSON.stringify(slot);
297
277
  }
298
- return JSON.stringify(slot);
299
278
  }
300
279
  return "<!---->";
301
280
  };
302
- const renderToNode = async (slot) => {
303
- if (typeof slot === "number" || typeof slot === "bigint" || typeof slot === "boolean" || typeof slot === "string" || typeof slot === "symbol") return document.createTextNode(slot.toString());
304
- if (typeof slot === "function") {
305
- let node;
306
- await createCompute(async () => {
307
- let input = slot;
308
- let parentNode = Array.isArray(node) ? node.at(0)?.parentNode : node?.parentNode;
309
- while (typeof input === "function") input = await input();
310
- let next = await (Array.isArray(input) ? Promise.all(input.map(renderToNode)) : renderToNode(input));
311
- if (Array.isArray(next)) if (Array.isArray(node)) {
312
- if (parentNode) {
313
- const range = document.createRange();
314
- const firstNode = node.at(0);
315
- if (firstNode) range.setStartBefore(firstNode);
316
- const lastNode = node.at(-1);
317
- if (lastNode) range.setEndAfter(lastNode);
318
- range.deleteContents();
319
- for (const child of next) parentNode?.appendChild(child);
281
+ const renderToNode = async (scope, slot) => {
282
+ if (slot) {
283
+ if (typeof slot === "number" || typeof slot === "bigint" || typeof slot === "boolean" || typeof slot === "string" || typeof slot === "symbol") return document.createTextNode(slot.toString());
284
+ if (typeof slot === "function") {
285
+ let node;
286
+ await createCompute(scope, async (scope$1) => {
287
+ let next;
288
+ let input = slot;
289
+ let parentNode = Array.isArray(node) ? node.at(0)?.parentNode : node?.parentNode;
290
+ while (typeof input === "function") input = await input();
291
+ if (Array.isArray(input)) if (input.length) next = await Promise.all(input.map((child) => renderToNode(scope$1, child)));
292
+ else next = document.createComment("");
293
+ else next = await renderToNode(scope$1, input);
294
+ if (Array.isArray(next)) if (Array.isArray(node)) {
295
+ if (parentNode) {
296
+ const range = document.createRange();
297
+ const firstNode = node.at(0);
298
+ if (firstNode) range.setStartBefore(firstNode);
299
+ const lastNode = node.at(-1);
300
+ if (lastNode) range.setEndAfter(lastNode);
301
+ range.deleteContents();
302
+ for (const child of next) parentNode?.appendChild(child);
303
+ }
304
+ } else node?.parentNode?.replaceChild(toFragment(next), node);
305
+ else if (Array.isArray(node)) {
306
+ if (parentNode) {
307
+ const range = document.createRange();
308
+ const firstNode = node.at(0);
309
+ if (firstNode) range.setStartBefore(firstNode);
310
+ const lastNode = node.at(-1);
311
+ if (lastNode) range.setEndAfter(lastNode);
312
+ range.deleteContents();
313
+ parentNode?.appendChild(next);
314
+ }
315
+ } else node?.parentNode?.replaceChild(next, node);
316
+ node = next;
317
+ });
318
+ if (Array.isArray(node)) return toFragment(node);
319
+ return node ?? document.createComment("");
320
+ }
321
+ if (typeof slot === "object") {
322
+ if (Array.isArray(slot)) {
323
+ if (slot.length) {
324
+ const children = await Promise.all(slot.map((child) => renderToNode(scope, child)));
325
+ return toFragment(children);
320
326
  }
321
- } else node?.parentNode?.replaceChild(toFragment(next), node);
322
- else if (Array.isArray(node)) {} else node?.parentNode?.replaceChild(next, node);
323
- node = next;
324
- });
325
- if (Array.isArray(node)) return toFragment(node);
326
- return node ?? document.createComment("");
327
- }
328
- if (typeof slot === "object") {
329
- if (Array.isArray(slot)) return toFragment(await Promise.all(slot.map(renderToNode)));
330
- if (isTemplate(slot)) {
331
- const element = document.createElementNS(namespace(slot.tag), slot.tag);
332
- for (const name in slot.attributes) {
333
- const value = slot.attributes[name];
334
- if (value) if (name.startsWith("on") && typeof value === "function") {
335
- const event = name.substring(2).toLowerCase();
336
- const controller = new AbortController();
337
- element.addEventListener(event, value, { signal: controller.signal });
338
- onCleanUp(() => controller.abort());
339
- } else createCompute(() => element.setAttribute(name, toString(value)));
327
+ return document.createComment("");
340
328
  }
341
- element.replaceChildren(await renderToNode(slot.children));
342
- return element;
329
+ if (isTemplate(slot)) {
330
+ const element = document.createElementNS(namespace(slot.tag), slot.tag);
331
+ for (const name in slot.attributes) {
332
+ const value = slot.attributes[name];
333
+ if (value) if (name.startsWith("on") && typeof value === "function") {
334
+ const event = name.substring(2).toLowerCase();
335
+ const controller = new AbortController();
336
+ element.addEventListener(event, value, { signal: controller.signal });
337
+ scope.dispose.push(() => controller.abort());
338
+ } else createCompute(scope, () => element.setAttribute(name, toString(value)));
339
+ }
340
+ element.replaceChildren(await renderToNode(scope, slot.children));
341
+ return element;
342
+ }
343
+ return document.createTextNode(JSON.stringify(slot));
343
344
  }
344
- return document.createTextNode(JSON.stringify(slot));
345
345
  }
346
346
  return document.createComment("");
347
347
  };
@@ -350,59 +350,61 @@ const defineComponent = (options) => {
350
350
  static $name = options.name;
351
351
  static $events = options.events ?? {};
352
352
  static $attributes = options.attributes ?? {};
353
+ scope;
354
+ hooks;
353
355
  events;
354
356
  attributes;
355
357
  shadowRoot;
356
358
  context;
357
- constructor(input, context) {
359
+ host;
360
+ constructor(input, context, host) {
361
+ this.scope = new Scope();
362
+ this.hooks = new Hooks();
358
363
  this.events = Object.keys(options.events ?? {}).reduce((output, name) => {
359
364
  Reflect.set(output, name, input?.[name], output);
360
365
  return output;
361
366
  }, {});
362
- this.attributes = Object.entries(options.attributes ?? {}).reduce((output, [name, attribute]) => {
363
- Reflect.set(output.value, name, input?.[name] ?? attribute.default, output.value);
364
- return output;
367
+ this.attributes = Object.entries(options.attributes ?? {}).reduce((attributes, [name, attribute]) => {
368
+ Reflect.set(attributes.value, name, fromValue(input?.[name]) ?? attribute.default, attributes.value);
369
+ return attributes;
365
370
  }, createState({}));
366
371
  this.shadowRoot = options.shadowRoot ?? { mode: "open" };
367
372
  this.context = context ?? {};
373
+ this.host = host;
368
374
  }
369
375
  getContext = (input) => {
370
- return this.context[descriptor(input)];
376
+ return this.context[descriptor(input)] ?? {};
371
377
  };
372
378
  setContext = (input, value) => {
373
379
  this.context[descriptor(input)] = value;
374
380
  };
381
+ onMounted = (invoke) => {
382
+ return this.hooks.on(this.scope, MOUNTED_HOOK, invoke);
383
+ };
375
384
  setup = () => options.setup(this);
376
385
  });
377
386
  };
378
387
  const toCustomElement = (component) => {
379
388
  return class extends HTMLElement {
380
389
  static formAssociated = true;
381
- scope;
382
390
  component;
383
391
  constructor() {
384
392
  super();
385
- this.scope = new Scope();
386
- this.component = new component();
393
+ this.component = new component(undefined, undefined, this);
387
394
  }
388
395
  async connectedCallback() {
389
- let previous = activeElement;
396
+ const shadow = this.component.shadowRoot ? this.attachShadow(this.component.shadowRoot) : this;
390
397
  const parentNode = getCustomElement(this.parentNode);
391
- activeElement = this;
392
- try {
393
- const shadow = this.component.shadowRoot ? this.attachShadow(this.component.shadowRoot) : this;
394
- for (const [name, event] of Object.entries(component.$events)) Reflect.set(this.component.events, name, (value) => {
395
- if (value instanceof Event) return;
396
- this.dispatchEvent(new CustomEvent(name.substring(2).toLowerCase(), {
397
- ...event,
398
- detail: value
399
- }));
400
- }, this.component.events);
401
- if (parentNode) for (const key in parentNode.component.context) this.component.setContext(key, parentNode.component.context[key]);
402
- await this.scope.run(async () => shadow.replaceChildren(await renderToNode(await this.component.setup())));
403
- } finally {
404
- activeElement = previous;
405
- }
398
+ for (const [name, event] of Object.entries(component.$events)) Reflect.set(this.component.events, name, (value) => {
399
+ if (value instanceof Event) return;
400
+ this.dispatchEvent(new CustomEvent(name.substring(2).toLowerCase(), {
401
+ ...event,
402
+ detail: value
403
+ }));
404
+ }, this.component.events);
405
+ if (parentNode) for (const key in parentNode.component.context) this.component.setContext(key, parentNode.component.context[key]);
406
+ shadow.replaceChildren(await renderToNode(this.component.scope, await this.component.setup()));
407
+ this.component.hooks.dispatch(MOUNTED_HOOK, this);
406
408
  }
407
409
  attributeChangedCallback(name, oldValue, value) {
408
410
  if (value === oldValue) return;
@@ -417,7 +419,7 @@ const toCustomElement = (component) => {
417
419
  convertedValue = Number(value);
418
420
  break;
419
421
  case Boolean:
420
- convertedValue = Boolean(value);
422
+ convertedValue = value.toLowerCase() === "true";
421
423
  break;
422
424
  case Object:
423
425
  convertedValue = JSON.parse(value);
@@ -427,7 +429,7 @@ const toCustomElement = (component) => {
427
429
  }
428
430
  }
429
431
  disconnectedCallback() {
430
- this.scope.stop();
432
+ this.component.scope.stop();
431
433
  }
432
434
  static get observedAttributes() {
433
435
  return Object.keys(component.$attributes ?? {});
@@ -435,19 +437,10 @@ const toCustomElement = (component) => {
435
437
  };
436
438
  };
437
439
  const registerComponent = (component) => {
438
- components.set(component.$name, component);
440
+ if (isServer()) components.set(component.$name, component);
439
441
  if (isClient()) customElements.define(component.$name, toCustomElement(component));
440
442
  return component;
441
443
  };
442
- const isClient = () => {
443
- return typeof window !== "undefined";
444
- };
445
- const isServer = () => {
446
- return typeof window === "undefined";
447
- };
448
- const addStyles = (...styles) => {
449
- activeElement?.shadowRoot?.adoptedStyleSheets.push(...styles);
450
- };
451
444
  const getGlobalStyles = () => {
452
445
  return Array.from(isServer() ? [] : document.styleSheets).map((style) => {
453
446
  const sheet = new CSSStyleSheet();
@@ -462,41 +455,14 @@ const getCustomElement = (node) => {
462
455
  return getCustomElement(node.parentNode);
463
456
  }
464
457
  };
465
- const preventDefault = (event) => {
466
- event.preventDefault();
467
- };
468
- const stopPropagation = (event) => {
469
- event.stopPropagation();
470
- };
471
- const stopImmediatePropagation = (event) => {
472
- event.stopImmediatePropagation();
473
- };
474
- let activeElement;
458
+ const isClient = () => typeof window !== "undefined";
459
+ const isServer = () => typeof window === "undefined";
460
+ const preventDefault = (event) => event.preventDefault();
461
+ const stopPropagation = (event) => event.stopPropagation();
462
+ const stopImmediatePropagation = (event) => event.stopImmediatePropagation();
463
+ const MOUNTED_HOOK = defineHook("MOUNTED_HOOK");
475
464
  const components = new Map();
476
465
 
477
- //#endregion
478
- //#region src/hooks/index.ts
479
- var Hooks = class {
480
- hooks;
481
- constructor() {
482
- this.hooks = new Map();
483
- }
484
- on = (input, invoke) => {
485
- const key = descriptor(input);
486
- const invokes = this.hooks.get(key) ?? new Set();
487
- invokes.add(invoke);
488
- this.hooks.set(key, invokes);
489
- return onCleanUp(() => invokes.delete(invoke));
490
- };
491
- dispatch = (input, value) => {
492
- const invokes = this.hooks.get(descriptor(input));
493
- if (invokes) for (const invoke of invokes) invoke(value);
494
- };
495
- };
496
- const defineHook = (description) => {
497
- return { key: Symbol(description) };
498
- };
499
-
500
466
  //#endregion
501
467
  //#region src/http/index.ts
502
468
  const createEvent = (request, context) => {
@@ -715,13 +681,15 @@ const fileName = (path) => {
715
681
  };
716
682
  const toPath = (value) => {
717
683
  const path = (value.startsWith("/") ? value : "/" + value).replaceAll(/\/index/g, "").replaceAll(/\[(.*)\]/g, (_, name) => ":" + name);
718
- return (path.startsWith("/") ? path : "/" + path).split(".");
684
+ const route = path.startsWith("/") ? path : "/" + path;
685
+ const split = route.split(".");
686
+ return split.length === 3 ? [split.at(0), split.at(1)] : [split.at(0)];
719
687
  };
720
- const $fetch = async (input, init) => {
688
+ const $fetch = async (event, input, init) => {
721
689
  let response;
722
- if (activeEvent) {
723
- const url = new URL(input.toString(), activeEvent.request.url);
724
- response = await (await import("#virtual/runtime")).runtime.fetch(new Request(url, init), activeEvent.context);
690
+ if (event) {
691
+ const url = new URL(input.toString(), event.request.url);
692
+ response = await (await import("#virtual/runtime")).runtime.fetch(new Request(url, init), event.context);
725
693
  } else response = await fetch(input, init);
726
694
  if (response.ok === false) throw response;
727
695
  switch (response.headers.get("Content-Type")) {
@@ -735,27 +703,23 @@ const getVariables = (event) => {
735
703
  const createRuntime = async () => {
736
704
  const radix = new Radix();
737
705
  const middlewares = new Array();
738
- const assets = await getContent(STATIC_CONTENT);
739
- for (const path in assets) radix.insert("GET/" + path, defineRoute({ fetch: async (event) => {
740
- event.response.headers.set("Content-Type", getMimeType(path));
741
- return new Response(await assets[path]?.(), event.response);
742
- } }));
743
- const pages = await getContent(PAGES_CONTENT);
744
- for (const path in pages) {
745
- const [name] = toPath(path);
746
- radix.insert("GET" + name, defineRoute({ fetch: async (event) => {
747
- const slot = await import("#virtual/client").then((module) => module.index);
748
- return sendHtml(event, await renderToString(slot, {}));
749
- } }));
750
- }
751
- const routes = await getContent(ROUTES_CONTENT);
706
+ const routes = await getRoutes();
752
707
  for (const path in routes) {
753
708
  const [name, method] = toPath(path);
754
709
  radix.insert((method ?? "GET").toUpperCase() + name, defineRoute({ fetch: async (event) => {
755
710
  const route = await routes[path]?.();
756
- if (route) return route.fetch(event);
711
+ if (typeof route === "object") return route.fetch(event);
712
+ if (route) {
713
+ const slot = await import("#virtual/client").then((module) => module.index);
714
+ return sendHtml(event, await renderToString(slot, { [descriptor(RUNTIME_CONTEXT)]: { event } }));
715
+ }
757
716
  } }));
758
717
  }
718
+ const assets = await getAssets();
719
+ for (const path in assets) radix.insert("GET/" + path, defineRoute({ fetch: async (event) => {
720
+ event.response.headers.set("Content-Type", getMimeType(path));
721
+ return new Response(await assets[path]?.(), event.response);
722
+ } }));
759
723
  const invoke = (event, next, index) => {
760
724
  return middlewares.at(index)?.(event, () => invoke(event, next, index + 1)) ?? next(event);
761
725
  };
@@ -765,44 +729,43 @@ const createRuntime = async () => {
765
729
  fetch: async (request, context) => {
766
730
  const url = getRequestUrl(request);
767
731
  const { value: route, inputs } = radix.match(request.method + url.pathname);
768
- activeEvent = createEvent(request, {
732
+ const event = createEvent(request, {
769
733
  ...context,
770
734
  inputs
771
735
  });
772
736
  try {
773
737
  if (route) {
774
- const response = await invoke(activeEvent, route.fetch, 0);
738
+ const response = await invoke(event, route.fetch, 0);
775
739
  if (response) return response;
776
740
  }
777
- return sendText(activeEvent, "Not found");
741
+ return sendText(event, "Not found");
778
742
  } catch (response) {
779
743
  if (response instanceof Response) return response;
780
744
  throw response;
781
- } finally {
782
- activeEvent = undefined;
783
745
  }
784
746
  }
785
747
  };
786
748
  };
787
- let activeEvent;
749
+ const RUNTIME_CONTEXT = defineContext("RUNTIME_CONTEXT");
788
750
 
789
751
  //#endregion
790
752
  //#region src/router/index.tsx
791
753
  const Outlet = defineComponent({
792
754
  name: "x-outlet",
793
755
  shadowRoot: false,
794
- setup: async () => {
756
+ setup: async ({ scope, getContext }) => {
757
+ const { event } = getContext(RUNTIME_CONTEXT);
795
758
  const radix = new Radix();
796
- const routes = await getContent(PAGES_CONTENT);
759
+ const routes = await getRoutes();
797
760
  for (const path in routes) {
798
761
  const [name] = toPath(path);
799
- radix.insert(name, routes[path]);
762
+ if (name) radix.insert(name, routes[path]);
800
763
  }
801
- const url = createState(new URL(activeEvent ? activeEvent.request.url : window.location.href));
764
+ const url = createState(new URL(event ? event.request.url : window.location.href));
802
765
  if (isClient()) {
803
766
  const controller = new AbortController();
804
767
  window.addEventListener("popstate", () => url.value = new URL(window.location.href), { signal: controller.signal });
805
- onCleanUp(() => controller.abort());
768
+ scope.dispose.push(() => controller.abort());
806
769
  }
807
770
  return async () => {
808
771
  const { value, inputs } = radix.match(url.value.pathname);
@@ -824,4 +787,4 @@ const anchorNavigate = (event) => {
824
787
  };
825
788
 
826
789
  //#endregion
827
- export { $fetch, Compute, Handler, Hooks, Outlet, PAGES_CONTENT, ROUTES_CONTENT, Radix, STATIC_CONTENT, Scope, activeCompute, activeElement, activeEvent, activeScope, addStyles, anchorNavigate, components, createApp, createCompute, createElement, createEvent, createMemo, createRuntime, createState, defineComponent, defineContent, defineContext, defineHook, defineRoute, descriptor, fileName, fromValue, getContent, getCookies, getCustomElement, getGlobalStyles, getMimeType, getRequestUrl, getSetCookies, getVariables, isClient, isServer, isTemplate, markdownToSlot, navigate, onCleanUp, preventDefault, registerComponent, renderToNode, renderToString, sendBadRequest, sendHtml, sendJson, sendRedirect, sendText, sendUnauthorized, setCookie, stopImmediatePropagation, stopPropagation, targets, toCustomElement, toFragment, toPath, toString };
790
+ export { $fetch, Compute, Handler, Hooks, MOUNTED_HOOK, Outlet, RUNTIME_CONTEXT, Radix, Scope, activeCompute, anchorNavigate, components, createApp, createCompute, createElement, createEvent, createMemo, createRuntime, createState, defineComponent, defineContext, defineHook, defineRoute, descriptor, fileName, fromValue, getAssets, getCookies, getCustomElement, getGlobalStyles, getMimeType, getRequestUrl, getRoutes, getSetCookies, getVariables, isClient, isServer, isTemplate, markdownToSlot, navigate, preventDefault, registerComponent, renderToNode, renderToString, sendBadRequest, sendHtml, sendJson, sendRedirect, sendText, sendUnauthorized, setCookie, stopImmediatePropagation, stopPropagation, targets, toCustomElement, toFragment, toPath, toString };
@@ -1,4 +1,4 @@
1
- import { type Primitiv, type Slot } from "../html";
1
+ import { type Slot } from "../html";
2
2
  export type EventAttributes = {
3
3
  onScroll?: (event: Event) => void;
4
4
  onScrollCapture?: (event: Event) => void;
@@ -124,7 +124,7 @@ export type EventAttributes = {
124
124
  onChangeCapture?: (event: Event) => void;
125
125
  };
126
126
  export type HtmlTags = Record<string, HtmlAttributes & EventAttributes>;
127
- export type HtmlAttributes = Record<string, Primitiv>;
127
+ export type HtmlAttributes = Record<string, unknown>;
128
128
  export type HtmlAnchorAttributes = HtmlAttributes & EventAttributes;
129
129
  export type HtmlAbbrAttributes = HtmlAttributes & EventAttributes;
130
130
  export type HtmlAddressAttributes = HtmlAttributes & EventAttributes;
package/dist/jsx/index.js CHANGED
@@ -1,4 +1,10 @@
1
1
 
2
+ //#region src/hooks/index.ts
3
+ const defineHook = (description) => {
4
+ return { key: Symbol(description) };
5
+ };
6
+
7
+ //#endregion
2
8
  //#region src/html/index.ts
3
9
  const createElement = (input, attributes, ...children) => {
4
10
  return {
@@ -7,6 +13,7 @@ const createElement = (input, attributes, ...children) => {
7
13
  children
8
14
  };
9
15
  };
16
+ const MOUNTED_HOOK = defineHook("MOUNTED_HOOK");
10
17
 
11
18
  //#endregion
12
19
  //#region src/jsx/index.ts
@@ -1,2 +1,2 @@
1
- import type { Slot, Template } from "../html";
2
- export declare const markdownToSlot: (input: string, options?: Record<string, Template>) => Slot[];
1
+ import type { Template } from "../html";
2
+ export declare const markdownToSlot: (input: string, options?: Record<string, Template>) => unknown[];
@@ -1,3 +1,4 @@
1
- export declare const Outlet: import("..").ComponentConstructor<{}, {}>;
1
+ import { type ComponentConstructor } from "../html";
2
+ export declare const Outlet: ComponentConstructor<{}, {}>;
2
3
  export declare const navigate: (url: string) => void;
3
4
  export declare const anchorNavigate: (event: Event) => void;
@@ -10,8 +10,10 @@ export type Runtime<T> = {
10
10
  };
11
11
  export declare const defineRoute: <T>(route: Route<T>) => Route<T>;
12
12
  export declare const fileName: (path: string) => string | undefined;
13
- export declare const toPath: (value: string) => [string, string | undefined];
14
- export declare const $fetch: <T>(input: string | URL, init?: RequestInit) => Promise<T>;
13
+ export declare const toPath: (value: string) => (string | undefined)[];
14
+ export declare const $fetch: <T>(event: Event | undefined, input: string | URL, init?: RequestInit) => Promise<T>;
15
15
  export declare const getVariables: <T>(event?: Event<T>) => T;
16
16
  export declare const createRuntime: <T>() => Promise<Runtime<T>>;
17
- export declare let activeEvent: Event<unknown> | undefined;
17
+ export declare const RUNTIME_CONTEXT: import("..").Descriptor<{
18
+ event?: Event;
19
+ }>;
@@ -1,17 +1,16 @@
1
- export type Value<T> = T | State<T> | (() => Value<T>);
1
+ export type Value<T> = T | (() => Value<T>);
2
2
  export interface State<T> {
3
3
  value: T;
4
4
  }
5
5
  export declare class Scope {
6
6
  readonly dispose: Array<(scope: Scope) => void>;
7
7
  constructor();
8
- run<T>(invoke: () => T): T;
9
8
  stop(): void;
10
9
  }
11
- export declare class Compute<T = void> {
12
- readonly invoke: () => T;
10
+ export declare class Compute<T = void> extends Scope {
13
11
  readonly scope: Scope;
14
- constructor(invoke: () => T);
12
+ readonly invoke: (scope: Scope) => T;
13
+ constructor(scope: Scope, invoke: (scope: Scope) => T);
15
14
  run(): T;
16
15
  }
17
16
  export declare class Handler<T extends object> implements ProxyHandler<T> {
@@ -20,10 +19,8 @@ export declare class Handler<T extends object> implements ProxyHandler<T> {
20
19
  }
21
20
  export declare function createState<T>(): State<T | undefined>;
22
21
  export declare function createState<T>(value: T): State<T>;
23
- export declare function createCompute<T>(invoke: () => T): T;
24
- export declare function createMemo<T>(invoke: () => T): State<T>;
25
- export declare function onCleanUp(invoke: (scope: Scope) => void): void;
22
+ export declare function createCompute<T>(scope: Scope, invoke: (scope: Scope) => T): T;
23
+ export declare function createMemo<T>(scope: Scope, invoke: () => T): State<T>;
26
24
  export declare function fromValue<T>(value: Value<T>): T;
27
- export declare let activeScope: Scope | undefined;
28
25
  export declare let activeCompute: Compute | undefined;
29
26
  export declare const targets: WeakMap<object, Map<string | symbol, Set<Compute<void>>>>;
@@ -0,0 +1,4 @@
1
+ export type Descriptor<T> = {
2
+ key: symbol;
3
+ };
4
+ export declare const descriptor: <T>(descriptor: string | Descriptor<T>) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "revojs",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "repository": "coverbase/revojs",
6
6
  "license": "MIT",