@typed/ui 0.14.0 → 1.0.0-beta.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.
Files changed (78) hide show
  1. package/README.md +104 -2
  2. package/dist/HttpRouter.d.ts +13 -0
  3. package/dist/HttpRouter.d.ts.map +1 -0
  4. package/dist/HttpRouter.js +99 -0
  5. package/dist/Link.d.ts +36 -0
  6. package/dist/Link.d.ts.map +1 -0
  7. package/dist/Link.js +45 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +2 -0
  11. package/package.json +33 -83
  12. package/src/HttpRouter.test.ts +294 -0
  13. package/src/HttpRouter.ts +168 -0
  14. package/src/Link.test.ts +84 -0
  15. package/src/Link.ts +97 -90
  16. package/src/index.ts +2 -40
  17. package/LICENSE +0 -21
  18. package/Link/package.json +0 -6
  19. package/Props/package.json +0 -6
  20. package/dist/cjs/Link.js +0 -76
  21. package/dist/cjs/Link.js.map +0 -1
  22. package/dist/cjs/Props.js +0 -26
  23. package/dist/cjs/Props.js.map +0 -1
  24. package/dist/cjs/dom-properties.js +0 -6
  25. package/dist/cjs/dom-properties.js.map +0 -1
  26. package/dist/cjs/hyperscript.js +0 -484
  27. package/dist/cjs/hyperscript.js.map +0 -1
  28. package/dist/cjs/index.js +0 -39
  29. package/dist/cjs/index.js.map +0 -1
  30. package/dist/cjs/internal/addEventListener.js +0 -19
  31. package/dist/cjs/internal/addEventListener.js.map +0 -1
  32. package/dist/cjs/useClickAway.js +0 -43
  33. package/dist/cjs/useClickAway.js.map +0 -1
  34. package/dist/cjs/usePagination.js +0 -68
  35. package/dist/cjs/usePagination.js.map +0 -1
  36. package/dist/dts/Link.d.ts +0 -26
  37. package/dist/dts/Link.d.ts.map +0 -1
  38. package/dist/dts/Props.d.ts +0 -67
  39. package/dist/dts/Props.d.ts.map +0 -1
  40. package/dist/dts/dom-properties.d.ts +0 -928
  41. package/dist/dts/dom-properties.d.ts.map +0 -1
  42. package/dist/dts/hyperscript.d.ts +0 -461
  43. package/dist/dts/hyperscript.d.ts.map +0 -1
  44. package/dist/dts/index.d.ts +0 -16
  45. package/dist/dts/index.d.ts.map +0 -1
  46. package/dist/dts/internal/addEventListener.d.ts +0 -6
  47. package/dist/dts/internal/addEventListener.d.ts.map +0 -1
  48. package/dist/dts/useClickAway.d.ts +0 -19
  49. package/dist/dts/useClickAway.d.ts.map +0 -1
  50. package/dist/dts/usePagination.d.ts +0 -41
  51. package/dist/dts/usePagination.d.ts.map +0 -1
  52. package/dist/esm/Link.js +0 -52
  53. package/dist/esm/Link.js.map +0 -1
  54. package/dist/esm/Props.js +0 -19
  55. package/dist/esm/Props.js.map +0 -1
  56. package/dist/esm/dom-properties.js +0 -5
  57. package/dist/esm/dom-properties.js.map +0 -1
  58. package/dist/esm/hyperscript.js +0 -470
  59. package/dist/esm/hyperscript.js.map +0 -1
  60. package/dist/esm/index.js +0 -37
  61. package/dist/esm/index.js.map +0 -1
  62. package/dist/esm/internal/addEventListener.js +0 -11
  63. package/dist/esm/internal/addEventListener.js.map +0 -1
  64. package/dist/esm/package.json +0 -4
  65. package/dist/esm/useClickAway.js +0 -36
  66. package/dist/esm/useClickAway.js.map +0 -1
  67. package/dist/esm/usePagination.js +0 -59
  68. package/dist/esm/usePagination.js.map +0 -1
  69. package/dom-properties/package.json +0 -6
  70. package/hyperscript/package.json +0 -6
  71. package/src/Props.ts +0 -104
  72. package/src/dom-properties.ts +0 -1132
  73. package/src/hyperscript.ts +0 -649
  74. package/src/internal/addEventListener.ts +0 -22
  75. package/src/useClickAway.ts +0 -66
  76. package/src/usePagination.ts +0 -122
  77. package/useClickAway/package.json +0 -6
  78. package/usePagination/package.json +0 -6
package/README.md CHANGED
@@ -1,5 +1,107 @@
1
1
  # @typed/ui
2
2
 
3
- > WIP
3
+ > **Beta:** This package is in beta; APIs may change.
4
4
 
5
- Docs: https://tylors.github.io/typed/docs/ui
5
+ `@typed/ui` is the **web integration layer** for `@typed/router` and `@typed/template`. It bridges typed-smol's routing and template system with the browser and Effect's HTTP stack.
6
+
7
+ ## Capabilities
8
+
9
+ - **Link** — A typed anchor component that intercepts same-origin clicks and navigates via `Navigation.navigate` instead of a full page reload. Keeps routing SPA-style while preserving normal `<a>` semantics (href, target, keyboard, right-click).
10
+ - **SSR** — `ssrForHttp` compiles a router Matcher into HttpRouter GET handlers for server-side rendering. Requests are parsed, matched, and the corresponding Fx is rendered to HTML. `handleHttpServerError` adds global middleware for 404/400/500.
11
+
12
+ ## Dependencies
13
+
14
+ - `effect`
15
+ - `@effect/platform-node`
16
+ - `@typed/fx`
17
+ - `@typed/navigation`
18
+ - `@typed/router`
19
+ - `@typed/template`
20
+ - `happy-dom` (dev)
21
+
22
+ ## API overview
23
+
24
+ - **Link** — `Link(options)` renders an `<a href="...">` that intercepts same-origin, same-document clicks and calls `Navigation.navigate` instead of a full page load. Options include `href`, `content`, `replace`, and standard anchor props. Requires **Navigation** and **RenderTemplate** in context (e.g. browser router).
25
+ - **SSR:** `ssrForHttp(router, matcher)` — registers route handlers on an Effect **HttpRouter** for server-side rendering; `handleHttpServerError(router)` — global middleware for HTTP server errors.
26
+
27
+ ## API reference
28
+
29
+ ### `Link`
30
+
31
+ Renders an `<a href="...">` that intercepts same-origin, same-document clicks and navigates via `Navigation.navigate` instead of a full page load. Requires **Navigation** and **RenderTemplate** in the Effect context (e.g. `BrowserRouter`).
32
+
33
+ ```ts
34
+ function Link<const Opts extends LinkOptions>(
35
+ options: Opts,
36
+ ): Fx<
37
+ RenderEvent,
38
+ Renderable.ErrorFromObject<Opts>,
39
+ Renderable.ServicesFromObject<Opts> | Scope | RenderTemplate
40
+ >;
41
+ ```
42
+
43
+ **`LinkOptions`**
44
+
45
+ | Property | Type | Required | Description |
46
+ | --------- | ----------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------- |
47
+ | `href` | `Renderable<string, any, any>` | Yes | Target URL. |
48
+ | `content` | `Renderable<string \| number \| boolean \| null \| undefined \| void \| RenderEvent, any, any>` | Yes | Link body (text or template content). |
49
+ | `replace` | `boolean` | No | If `true`, use history replace instead of push. Default: `false`. |
50
+
51
+ In addition, `LinkOptions` accepts standard anchor event handlers (e.g. `onclick`), `ref`, and other writable `HTMLAnchorElement` properties. Custom `onclick` runs first; if the event is not `preventDefault`'d, the built-in navigation handler runs.
52
+
53
+ ---
54
+
55
+ ### `ssrForHttp`
56
+
57
+ Registers route handlers on an Effect **HttpRouter** for server-side rendering. The matcher's routes are compiled and each case is exposed as a GET route; requests are parsed, matched, and the corresponding Fx is rendered to HTML. Requires **Router** and **Scope** to be provided elsewhere; other matcher services remain in the effect requirement.
58
+
59
+ **Overloads:**
60
+
61
+ ```ts
62
+ // (router, matcher)
63
+ function ssrForHttp<E, R>(
64
+ router: HttpRouter,
65
+ input: Matcher<RenderEvent, E, R>,
66
+ ): Effect.Effect<void, never, Exclude<R, Scope | Router>>;
67
+
68
+ // (matcher)(router) — curried
69
+ function ssrForHttp<E, R>(
70
+ input: Matcher<RenderEvent, E, R>,
71
+ ): (router: HttpRouter) => Effect.Effect<void, never, Exclude<R, Scope | Router>>;
72
+ ```
73
+
74
+ - **`router`** — Effect `HttpRouter` to attach GET handlers to.
75
+ - **`input`** — A **Matcher** from `@typed/router` whose cases produce `RenderEvent` Fx (e.g. templates). Route path and query params are decoded and passed to the handler; `Scope` and `Router` are provided by the SSR pipeline.
76
+
77
+ ---
78
+
79
+ ### `handleHttpServerError`
80
+
81
+ Adds global middleware to an **HttpRouter** that catches `HttpServerError` and returns appropriate HTTP responses:
82
+
83
+ | Error reason | Status |
84
+ | --------------------------------- | ------ |
85
+ | `RouteNotFound` | 404 |
86
+ | `RequestParseError` | 400 |
87
+ | `InternalError` / `ResponseError` | 500 |
88
+
89
+ ```ts
90
+ function handleHttpServerError(router: HttpRouter): Effect.Effect<void, never, HttpRouter>;
91
+ ```
92
+
93
+ Use after registering routes (e.g. after `ssrForHttp`) so unhandled route and parse errors are converted to 404/400/500 instead of failing the server.
94
+
95
+ ## Example
96
+
97
+ ```ts
98
+ import { Link } from "@typed/ui";
99
+ import { html } from "@typed/template";
100
+
101
+ // In a template: link that navigates via Navigation (no full reload)
102
+ const nav = html`<nav>
103
+ ${Link({ href: "/", content: "Home" })} ${Link({ href: "/todos", content: "Todos" })}
104
+ </nav>`;
105
+ ```
106
+
107
+ For SSR, provide the router and matcher to `ssrForHttp` when setting up the HTTP server; see Effect's `HttpRouter` and the TodoMVC example structure.
@@ -0,0 +1,13 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Scope from "effect/Scope";
3
+ import { type HttpRouter } from "effect/unstable/http/HttpRouter";
4
+ import { type Matcher, type Router } from "@typed/router";
5
+ import { type RenderEvent } from "@typed/template";
6
+ type ProvidedForSsr = Scope.Scope | Router;
7
+ export declare const ssrForHttp: {
8
+ <E, R>(input: Matcher<RenderEvent, E, R>): (router: HttpRouter) => Effect.Effect<void, never, Exclude<R, ProvidedForSsr>>;
9
+ <E, R>(router: HttpRouter, input: Matcher<RenderEvent, E, R>): Effect.Effect<void, never, Exclude<R, ProvidedForSsr>>;
10
+ };
11
+ export declare function handleHttpServerError(router: HttpRouter): Effect.Effect<void, never, import("effect/unstable/http/HttpRouter").Request<"GlobalError", unknown>>;
12
+ export {};
13
+ //# sourceMappingURL=HttpRouter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HttpRouter.d.ts","sourceRoot":"","sources":["../src/HttpRouter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAKxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,EAAE,KAAK,UAAU,EAA4B,MAAM,iCAAiC,CAAC;AAK5F,OAAO,EAQL,KAAK,OAAO,EAGZ,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEvE,KAAK,cAAc,GAAG,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC;AAE3C,eAAO,MAAM,UAAU,EAAE;IACvB,CAAC,CAAC,EAAE,CAAC,EACH,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,GAChC,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC;IAClF,CAAC,CAAC,EAAE,CAAC,EACH,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,GAChC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC;CAY1D,CAAC;AAEH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,yGAQvD"}
@@ -0,0 +1,99 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Exit from "effect/Exit";
3
+ import { dual } from "effect/Function";
4
+ import * as Layer from "effect/Layer";
5
+ import * as Option from "effect/Option";
6
+ import * as Scope from "effect/Scope";
7
+ import * as ServiceMap from "effect/ServiceMap";
8
+ import { RouteContext } from "effect/unstable/http/HttpRouter";
9
+ import * as HttpServerError from "effect/unstable/http/HttpServerError";
10
+ import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
11
+ import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
12
+ import { RefSubject } from "@typed/fx";
13
+ import { compile, CurrentRoute, makeCatchManager, makeLayerManager, makeLayoutManager, Join, Parse, } from "@typed/router";
14
+ import { initialMemory } from "@typed/navigation";
15
+ import { renderToHtmlString } from "@typed/template";
16
+ export const ssrForHttp = dual(2, (router, input) => {
17
+ return Effect.gen(function* () {
18
+ const matcher = Option.match(yield* Effect.serviceOption(CurrentRoute), {
19
+ onNone: () => input,
20
+ onSome: (parent) => input.prefix(parent.route),
21
+ });
22
+ const entries = compile(matcher.cases);
23
+ const currentServices = yield* Effect.services();
24
+ yield* router.addAll(entries.map((e) => toRoute(e, currentServices)));
25
+ });
26
+ });
27
+ export function handleHttpServerError(router) {
28
+ return router.addGlobalMiddleware(Effect.catch((error) => HttpServerError.isHttpServerError(error)
29
+ ? Effect.succeed(HttpServerResponse.text(error.message, { status: getStatus(error) }))
30
+ : Effect.fail(error)));
31
+ }
32
+ function getStatus(error) {
33
+ switch (error.reason._tag) {
34
+ case "RouteNotFound":
35
+ return 404;
36
+ case "RequestParseError":
37
+ return 400;
38
+ case "InternalError":
39
+ case "ResponseError":
40
+ return 500;
41
+ }
42
+ }
43
+ function toRoute(entry, currentServices) {
44
+ return {
45
+ ["~effect/http/HttpRouter/Route"]: "~effect/http/HttpRouter/Route",
46
+ method: "GET",
47
+ path: entry.route.path,
48
+ handler: Effect.gen(function* () {
49
+ const fiberId = yield* Effect.fiberId;
50
+ const rootScope = yield* Effect.scope;
51
+ const routeContext = yield* RouteContext;
52
+ const request = yield* HttpServerRequest.HttpServerRequest;
53
+ const searchParams = yield* HttpServerRequest.ParsedSearchParams;
54
+ const provided = Layer.mergeAll(initialMemory({ url: request.url }), Layer.succeed(CurrentRoute, yield* Effect.serviceOption(CurrentRoute).pipe(Effect.map(Option.match({
55
+ onNone: () => ({
56
+ route: Parse(request.url),
57
+ parent: undefined,
58
+ }),
59
+ onSome: (parent) => ({
60
+ route: Join(parent.route, Parse(request.url)),
61
+ parent,
62
+ }),
63
+ })))));
64
+ const input = { ...routeContext.params, ...searchParams };
65
+ const params = yield* Effect.mapError(entry.decode(input), (cause) => new HttpServerError.HttpServerError({
66
+ reason: new HttpServerError.RequestParseError({ request, cause }),
67
+ }));
68
+ const memoMap = yield* Layer.makeMemoMap;
69
+ const layerManager = makeLayerManager(memoMap, rootScope, fiberId);
70
+ const layoutManager = makeLayoutManager(rootScope, fiberId);
71
+ const catchManager = makeCatchManager(rootScope, fiberId);
72
+ const prepared = yield* layerManager.prepare(entry.layers.concat(provided));
73
+ const guardExit = yield* entry
74
+ .guard(params)
75
+ .pipe(Effect.provideServices(prepared.services), Effect.exit);
76
+ if (Exit.isFailure(guardExit) || Option.isNone(guardExit.value)) {
77
+ yield* prepared.rollback;
78
+ return yield* new HttpServerError.HttpServerError({
79
+ reason: new HttpServerError.RouteNotFound({ request }),
80
+ });
81
+ }
82
+ const matchedParams = guardExit.value.value;
83
+ yield* prepared.commit;
84
+ const scope = yield* Scope.fork(rootScope);
85
+ const paramsRef = yield* RefSubject.make(matchedParams).pipe(Scope.provide(scope));
86
+ const preparedServices = prepared.services;
87
+ const handlerServices = ServiceMap.merge(ServiceMap.merge(currentServices, preparedServices), ServiceMap.make(Scope.Scope, scope));
88
+ const handlerFx = entry.handler(paramsRef);
89
+ const withLayouts = yield* layoutManager.apply(entry.layouts, matchedParams, handlerFx, preparedServices);
90
+ const withCatches = yield* catchManager.apply(entry.catches, withLayouts, preparedServices);
91
+ const html = yield* renderToHtmlString(withCatches).pipe(Effect.provideServices(handlerServices));
92
+ return HttpServerResponse.text(html, {
93
+ headers: { "content-type": "text/html; charset=utf-8" },
94
+ });
95
+ }),
96
+ uninterruptible: false,
97
+ prefix: undefined,
98
+ };
99
+ }
package/dist/Link.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import * as Effect from "effect/Effect";
2
+ import type { Scope } from "effect/Scope";
3
+ import type { Stream } from "effect/Stream";
4
+ import { type Fx } from "@typed/fx/Fx";
5
+ import { EventHandler, type Renderable, type RenderEvent, type RenderTemplate } from "@typed/template";
6
+ type EventHandlerProperty = `on${string}`;
7
+ type AnchorEventHandlers = {
8
+ readonly [K in keyof HTMLAnchorElement as K extends EventHandlerProperty ? K : never]?: Effect.Effect<unknown, any, any> | EventHandler.EventHandler<Event, any, any>;
9
+ };
10
+ type AnchorRef = {
11
+ readonly ref?: (element: HTMLAnchorElement) => void | Effect.Effect<unknown, any, any> | Stream<unknown, any, any> | Fx<unknown, any, any>;
12
+ };
13
+ type IfEquals<X, Y, Output> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? Output : never;
14
+ type WritableKeys<T> = {
15
+ [P in keyof T]-?: IfEquals<{
16
+ [Q in P]: T[P];
17
+ }, {
18
+ -readonly [Q in P]: T[P];
19
+ }, P>;
20
+ }[keyof T];
21
+ type AnchorProperties = {
22
+ readonly [K in WritableKeys<HTMLAnchorElement> as K extends EventHandlerProperty | "ref" ? never : K]?: Renderable<HTMLAnchorElement[K], any, any>;
23
+ };
24
+ export interface LinkOptions extends AnchorEventHandlers, AnchorRef, AnchorProperties {
25
+ readonly href: Renderable<string, any, any>;
26
+ readonly content: Renderable<string | number | boolean | null | undefined | void | RenderEvent, any, any>;
27
+ readonly replace?: boolean;
28
+ }
29
+ /**
30
+ * Renders an `<a href="...">` that intercepts same-origin, same-document clicks
31
+ * and navigates via `Navigation.navigate` instead of full page load. Requires
32
+ * `Navigation` and `RenderTemplate` in the Effect context (e.g. `BrowserRouter`).
33
+ */
34
+ export declare function Link<const Opts extends LinkOptions>(options: Opts): Fx<RenderEvent, Renderable.ErrorFromObject<Opts>, Renderable.ServicesFromObject<Opts> | Scope | RenderTemplate>;
35
+ export {};
36
+ //# sourceMappingURL=Link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Link.d.ts","sourceRoot":"","sources":["../src/Link.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,EAAO,MAAM,cAAc,CAAC;AAG5C,OAAO,EACL,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAEzB,KAAK,oBAAoB,GAAG,KAAK,MAAM,EAAE,CAAC;AAE1C,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,CAAC,IAAI,MAAM,iBAAiB,IAAI,CAAC,SAAS,oBAAoB,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,EAClF,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAChC,YAAY,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC;CAC/C,CAAC;AAEF,KAAK,SAAS,GAAG;IACf,QAAQ,CAAC,GAAG,CAAC,EAAE,CACb,OAAO,EAAE,iBAAiB,KACvB,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;CAClG,CAAC;AAEF,KAAK,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,IACxB,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC;AAEvF,KAAK,YAAY,CAAC,CAAC,IAAI;KACpB,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC;SAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAAE,EAAE;QAAE,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAAE,EAAE,CAAC,CAAC;CAChF,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX,KAAK,gBAAgB,GAAG;IACtB,QAAQ,EAAE,CAAC,IAAI,YAAY,CAAC,iBAAiB,CAAC,IAAI,CAAC,SAAS,oBAAoB,GAAG,KAAK,GACpF,KAAK,GACL,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC;CACpD,CAAC;AAEF,MAAM,WAAW,WAAY,SAAQ,mBAAmB,EAAE,SAAS,EAAE,gBAAgB;IACnF,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,UAAU,CAC1B,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,GAAG,WAAW,EACjE,GAAG,EACH,GAAG,CACJ,CAAC;IACF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAyBD;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,KAAK,CAAC,IAAI,SAAS,WAAW,EACjD,OAAO,EAAE,IAAI,GACZ,EAAE,CACH,WAAW,EACX,UAAU,CAAC,eAAe,CAAC,IAAI,CAAC,EAChC,UAAU,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,cAAc,CAC7D,CAqBA"}
package/dist/Link.js ADDED
@@ -0,0 +1,45 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { gen } from "@typed/fx/Fx";
3
+ import { RefSubject } from "@typed/fx/RefSubject";
4
+ import { getUrl, Navigation } from "@typed/navigation";
5
+ import { EventHandler, html, } from "@typed/template";
6
+ function makeLinkClickHandler(replace$) {
7
+ return EventHandler.make((ev) => Effect.gen(function* () {
8
+ const href = ev.currentTarget.href;
9
+ if (ev.ctrlKey || ev.metaKey || ev.shiftKey)
10
+ return;
11
+ const t = ev.currentTarget.target;
12
+ if (t && t !== "_self")
13
+ return;
14
+ const nav = yield* Navigation;
15
+ const target = getUrl(nav.origin, href);
16
+ if (target.origin !== nav.origin)
17
+ return;
18
+ ev.preventDefault();
19
+ const replace = yield* replace$;
20
+ yield* nav.navigate(href, { history: replace ? "replace" : "push" });
21
+ }));
22
+ }
23
+ /**
24
+ * Renders an `<a href="...">` that intercepts same-origin, same-document clicks
25
+ * and navigates via `Navigation.navigate` instead of full page load. Requires
26
+ * `Navigation` and `RenderTemplate` in the Effect context (e.g. `BrowserRouter`).
27
+ */
28
+ export function Link(options) {
29
+ return gen(function* () {
30
+ const { replace = false, onclick, content: children, ...rest } = options;
31
+ const replace$ = yield* RefSubject.make(replace);
32
+ const navigationHandler = makeLinkClickHandler(replace$);
33
+ const userHandler = onclick ? EventHandler.fromEffectOrEventHandler(onclick) : undefined;
34
+ const clickHandler = userHandler
35
+ ? EventHandler.make(Effect.fn(function* (ev) {
36
+ yield* userHandler.handler(ev);
37
+ if (ev.defaultPrevented)
38
+ return;
39
+ yield* navigationHandler.handler(ev);
40
+ }), { ...userHandler.options, preventDefault: true })
41
+ : navigationHandler;
42
+ const props = { ...rest, onclick: clickHandler };
43
+ return html `<a ...${props}>${children}</a>`;
44
+ });
45
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./HttpRouter.js";
2
+ export * from "./Link.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./HttpRouter.js";
2
+ export * from "./Link.js";
package/package.json CHANGED
@@ -1,90 +1,40 @@
1
1
  {
2
2
  "name": "@typed/ui",
3
- "version": "0.14.0",
4
- "description": "",
5
- "license": "MIT",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/tylors/typed.git"
9
- },
10
- "sideEffects": [],
11
- "author": "Typed contributors",
12
- "homepage": "https://github.com/tylors/typed",
13
- "dependencies": {
14
- "@effect/platform": "^0.66.2",
15
- "@effect/schema": "^0.74.1",
16
- "csstype": "^3.1.3",
17
- "effect": "^3.8.4",
18
- "@typed/context": "0.30.0",
19
- "@typed/dom": "18.0.0",
20
- "@typed/environment": "0.11.0",
21
- "@typed/fx": "1.32.0",
22
- "@typed/navigation": "0.18.0",
23
- "@typed/route": "9.0.0",
24
- "@typed/router": "0.32.0",
25
- "@typed/template": "0.14.0"
26
- },
27
- "main": "./dist/cjs/index.js",
28
- "module": "./dist/esm/index.js",
29
- "types": "./dist/dts/index.d.ts",
3
+ "version": "1.0.0-beta.1",
4
+ "type": "module",
30
5
  "exports": {
31
- "./package.json": "./package.json",
32
6
  ".": {
33
- "types": "./dist/dts/index.d.ts",
34
- "import": "./dist/esm/index.js",
35
- "default": "./dist/cjs/index.js"
36
- },
37
- "./Link": {
38
- "types": "./dist/dts/Link.d.ts",
39
- "import": "./dist/esm/Link.js",
40
- "default": "./dist/cjs/Link.js"
41
- },
42
- "./Props": {
43
- "types": "./dist/dts/Props.d.ts",
44
- "import": "./dist/esm/Props.js",
45
- "default": "./dist/cjs/Props.js"
46
- },
47
- "./dom-properties": {
48
- "types": "./dist/dts/dom-properties.d.ts",
49
- "import": "./dist/esm/dom-properties.js",
50
- "default": "./dist/cjs/dom-properties.js"
51
- },
52
- "./hyperscript": {
53
- "types": "./dist/dts/hyperscript.d.ts",
54
- "import": "./dist/esm/hyperscript.js",
55
- "default": "./dist/cjs/hyperscript.js"
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.js"
56
9
  },
57
- "./useClickAway": {
58
- "types": "./dist/dts/useClickAway.d.ts",
59
- "import": "./dist/esm/useClickAway.js",
60
- "default": "./dist/cjs/useClickAway.js"
61
- },
62
- "./usePagination": {
63
- "types": "./dist/dts/usePagination.d.ts",
64
- "import": "./dist/esm/usePagination.js",
65
- "default": "./dist/cjs/usePagination.js"
10
+ "./*": {
11
+ "types": "./dist/*.d.ts",
12
+ "import": "./dist/*.js"
66
13
  }
67
14
  },
68
- "typesVersions": {
69
- "*": {
70
- "Link": [
71
- "./dist/dts/Link.d.ts"
72
- ],
73
- "Props": [
74
- "./dist/dts/Props.d.ts"
75
- ],
76
- "dom-properties": [
77
- "./dist/dts/dom-properties.d.ts"
78
- ],
79
- "hyperscript": [
80
- "./dist/dts/hyperscript.d.ts"
81
- ],
82
- "useClickAway": [
83
- "./dist/dts/useClickAway.d.ts"
84
- ],
85
- "usePagination": [
86
- "./dist/dts/usePagination.d.ts"
87
- ]
88
- }
89
- }
90
- }
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "[ -d dist ] || rm -f tsconfig.tsbuildinfo; tsc",
20
+ "test": "vitest run --passWithNoTests"
21
+ },
22
+ "dependencies": {
23
+ "@effect/platform-node": "catalog:",
24
+ "@typed/fx": "workspace:*",
25
+ "@typed/id": "workspace:*",
26
+ "@typed/navigation": "workspace:*",
27
+ "@typed/router": "workspace:*",
28
+ "@typed/template": "workspace:*",
29
+ "effect": "catalog:",
30
+ "happy-dom": "catalog:"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "catalog:",
34
+ "vitest": "catalog:"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "src"
39
+ ]
40
+ }