@typed/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Anchor.js","sourceRoot":"","sources":["../../src/Anchor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import * as Fx from "@typed/fx/Fx";
2
+ import * as RefSubject from "@typed/fx/RefSubject";
3
+ import * as Navigation from "@typed/navigation";
4
+ import { makeHref } from "@typed/router";
5
+ import * as EventHandler from "@typed/template/EventHandler";
6
+ import { Placeholder } from "@typed/template/Placeholder";
7
+ import { Effect } from "effect";
8
+ import { Anchor, getEventHandler } from "./Anchor";
9
+ /**
10
+ * @since 1.0.0
11
+ */
12
+ export function Link(props, ...children) {
13
+ return Fx.gen(function* (_) {
14
+ const onClickHandler = getEventHandler(props.onClick);
15
+ const to = yield* _(Placeholder.asRef(props.to));
16
+ const relative = yield* _(Placeholder.asRef(props.relative ?? true));
17
+ const replace = yield* _(Placeholder.asRef(props.replace ?? false));
18
+ const state = yield* _(Placeholder.asRef(props.state));
19
+ const reloadDocument = yield* _(Placeholder.asRef(props.reloadDocument ?? false));
20
+ const href = RefSubject.tuple(relative, to).mapEffect(([rel, to]) => rel ? makeHref(to) : Effect.succeed(to));
21
+ const navigate = Effect.gen(function* (_) {
22
+ const current = yield* _(Effect.all({ replace, state, reloadDocument }));
23
+ const url = yield* _(href);
24
+ yield* _(Navigation.navigate(url, {
25
+ history: current.replace ? "replace" : "auto",
26
+ state: current.state
27
+ }));
28
+ if (current.reloadDocument) {
29
+ yield* _(Navigation.reload({ state: current.state }));
30
+ }
31
+ });
32
+ const onClick = EventHandler.preventDefault((ev) => Effect.gen(function* (_) {
33
+ if (onClickHandler) {
34
+ yield* _(onClickHandler.handler(ev));
35
+ }
36
+ yield* _(navigate);
37
+ }), onClickHandler?.options);
38
+ return Anchor({ ...props, href, state, onClick }, ...children);
39
+ });
40
+ }
41
+ //# sourceMappingURL=Link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Link.js","sourceRoot":"","sources":["../../src/Link.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,cAAc,CAAA;AAClC,OAAO,KAAK,UAAU,MAAM,sBAAsB,CAAA;AAClD,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,KAAK,YAAY,MAAM,8BAA8B,CAAA;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAIzD,OAAO,EAAE,MAAM,EAAc,MAAM,QAAQ,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAoB,eAAe,EAAE,MAAM,UAAU,CAAA;AAapE;;GAEG;AACH,MAAM,UAAU,IAAI,CAClB,KAAY,EACZ,GAAG,QAAkB;IAWrB,OAAO,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAC,CAAC;QACvB,MAAM,cAAc,GAAG,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACrD,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CACjB,WAAW,CAAC,KAAK,CAA2E,KAAK,CAAC,EAAE,CAAC,CACtG,CAAA;QACD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CACvB,WAAW,CAAC,KAAK,CACf,KAAK,CAAC,QAAQ,IAAI,IAAI,CACvB,CACF,CAAA;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CACtB,WAAW,CAAC,KAAK,CACf,KAAK,CAAC,OAAO,IAAI,KAAK,CACvB,CACF,CAAA;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CACpB,WAAW,CAAC,KAAK,CACf,KAAK,CAAC,KAAiC,CACxC,CACF,CAAA;QACD,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,CAAC,CAC7B,WAAW,CAAC,KAAK,CAKf,KAAK,CAAC,cAAc,IAAI,KAAK,CAC9B,CACF,CAAA;QAED,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAA;QAE7G,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAC,CAAC;YACrC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,CAAA;YACxE,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;YAE1B,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,EAAE;gBAChC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;gBAC7C,KAAK,EAAE,OAAO,CAAC,KAAK;aACrB,CAAC,CAAC,CAAA;YAEH,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;gBAC3B,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;YACvD,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,MAAM,OAAO,GAAG,YAAY,CAAC,cAAc,CACzC,CAAC,EAAyD,EAAE,EAAE,CAC5D,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAC,CAAC;YACpB,IAAI,cAAc,EAAE,CAAC;gBACnB,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAA;YACtC,CAAC;YACD,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;QACpB,CAAC,CAAC,EACJ,cAAc,EAAE,OAAO,CACxB,CAAA;QAED,OAAO,MAAM,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,QAAQ,CAAC,CAAA;IAChE,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ /**
5
+ * @since 1.0.0
6
+ */
7
+ export * from "./Anchor";
8
+ /**
9
+ * @since 1.0.0
10
+ */
11
+ export * from "./Link";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,cAAc,UAAU,CAAA;AACxB;;GAEG;AACH,cAAc,QAAQ,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dom-properties.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom-properties.js","sourceRoot":"","sources":["../../../src/internal/dom-properties.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ {
2
+ "type": "module",
3
+ "sideEffects": []
4
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@typed/ui",
3
+ "version": "0.1.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
+ "dependencies": {
13
+ "@effect/platform": "^0.31.1",
14
+ "@effect/schema": "0.49.4",
15
+ "csstype": "^3.1.2",
16
+ "effect": "2.0.0-next.56",
17
+ "@typed/context": "0.1.0",
18
+ "@typed/dom": "0.1.0",
19
+ "@typed/environment": "0.1.0",
20
+ "@typed/navigation": "0.1.0",
21
+ "@typed/fx": "1.18.0",
22
+ "@typed/route": "1.0.0",
23
+ "@typed/router": "0.19.0",
24
+ "@typed/template": "0.1.0"
25
+ },
26
+ "main": "./dist/cjs/index.js",
27
+ "module": "./dist/esm/index.js",
28
+ "types": "./dist/dts/index.d.ts",
29
+ "exports": {
30
+ "./package.json": "./package.json",
31
+ ".": {
32
+ "types": "./dist/dts/index.d.ts",
33
+ "import": "./dist/esm/index.js",
34
+ "default": "./dist/cjs/index.js"
35
+ },
36
+ "./Anchor": {
37
+ "types": "./dist/dts/Anchor.d.ts",
38
+ "import": "./dist/esm/Anchor.js",
39
+ "default": "./dist/cjs/Anchor.js"
40
+ },
41
+ "./Link": {
42
+ "types": "./dist/dts/Link.d.ts",
43
+ "import": "./dist/esm/Link.js",
44
+ "default": "./dist/cjs/Link.js"
45
+ }
46
+ },
47
+ "typesVersions": {
48
+ "*": {
49
+ "Anchor": [
50
+ "./dist/dts/Anchor.d.ts"
51
+ ],
52
+ "Link": [
53
+ "./dist/dts/Link.d.ts"
54
+ ]
55
+ }
56
+ }
57
+ }
package/src/Anchor.ts ADDED
@@ -0,0 +1,210 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+
5
+ import type { EventWithCurrentTarget } from "@typed/dom/EventTarget"
6
+ import * as Fx from "@typed/fx/Fx"
7
+ import * as Directive from "@typed/template/Directive"
8
+ import type { DefaultEventMap, ElementSource } from "@typed/template/ElementSource"
9
+ import * as EventHandler from "@typed/template/EventHandler"
10
+ import type { Placeholder } from "@typed/template/Placeholder"
11
+ import type { Renderable } from "@typed/template/Renderable"
12
+ import type { TemplateFx } from "@typed/template/RenderTemplate"
13
+ import { html } from "@typed/template/RenderTemplate"
14
+ import type { Rendered } from "@typed/wire"
15
+ import type { ReadonlyRecord, Scope } from "effect"
16
+ import { Effect } from "effect"
17
+ import { uncapitalize } from "effect/String"
18
+ import type { HTMLAnchorElementProperties } from "./internal/dom-properties"
19
+
20
+ /**
21
+ * @since 1.0.0
22
+ */
23
+ export type AnchorProps =
24
+ & {
25
+ readonly [K in keyof HTMLAnchorElementProperties]:
26
+ | HTMLAnchorElementProperties[K]
27
+ | Placeholder.Any<HTMLAnchorElementProperties[K]>
28
+ | Directive.Directive<any, any>
29
+ }
30
+ & {
31
+ readonly ref?: (ref: ElementSource<HTMLAnchorElement>) => Effect.Effect<any, any, any>
32
+ readonly data?: Placeholder.Any<ReadonlyRecord.ReadonlyRecord<any>>
33
+ }
34
+ & EventHandlerProps<HTMLAnchorElement>
35
+
36
+ /**
37
+ * @since 1.0.0
38
+ */
39
+ export type EventHandlerProps<El extends HTMLElement | SVGElement, EventMap extends {} = DefaultEventMap<El>> = {
40
+ readonly [K in keyof EventMap as K extends string ? `on${Capitalize<K>}` : never]?:
41
+ | EventHandler.EventHandler<
42
+ any,
43
+ any,
44
+ EventWithCurrentTarget<El, Extract<EventMap[K], Event>>
45
+ >
46
+ | Effect.Effect<any, any, unknown>
47
+ | null
48
+ | undefined
49
+ }
50
+ type ReturnOf<T> = T extends (...args: any) => infer R ? R : never
51
+
52
+ /**
53
+ * @since 1.0.0
54
+ */
55
+ export function Anchor<
56
+ const Props extends AnchorProps,
57
+ Children extends ReadonlyArray<Renderable<any, any>> = readonly []
58
+ >(
59
+ props: Props,
60
+ ...children: Children
61
+ ): TemplateFx<
62
+ Placeholder.Context<Props[keyof Props] | ReturnOf<Props["ref"]> | Children[number]>,
63
+ Placeholder.Error<Props[keyof Props] | ReturnOf<Props["ref"]> | Children[number]>,
64
+ HTMLAnchorElement
65
+ > {
66
+ const ref = Directive.ref(({ value: ref }) =>
67
+ Effect.gen(function*(_) {
68
+ yield* _(addEventListeners(props, ref))
69
+
70
+ if (props.ref) {
71
+ yield* _(props.ref(ref as any))
72
+ }
73
+ })
74
+ )
75
+ return html`<a
76
+ ref="${ref}"
77
+ .data="${props.data}"
78
+ ?hidden="${props.hidden}"
79
+ ?hidefocus="${props.hideFocus}"
80
+ ?spellcheck="${props.spellcheck}"
81
+ .scrollLeft="${props.scrollLeft}"
82
+ .scrollTop="${props.scrollTop}"
83
+ accesskey="${props.accessKey}"
84
+ charset="${props.charset}"
85
+ class="${props.className}"
86
+ contenteditable="${props.contentEditable}"
87
+ coords="${props.coords}"
88
+ dir="${props.dir}"
89
+ download="${props.download}"
90
+ draggable="${props.draggable}"
91
+ .hash="${props.hash}"
92
+ .host="${props.host}"
93
+ .hostname="${props.hostname}"
94
+ .href="${props.href}"
95
+ hreflang="${props.hreflang}"
96
+ id="${props.id}"
97
+ id="${props.id}"
98
+ lang="${props.lang}"
99
+ Methods="${props.Methods}"
100
+ name="${props.name}"
101
+ .pathname="${props.pathname}"
102
+ .port="${props.port}"
103
+ .protocol="${props.protocol}"
104
+ rel="${props.rel}"
105
+ rev="${props.rev}"
106
+ .search="${props.search}"
107
+ shape="${props.shape}"
108
+ slot="${props.slot}"
109
+ tabindex="${props.tabIndex}"
110
+ target="${props.target}"
111
+ text="${props.text}"
112
+ title="${props.title}"
113
+ type="${props.type}"
114
+ urn="${props.urn}"
115
+ >${children}</a>` as any
116
+ }
117
+
118
+ /**
119
+ * @since 1.0.0
120
+ */
121
+ export function addEventListeners<Props extends EventHandlerProps<any>, T extends Rendered>(
122
+ props: Props,
123
+ ref: ElementSource<T>
124
+ ): Effect.Effect<Scope.Scope | GetEventHandlersContext<Props>, never, void> {
125
+ return Fx.forkScoped(
126
+ Fx.merge(
127
+ getEventHandlers(props).map(([type, handler]: any) => addEventListener(ref, type, handler))
128
+ )
129
+ ) as any
130
+ }
131
+
132
+ function addEventListener<T extends Rendered, R, E, Ev extends Event>(
133
+ ref: ElementSource<T>,
134
+ event: string,
135
+ handler: EventHandler.EventHandler<R, E, Ev>
136
+ ) {
137
+ return Fx.mapEffect(ref.events(event as any, handler.options), (ev) => handler.handler(ev as any))
138
+ }
139
+
140
+ type ValuesOf<T> = [T[keyof T]] extends [infer R] ? R : never
141
+
142
+ type ToEventType<T> = T extends `on${infer S}` ? Uncapitalize<S> : never
143
+
144
+ /**
145
+ * @since 1.0.0
146
+ */
147
+ export type GetEventHandlersContext<T extends EventHandlerProps<any>> = ValuesOf<
148
+ {
149
+ readonly [K in keyof T as K extends `on${string}` ? K : never]: EventHandler.Context<GetEventHandler<T[K]>>
150
+ }
151
+ >
152
+
153
+ /**
154
+ * @since 1.0.0
155
+ */
156
+ export type GetEventHandlers<T extends EventHandlerProps<any>> = [
157
+ ReadonlyArray<
158
+ ValuesOf<
159
+ {
160
+ readonly [K in keyof T as K extends `on${string}` ? K : never]: readonly [ToEventType<K>, GetEventHandler<T[K]>]
161
+ }
162
+ >
163
+ >
164
+ ] extends [ReadonlyArray<infer R>] ? ReadonlyArray<R> : never
165
+
166
+ type GetEventHandler<
167
+ T
168
+ > = T extends EventHandler.EventHandler<infer R, infer E, infer Ev> ? EventHandler.EventHandler<R, E, Ev>
169
+ : T extends Effect.Effect<infer R, infer E, infer _> ? EventHandler.EventHandler<R, E, Event>
170
+ : never
171
+
172
+ /**
173
+ * @since 1.0.0
174
+ */
175
+ export function getEventHandlers<Props extends EventHandlerProps<any>>(props: Props) {
176
+ const eventHandlers: Array<readonly [string, EventHandler.EventHandler<any, any, any>]> = Object.keys(props).filter((
177
+ x
178
+ ) => x[0] === "o" && x[1] === "n").flatMap((key) => {
179
+ const handler = getEventHandler(
180
+ props[key as keyof typeof props] as
181
+ | EventHandler.EventHandler<any, any, any>
182
+ | Effect.Effect<any, any, unknown>
183
+ | null
184
+ | undefined
185
+ )
186
+
187
+ if (!handler) return []
188
+
189
+ const eventType = uncapitalize(key.slice(2))
190
+
191
+ return [[eventType, handler] as const]
192
+ })
193
+
194
+ return eventHandlers as any as GetEventHandlers<Props>
195
+ }
196
+
197
+ /**
198
+ * @since 1.0.0
199
+ */
200
+ export function getEventHandler<R, E, Ev extends Event = Event>(
201
+ handler: EventHandler.EventHandler<R, E, Ev> | Effect.Effect<R, E, unknown> | null | undefined
202
+ ): EventHandler.EventHandler<R, E, Ev> | null {
203
+ if (!handler) return null
204
+
205
+ if (Effect.isEffect(handler)) {
206
+ return EventHandler.make(() => handler)
207
+ } else {
208
+ return handler
209
+ }
210
+ }
package/src/Link.ts ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import type { EventWithCurrentTarget } from "@typed/dom/EventTarget"
5
+ import * as Fx from "@typed/fx/Fx"
6
+ import * as RefSubject from "@typed/fx/RefSubject"
7
+ import * as Navigation from "@typed/navigation"
8
+ import type { CurrentRoute } from "@typed/router"
9
+ import { makeHref } from "@typed/router"
10
+ import * as EventHandler from "@typed/template/EventHandler"
11
+ import { Placeholder } from "@typed/template/Placeholder"
12
+ import type { Renderable } from "@typed/template/Renderable"
13
+ import type { RenderEvent } from "@typed/template/RenderEvent"
14
+ import type { RenderTemplate } from "@typed/template/RenderTemplate"
15
+ import { Effect, type Scope } from "effect"
16
+ import { Anchor, type AnchorProps, getEventHandler } from "./Anchor"
17
+
18
+ /**
19
+ * @since 1.0.0
20
+ */
21
+ export type LinkProps = Omit<AnchorProps, keyof URL> & {
22
+ readonly to: string | Placeholder.Any<string>
23
+ readonly relative?: boolean | Placeholder.Any<boolean>
24
+ readonly replace?: boolean | Placeholder.Any<boolean>
25
+ readonly state?: unknown | Placeholder.Any<unknown>
26
+ readonly reloadDocument?: boolean | Placeholder.Any<boolean>
27
+ }
28
+
29
+ /**
30
+ * @since 1.0.0
31
+ */
32
+ export function Link<Props extends LinkProps, Children extends ReadonlyArray<Renderable<any, any>> = readonly []>(
33
+ props: Props,
34
+ ...children: Children
35
+ ): Fx.Fx<
36
+ | Navigation.Navigation
37
+ | CurrentRoute
38
+ | RenderTemplate
39
+ | Scope.Scope
40
+ | Location
41
+ | Placeholder.Context<Props[keyof Props] | Children[number]>,
42
+ Placeholder.Error<Props[keyof Props] | Children[number]>,
43
+ RenderEvent
44
+ > {
45
+ return Fx.gen(function*(_) {
46
+ const onClickHandler = getEventHandler(props.onClick)
47
+ const to = yield* _(
48
+ Placeholder.asRef<Placeholder.Context<Props["to"]>, Placeholder.Error<Props["to"]>, string>(props.to)
49
+ )
50
+ const relative = yield* _(
51
+ Placeholder.asRef<Placeholder.Context<Props["relative"]>, Placeholder.Error<Props["relative"]>, boolean>(
52
+ props.relative ?? true
53
+ )
54
+ )
55
+ const replace = yield* _(
56
+ Placeholder.asRef<Placeholder.Context<Props["replace"]>, Placeholder.Error<Props["replace"]>, boolean>(
57
+ props.replace ?? false
58
+ )
59
+ )
60
+ const state = yield* _(
61
+ Placeholder.asRef<Placeholder.Context<Props["state"]>, Placeholder.Error<Props["state"]>, unknown>(
62
+ props.state as Placeholder.Any<unknown>
63
+ )
64
+ )
65
+ const reloadDocument = yield* _(
66
+ Placeholder.asRef<
67
+ Placeholder.Context<Props["reloadDocument"]>,
68
+ Placeholder.Error<Props["reloadDocument"]>,
69
+ boolean
70
+ >(
71
+ props.reloadDocument ?? false
72
+ )
73
+ )
74
+
75
+ const href = RefSubject.tuple(relative, to).mapEffect(([rel, to]) => rel ? makeHref(to) : Effect.succeed(to))
76
+
77
+ const navigate = Effect.gen(function*(_) {
78
+ const current = yield* _(Effect.all({ replace, state, reloadDocument }))
79
+ const url = yield* _(href)
80
+
81
+ yield* _(Navigation.navigate(url, {
82
+ history: current.replace ? "replace" : "auto",
83
+ state: current.state
84
+ }))
85
+
86
+ if (current.reloadDocument) {
87
+ yield* _(Navigation.reload({ state: current.state }))
88
+ }
89
+ })
90
+
91
+ const onClick = EventHandler.preventDefault(
92
+ (ev: EventWithCurrentTarget<HTMLAnchorElement, MouseEvent>) =>
93
+ Effect.gen(function*(_) {
94
+ if (onClickHandler) {
95
+ yield* _(onClickHandler.handler(ev))
96
+ }
97
+ yield* _(navigate)
98
+ }),
99
+ onClickHandler?.options
100
+ )
101
+
102
+ return Anchor({ ...props, href, state, onClick }, ...children)
103
+ })
104
+ }
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+
5
+ /**
6
+ * @since 1.0.0
7
+ */
8
+ export * from "./Anchor"
9
+ /**
10
+ * @since 1.0.0
11
+ */
12
+ export * from "./Link"