@alepha/react 0.13.1 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/index.browser.js +5 -5
- package/dist/auth/index.browser.js.map +1 -1
- package/dist/auth/index.d.ts +330 -330
- package/dist/auth/index.js +7 -7
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.browser.js +19 -18
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +352 -344
- package/dist/core/index.js +25 -24
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +381 -0
- package/dist/core/index.native.js.map +1 -0
- package/dist/form/index.d.ts +2 -2
- package/dist/head/index.browser.js +7 -7
- package/dist/head/index.browser.js.map +1 -1
- package/dist/head/index.d.ts +265 -265
- package/dist/head/index.js +7 -7
- package/dist/head/index.js.map +1 -1
- package/dist/i18n/index.d.ts +20 -20
- package/dist/i18n/index.js +12 -12
- package/dist/i18n/index.js.map +1 -1
- package/dist/websocket/index.d.ts +7 -7
- package/dist/websocket/index.js.map +1 -1
- package/package.json +18 -9
- package/src/auth/index.ts +1 -1
- package/src/auth/providers/ReactAuthProvider.ts +1 -1
- package/src/auth/services/ReactAuth.ts +5 -5
- package/src/core/components/NestedView.tsx +1 -1
- package/src/core/hooks/useStore.ts +4 -4
- package/src/core/index.browser.ts +2 -2
- package/src/core/index.native.ts +1 -1
- package/src/core/index.shared-router.ts +1 -1
- package/src/core/index.ts +3 -3
- package/src/core/{descriptors → primitives}/$page.ts +20 -20
- package/src/core/providers/ReactBrowserProvider.ts +2 -2
- package/src/core/providers/ReactBrowserRouterProvider.ts +2 -2
- package/src/core/providers/ReactPageProvider.ts +25 -11
- package/src/core/providers/ReactServerProvider.ts +12 -12
- package/src/core/services/ReactPageServerService.ts +6 -6
- package/src/core/services/ReactPageService.ts +6 -6
- package/src/core/services/ReactRouter.ts +3 -3
- package/src/head/index.browser.ts +3 -3
- package/src/head/index.ts +4 -4
- package/src/head/{descriptors → primitives}/$head.ts +6 -6
- package/src/i18n/hooks/useI18n.ts +2 -2
- package/src/i18n/index.ts +3 -3
- package/src/i18n/{descriptors → primitives}/$dictionary.ts +8 -8
- package/src/i18n/providers/I18nProvider.ts +5 -5
- package/src/websocket/hooks/useRoom.tsx +3 -3
package/dist/head/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AlephaReact, useInject } from "@alepha/react";
|
|
2
|
-
import { $hook, $inject, $module, Alepha,
|
|
2
|
+
import { $hook, $inject, $module, Alepha, KIND, Primitive, createPrimitive } from "alepha";
|
|
3
3
|
import { ServerTimingProvider } from "alepha/server";
|
|
4
4
|
import { useCallback, useEffect, useMemo } from "react";
|
|
5
5
|
|
|
@@ -40,20 +40,20 @@ var HeadProvider = class {
|
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
//#endregion
|
|
43
|
-
//#region src/head/
|
|
43
|
+
//#region src/head/primitives/$head.ts
|
|
44
44
|
/**
|
|
45
45
|
* Set global `<head>` options for the application.
|
|
46
46
|
*/
|
|
47
47
|
const $head = (options) => {
|
|
48
|
-
return
|
|
48
|
+
return createPrimitive(HeadPrimitive, options);
|
|
49
49
|
};
|
|
50
|
-
var
|
|
50
|
+
var HeadPrimitive = class extends Primitive {
|
|
51
51
|
provider = $inject(HeadProvider);
|
|
52
52
|
onInit() {
|
|
53
53
|
this.provider.global = this.options;
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
|
-
$head[KIND] =
|
|
56
|
+
$head[KIND] = HeadPrimitive;
|
|
57
57
|
|
|
58
58
|
//#endregion
|
|
59
59
|
//#region src/head/providers/ServerHeadProvider.ts
|
|
@@ -221,10 +221,10 @@ const useHead = (options) => {
|
|
|
221
221
|
*/
|
|
222
222
|
const AlephaReactHead = $module({
|
|
223
223
|
name: "alepha.react.head",
|
|
224
|
-
|
|
224
|
+
primitives: [$head],
|
|
225
225
|
services: [AlephaReact, ServerHeadProvider]
|
|
226
226
|
});
|
|
227
227
|
|
|
228
228
|
//#endregion
|
|
229
|
-
export { $head, AlephaReactHead,
|
|
229
|
+
export { $head, AlephaReactHead, HeadPrimitive, ServerHeadProvider, useHead };
|
|
230
230
|
//# sourceMappingURL=index.js.map
|
package/dist/head/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["attrs: Record<string, string>","match: RegExpExecArray | null","attrs: Record<string, string>","metas: { name: string; content: string }[]"],"sources":["../../src/head/providers/HeadProvider.ts","../../src/head/descriptors/$head.ts","../../src/head/providers/ServerHeadProvider.ts","../../src/head/providers/BrowserHeadProvider.ts","../../src/head/hooks/useHead.ts","../../src/head/index.ts"],"sourcesContent":["import type { PageRoute, ReactRouterState } from \"@alepha/react\";\nimport type { Head } from \"../interfaces/Head.ts\";\n\nexport class HeadProvider {\n public global?: Head | (() => Head);\n\n protected getGlobalHead(): Head | undefined {\n if (typeof this.global === \"function\") {\n return this.global();\n }\n return this.global;\n }\n\n public fillHead(state: ReactRouterState) {\n state.head = {\n ...state.head,\n ...this.getGlobalHead(),\n };\n\n for (const layer of state.layers) {\n if (layer.route?.head && !layer.error) {\n this.fillHeadByPage(layer.route, state, layer.props ?? {});\n }\n }\n }\n\n protected fillHeadByPage(\n page: PageRoute,\n state: ReactRouterState,\n props: Record<string, any>,\n ): void {\n if (!page.head) {\n return;\n }\n\n state.head ??= {};\n\n const head =\n typeof page.head === \"function\"\n ? page.head(props, state.head)\n : page.head;\n\n if (head.title) {\n state.head ??= {};\n\n if (state.head.titleSeparator) {\n state.head.title = `${head.title}${state.head.titleSeparator}${state.head.title}`;\n } else {\n state.head.title = head.title;\n }\n\n state.head.titleSeparator = head.titleSeparator;\n }\n\n if (head.htmlAttributes) {\n state.head.htmlAttributes = {\n ...state.head.htmlAttributes,\n ...head.htmlAttributes,\n };\n }\n\n if (head.bodyAttributes) {\n state.head.bodyAttributes = {\n ...state.head.bodyAttributes,\n ...head.bodyAttributes,\n };\n }\n\n if (head.meta) {\n state.head.meta = [...(state.head.meta ?? []), ...(head.meta ?? [])];\n }\n }\n}\n","import { $inject, createDescriptor, Descriptor, KIND } from \"alepha\";\nimport type { Head } from \"../interfaces/Head.ts\";\nimport { HeadProvider } from \"../providers/HeadProvider.ts\";\n\n/**\n * Set global `<head>` options for the application.\n */\nexport const $head = (options: HeadDescriptorOptions) => {\n return createDescriptor(HeadDescriptor, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type HeadDescriptorOptions = Head | (() => Head);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class HeadDescriptor extends Descriptor<HeadDescriptorOptions> {\n protected readonly provider = $inject(HeadProvider);\n protected onInit() {\n this.provider.global = this.options;\n }\n}\n\n$head[KIND] = HeadDescriptor;\n","import { $hook, $inject } from \"alepha\";\nimport { ServerTimingProvider } from \"alepha/server\";\nimport type { SimpleHead } from \"../interfaces/Head.ts\";\nimport { HeadProvider } from \"./HeadProvider.ts\";\n\nexport class ServerHeadProvider {\n protected readonly headProvider = $inject(HeadProvider);\n protected readonly serverTimingProvider = $inject(ServerTimingProvider);\n\n protected readonly onServerRenderEnd = $hook({\n on: \"react:server:render:end\",\n handler: async (ev) => {\n this.serverTimingProvider.beginTiming(\"renderHead\");\n this.headProvider.fillHead(ev.state);\n if (ev.state.head) {\n ev.html = this.renderHead(ev.html, ev.state.head);\n }\n this.serverTimingProvider.endTiming(\"renderHead\");\n },\n });\n\n public renderHead(template: string, head: SimpleHead): string {\n let result = template;\n\n // Inject htmlAttributes\n const htmlAttributes = head.htmlAttributes;\n if (htmlAttributes) {\n result = result.replace(\n /<html([^>]*)>/i,\n (_, existingAttrs) =>\n `<html${this.mergeAttributes(existingAttrs, htmlAttributes)}>`,\n );\n }\n\n // Inject bodyAttributes\n const bodyAttributes = head.bodyAttributes;\n if (bodyAttributes) {\n result = result.replace(\n /<body([^>]*)>/i,\n (_, existingAttrs) =>\n `<body${this.mergeAttributes(existingAttrs, bodyAttributes)}>`,\n );\n }\n\n // Build head content\n let headContent = \"\";\n const title = head.title;\n if (title) {\n if (template.includes(\"<title>\")) {\n result = result.replace(\n /<title>(.*?)<\\/title>/i,\n () => `<title>${this.escapeHtml(title)}</title>`,\n );\n } else {\n headContent += `<title>${this.escapeHtml(title)}</title>\\n`;\n }\n }\n\n if (head.meta) {\n for (const meta of head.meta) {\n headContent += `<meta name=\"${this.escapeHtml(meta.name)}\" content=\"${this.escapeHtml(meta.content)}\">\\n`;\n }\n }\n\n // Inject into <head>...</head>\n result = result.replace(\n /<head([^>]*)>(.*?)<\\/head>/is,\n (_, existingAttrs, existingHead) =>\n `<head${existingAttrs}>${existingHead}${headContent}</head>`,\n );\n\n return result.trim();\n }\n\n protected mergeAttributes(\n existing: string,\n attrs: Record<string, string>,\n ): string {\n const existingAttrs = this.parseAttributes(existing);\n const merged = { ...existingAttrs, ...attrs };\n return Object.entries(merged)\n .map(([k, v]) => ` ${k}=\"${this.escapeHtml(v)}\"`)\n .join(\"\");\n }\n\n protected parseAttributes(attrStr: string): Record<string, string> {\n attrStr = attrStr.replaceAll(\"'\", '\"');\n\n const attrs: Record<string, string> = {};\n const attrRegex = /([^\\s=]+)(?:=\"([^\"]*)\")?/g;\n let match: RegExpExecArray | null = attrRegex.exec(attrStr);\n\n while (match) {\n attrs[match[1]] = match[2] ?? \"\";\n match = attrRegex.exec(attrStr);\n }\n\n return attrs;\n }\n\n protected escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n }\n}\n","import { $hook, $inject } from \"alepha\";\nimport type { Head } from \"../interfaces/Head.ts\";\nimport { HeadProvider } from \"./HeadProvider.ts\";\n\nexport class BrowserHeadProvider {\n protected readonly headProvider = $inject(HeadProvider);\n\n protected get document(): Document {\n return window.document;\n }\n\n protected readonly onBrowserRender = $hook({\n on: \"react:browser:render\",\n handler: async ({ state }) => {\n this.headProvider.fillHead(state);\n if (state.head) {\n this.renderHead(this.document, state.head);\n }\n },\n });\n\n protected readonly onTransitionEnd = $hook({\n on: \"react:transition:end\",\n handler: async ({ state }) => {\n this.headProvider.fillHead(state);\n if (state.head) {\n this.renderHead(this.document, state.head);\n }\n },\n });\n\n public getHead(document: Document): Head {\n return {\n get title() {\n return document.title;\n },\n get htmlAttributes() {\n const attrs: Record<string, string> = {};\n for (const attr of document.documentElement.attributes) {\n attrs[attr.name] = attr.value;\n }\n return attrs;\n },\n get bodyAttributes() {\n const attrs: Record<string, string> = {};\n for (const attr of document.body.attributes) {\n attrs[attr.name] = attr.value;\n }\n return attrs;\n },\n get meta() {\n const metas: { name: string; content: string }[] = [];\n for (const meta of document.head.querySelectorAll(\"meta[name]\")) {\n const name = meta.getAttribute(\"name\");\n const content = meta.getAttribute(\"content\");\n if (name && content) {\n metas.push({ name, content });\n }\n }\n return metas;\n },\n };\n }\n\n public renderHead(document: Document, head: Head): void {\n if (head.title) {\n document.title = head.title;\n }\n\n if (head.bodyAttributes) {\n for (const [key, value] of Object.entries(head.bodyAttributes)) {\n if (value) {\n document.body.setAttribute(key, value);\n } else {\n document.body.removeAttribute(key);\n }\n }\n }\n\n if (head.htmlAttributes) {\n for (const [key, value] of Object.entries(head.htmlAttributes)) {\n if (value) {\n document.documentElement.setAttribute(key, value);\n } else {\n document.documentElement.removeAttribute(key);\n }\n }\n }\n\n if (head.meta) {\n for (const it of head.meta) {\n const { name, content } = it;\n const meta = document.querySelector(`meta[name=\"${name}\"]`);\n if (meta) {\n meta.setAttribute(\"content\", content);\n } else {\n const newMeta = document.createElement(\"meta\");\n newMeta.setAttribute(\"name\", name);\n newMeta.setAttribute(\"content\", content);\n document.head.appendChild(newMeta);\n }\n }\n }\n }\n}\n","import { useInject } from \"@alepha/react\";\nimport { Alepha } from \"alepha\";\nimport { useCallback, useEffect, useMemo } from \"react\";\nimport type { Head } from \"../interfaces/Head.ts\";\nimport { BrowserHeadProvider } from \"../providers/BrowserHeadProvider.ts\";\n\n/**\n * ```tsx\n * const App = () => {\n * const [head, setHead] = useHead({\n * // will set the document title on the first render\n * title: \"My App\",\n * });\n *\n * return (\n * // This will update the document title when the button is clicked\n * <button onClick={() => setHead({ title: \"Change Title\" })}>\n * Change Title {head.title}\n * </button>\n * );\n * }\n * ```\n */\nexport const useHead = (options?: UseHeadOptions): UseHeadReturn => {\n const alepha = useInject(Alepha);\n\n const current = useMemo(() => {\n if (!alepha.isBrowser()) {\n return {};\n }\n\n return alepha.inject(BrowserHeadProvider).getHead(window.document);\n }, []);\n\n const setHead = useCallback((head?: Head | ((previous?: Head) => Head)) => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n alepha\n .inject(BrowserHeadProvider)\n .renderHead(\n window.document,\n typeof head === \"function\" ? head(current) : head || {},\n );\n }, []);\n\n useEffect(() => {\n if (options) {\n setHead(options);\n }\n }, []);\n\n return [current, setHead];\n};\n\nexport type UseHeadOptions = Head | ((previous?: Head) => Head);\n\nexport type UseHeadReturn = [\n Head,\n (head?: Head | ((previous?: Head) => Head)) => void,\n];\n","import {\n AlephaReact,\n type PageConfigSchema,\n type TPropsDefault,\n type TPropsParentDefault,\n} from \"@alepha/react\";\nimport { $module } from \"alepha\";\nimport { $head } from \"./descriptors/$head.ts\";\nimport type { Head } from \"./interfaces/Head.ts\";\nimport { ServerHeadProvider } from \"./providers/ServerHeadProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./descriptors/$head.ts\";\nexport * from \"./hooks/useHead.ts\";\nexport * from \"./interfaces/Head.ts\";\nexport * from \"./providers/ServerHeadProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"@alepha/react\" {\n interface PageDescriptorOptions<\n TConfig extends PageConfigSchema = PageConfigSchema,\n TProps extends object = TPropsDefault,\n TPropsParent extends object = TPropsParentDefault,\n > {\n head?: Head | ((props: TProps, previous?: Head) => Head);\n }\n\n interface ReactRouterState {\n head: Head;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Fill `<head>` server & client side.\n *\n * @see {@link ServerHeadProvider}\n * @module alepha.react.head\n */\nexport const AlephaReactHead = $module({\n name: \"alepha.react.head\",\n descriptors: [$head],\n services: [AlephaReact, ServerHeadProvider],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["attrs: Record<string, string>","match: RegExpExecArray | null","attrs: Record<string, string>","metas: { name: string; content: string }[]"],"sources":["../../src/head/providers/HeadProvider.ts","../../src/head/primitives/$head.ts","../../src/head/providers/ServerHeadProvider.ts","../../src/head/providers/BrowserHeadProvider.ts","../../src/head/hooks/useHead.ts","../../src/head/index.ts"],"sourcesContent":["import type { PageRoute, ReactRouterState } from \"@alepha/react\";\nimport type { Head } from \"../interfaces/Head.ts\";\n\nexport class HeadProvider {\n public global?: Head | (() => Head);\n\n protected getGlobalHead(): Head | undefined {\n if (typeof this.global === \"function\") {\n return this.global();\n }\n return this.global;\n }\n\n public fillHead(state: ReactRouterState) {\n state.head = {\n ...state.head,\n ...this.getGlobalHead(),\n };\n\n for (const layer of state.layers) {\n if (layer.route?.head && !layer.error) {\n this.fillHeadByPage(layer.route, state, layer.props ?? {});\n }\n }\n }\n\n protected fillHeadByPage(\n page: PageRoute,\n state: ReactRouterState,\n props: Record<string, any>,\n ): void {\n if (!page.head) {\n return;\n }\n\n state.head ??= {};\n\n const head =\n typeof page.head === \"function\"\n ? page.head(props, state.head)\n : page.head;\n\n if (head.title) {\n state.head ??= {};\n\n if (state.head.titleSeparator) {\n state.head.title = `${head.title}${state.head.titleSeparator}${state.head.title}`;\n } else {\n state.head.title = head.title;\n }\n\n state.head.titleSeparator = head.titleSeparator;\n }\n\n if (head.htmlAttributes) {\n state.head.htmlAttributes = {\n ...state.head.htmlAttributes,\n ...head.htmlAttributes,\n };\n }\n\n if (head.bodyAttributes) {\n state.head.bodyAttributes = {\n ...state.head.bodyAttributes,\n ...head.bodyAttributes,\n };\n }\n\n if (head.meta) {\n state.head.meta = [...(state.head.meta ?? []), ...(head.meta ?? [])];\n }\n }\n}\n","import { $inject, createPrimitive, Primitive, KIND } from \"alepha\";\nimport type { Head } from \"../interfaces/Head.ts\";\nimport { HeadProvider } from \"../providers/HeadProvider.ts\";\n\n/**\n * Set global `<head>` options for the application.\n */\nexport const $head = (options: HeadPrimitiveOptions) => {\n return createPrimitive(HeadPrimitive, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type HeadPrimitiveOptions = Head | (() => Head);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class HeadPrimitive extends Primitive<HeadPrimitiveOptions> {\n protected readonly provider = $inject(HeadProvider);\n protected onInit() {\n this.provider.global = this.options;\n }\n}\n\n$head[KIND] = HeadPrimitive;\n","import { $hook, $inject } from \"alepha\";\nimport { ServerTimingProvider } from \"alepha/server\";\nimport type { SimpleHead } from \"../interfaces/Head.ts\";\nimport { HeadProvider } from \"./HeadProvider.ts\";\n\nexport class ServerHeadProvider {\n protected readonly headProvider = $inject(HeadProvider);\n protected readonly serverTimingProvider = $inject(ServerTimingProvider);\n\n protected readonly onServerRenderEnd = $hook({\n on: \"react:server:render:end\",\n handler: async (ev) => {\n this.serverTimingProvider.beginTiming(\"renderHead\");\n this.headProvider.fillHead(ev.state);\n if (ev.state.head) {\n ev.html = this.renderHead(ev.html, ev.state.head);\n }\n this.serverTimingProvider.endTiming(\"renderHead\");\n },\n });\n\n public renderHead(template: string, head: SimpleHead): string {\n let result = template;\n\n // Inject htmlAttributes\n const htmlAttributes = head.htmlAttributes;\n if (htmlAttributes) {\n result = result.replace(\n /<html([^>]*)>/i,\n (_, existingAttrs) =>\n `<html${this.mergeAttributes(existingAttrs, htmlAttributes)}>`,\n );\n }\n\n // Inject bodyAttributes\n const bodyAttributes = head.bodyAttributes;\n if (bodyAttributes) {\n result = result.replace(\n /<body([^>]*)>/i,\n (_, existingAttrs) =>\n `<body${this.mergeAttributes(existingAttrs, bodyAttributes)}>`,\n );\n }\n\n // Build head content\n let headContent = \"\";\n const title = head.title;\n if (title) {\n if (template.includes(\"<title>\")) {\n result = result.replace(\n /<title>(.*?)<\\/title>/i,\n () => `<title>${this.escapeHtml(title)}</title>`,\n );\n } else {\n headContent += `<title>${this.escapeHtml(title)}</title>\\n`;\n }\n }\n\n if (head.meta) {\n for (const meta of head.meta) {\n headContent += `<meta name=\"${this.escapeHtml(meta.name)}\" content=\"${this.escapeHtml(meta.content)}\">\\n`;\n }\n }\n\n // Inject into <head>...</head>\n result = result.replace(\n /<head([^>]*)>(.*?)<\\/head>/is,\n (_, existingAttrs, existingHead) =>\n `<head${existingAttrs}>${existingHead}${headContent}</head>`,\n );\n\n return result.trim();\n }\n\n protected mergeAttributes(\n existing: string,\n attrs: Record<string, string>,\n ): string {\n const existingAttrs = this.parseAttributes(existing);\n const merged = { ...existingAttrs, ...attrs };\n return Object.entries(merged)\n .map(([k, v]) => ` ${k}=\"${this.escapeHtml(v)}\"`)\n .join(\"\");\n }\n\n protected parseAttributes(attrStr: string): Record<string, string> {\n attrStr = attrStr.replaceAll(\"'\", '\"');\n\n const attrs: Record<string, string> = {};\n const attrRegex = /([^\\s=]+)(?:=\"([^\"]*)\")?/g;\n let match: RegExpExecArray | null = attrRegex.exec(attrStr);\n\n while (match) {\n attrs[match[1]] = match[2] ?? \"\";\n match = attrRegex.exec(attrStr);\n }\n\n return attrs;\n }\n\n protected escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n }\n}\n","import { $hook, $inject } from \"alepha\";\nimport type { Head } from \"../interfaces/Head.ts\";\nimport { HeadProvider } from \"./HeadProvider.ts\";\n\nexport class BrowserHeadProvider {\n protected readonly headProvider = $inject(HeadProvider);\n\n protected get document(): Document {\n return window.document;\n }\n\n protected readonly onBrowserRender = $hook({\n on: \"react:browser:render\",\n handler: async ({ state }) => {\n this.headProvider.fillHead(state);\n if (state.head) {\n this.renderHead(this.document, state.head);\n }\n },\n });\n\n protected readonly onTransitionEnd = $hook({\n on: \"react:transition:end\",\n handler: async ({ state }) => {\n this.headProvider.fillHead(state);\n if (state.head) {\n this.renderHead(this.document, state.head);\n }\n },\n });\n\n public getHead(document: Document): Head {\n return {\n get title() {\n return document.title;\n },\n get htmlAttributes() {\n const attrs: Record<string, string> = {};\n for (const attr of document.documentElement.attributes) {\n attrs[attr.name] = attr.value;\n }\n return attrs;\n },\n get bodyAttributes() {\n const attrs: Record<string, string> = {};\n for (const attr of document.body.attributes) {\n attrs[attr.name] = attr.value;\n }\n return attrs;\n },\n get meta() {\n const metas: { name: string; content: string }[] = [];\n for (const meta of document.head.querySelectorAll(\"meta[name]\")) {\n const name = meta.getAttribute(\"name\");\n const content = meta.getAttribute(\"content\");\n if (name && content) {\n metas.push({ name, content });\n }\n }\n return metas;\n },\n };\n }\n\n public renderHead(document: Document, head: Head): void {\n if (head.title) {\n document.title = head.title;\n }\n\n if (head.bodyAttributes) {\n for (const [key, value] of Object.entries(head.bodyAttributes)) {\n if (value) {\n document.body.setAttribute(key, value);\n } else {\n document.body.removeAttribute(key);\n }\n }\n }\n\n if (head.htmlAttributes) {\n for (const [key, value] of Object.entries(head.htmlAttributes)) {\n if (value) {\n document.documentElement.setAttribute(key, value);\n } else {\n document.documentElement.removeAttribute(key);\n }\n }\n }\n\n if (head.meta) {\n for (const it of head.meta) {\n const { name, content } = it;\n const meta = document.querySelector(`meta[name=\"${name}\"]`);\n if (meta) {\n meta.setAttribute(\"content\", content);\n } else {\n const newMeta = document.createElement(\"meta\");\n newMeta.setAttribute(\"name\", name);\n newMeta.setAttribute(\"content\", content);\n document.head.appendChild(newMeta);\n }\n }\n }\n }\n}\n","import { useInject } from \"@alepha/react\";\nimport { Alepha } from \"alepha\";\nimport { useCallback, useEffect, useMemo } from \"react\";\nimport type { Head } from \"../interfaces/Head.ts\";\nimport { BrowserHeadProvider } from \"../providers/BrowserHeadProvider.ts\";\n\n/**\n * ```tsx\n * const App = () => {\n * const [head, setHead] = useHead({\n * // will set the document title on the first render\n * title: \"My App\",\n * });\n *\n * return (\n * // This will update the document title when the button is clicked\n * <button onClick={() => setHead({ title: \"Change Title\" })}>\n * Change Title {head.title}\n * </button>\n * );\n * }\n * ```\n */\nexport const useHead = (options?: UseHeadOptions): UseHeadReturn => {\n const alepha = useInject(Alepha);\n\n const current = useMemo(() => {\n if (!alepha.isBrowser()) {\n return {};\n }\n\n return alepha.inject(BrowserHeadProvider).getHead(window.document);\n }, []);\n\n const setHead = useCallback((head?: Head | ((previous?: Head) => Head)) => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n alepha\n .inject(BrowserHeadProvider)\n .renderHead(\n window.document,\n typeof head === \"function\" ? head(current) : head || {},\n );\n }, []);\n\n useEffect(() => {\n if (options) {\n setHead(options);\n }\n }, []);\n\n return [current, setHead];\n};\n\nexport type UseHeadOptions = Head | ((previous?: Head) => Head);\n\nexport type UseHeadReturn = [\n Head,\n (head?: Head | ((previous?: Head) => Head)) => void,\n];\n","import {\n AlephaReact,\n type PageConfigSchema,\n type TPropsDefault,\n type TPropsParentDefault,\n} from \"@alepha/react\";\nimport { $module } from \"alepha\";\nimport { $head } from \"./primitives/$head.ts\";\nimport type { Head } from \"./interfaces/Head.ts\";\nimport { ServerHeadProvider } from \"./providers/ServerHeadProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$head.ts\";\nexport * from \"./hooks/useHead.ts\";\nexport * from \"./interfaces/Head.ts\";\nexport * from \"./providers/ServerHeadProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"@alepha/react\" {\n interface PagePrimitiveOptions<\n TConfig extends PageConfigSchema = PageConfigSchema,\n TProps extends object = TPropsDefault,\n TPropsParent extends object = TPropsParentDefault,\n > {\n head?: Head | ((props: TProps, previous?: Head) => Head);\n }\n\n interface ReactRouterState {\n head: Head;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Fill `<head>` server & client side.\n *\n * @see {@link ServerHeadProvider}\n * @module alepha.react.head\n */\nexport const AlephaReactHead = $module({\n name: \"alepha.react.head\",\n primitives: [$head],\n services: [AlephaReact, ServerHeadProvider],\n});\n"],"mappings":"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"}
|
package/dist/i18n/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as alepha16 from "alepha";
|
|
2
|
-
import { Alepha, Async,
|
|
2
|
+
import { Alepha, Async, KIND, LogLevel, LoggerInterface, Primitive, Static, TSchema, TypeBoxError } from "alepha";
|
|
3
3
|
import dayjsDuration from "dayjs/plugin/duration.js";
|
|
4
4
|
import DayjsApi, { Dayjs, ManipulateType, PluginFunc } from "dayjs";
|
|
5
5
|
|
|
@@ -15,8 +15,8 @@ declare class DateTimeProvider {
|
|
|
15
15
|
protected readonly timeouts: Timeout[];
|
|
16
16
|
protected readonly intervals: Interval[];
|
|
17
17
|
constructor();
|
|
18
|
-
protected readonly onStart: alepha16.
|
|
19
|
-
protected readonly onStop: alepha16.
|
|
18
|
+
protected readonly onStart: alepha16.HookPrimitive<"start">;
|
|
19
|
+
protected readonly onStop: alepha16.HookPrimitive<"stop">;
|
|
20
20
|
setLocale(locale: string): void;
|
|
21
21
|
isDateTime(value: unknown): value is DateTime;
|
|
22
22
|
/**
|
|
@@ -230,8 +230,8 @@ declare module "alepha" {
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
//#endregion
|
|
233
|
-
//#region ../alepha/src/server-cookies/
|
|
234
|
-
interface
|
|
233
|
+
//#region ../alepha/src/server-cookies/primitives/$cookie.d.ts
|
|
234
|
+
interface CookiePrimitiveOptions<T extends TSchema> {
|
|
235
235
|
/** The schema for the cookie's value, used for validation and type safety. */
|
|
236
236
|
schema: T;
|
|
237
237
|
/** The name of the cookie. */
|
|
@@ -255,9 +255,9 @@ interface CookieDescriptorOptions<T extends TSchema> {
|
|
|
255
255
|
/** If true, the cookie will be signed to prevent tampering. Requires `COOKIE_SECRET` env var. */
|
|
256
256
|
sign?: boolean;
|
|
257
257
|
}
|
|
258
|
-
interface
|
|
258
|
+
interface AbstractCookiePrimitive<T extends TSchema> {
|
|
259
259
|
readonly name: string;
|
|
260
|
-
readonly options:
|
|
260
|
+
readonly options: CookiePrimitiveOptions<T>;
|
|
261
261
|
set(value: Static<T>, options?: {
|
|
262
262
|
cookies?: Cookies;
|
|
263
263
|
ttl?: DurationLike;
|
|
@@ -290,9 +290,9 @@ declare module "alepha/server" {
|
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
292
|
/**
|
|
293
|
-
* Provides HTTP cookie management capabilities for server requests and responses with type-safe cookie
|
|
293
|
+
* Provides HTTP cookie management capabilities for server requests and responses with type-safe cookie primitives.
|
|
294
294
|
*
|
|
295
|
-
* The server-cookies module enables declarative cookie handling using the `$cookie`
|
|
295
|
+
* The server-cookies module enables declarative cookie handling using the `$cookie` primitive on class properties.
|
|
296
296
|
* It offers automatic cookie parsing, secure cookie configuration, and seamless integration with server routes
|
|
297
297
|
* for managing user sessions, preferences, and authentication tokens.
|
|
298
298
|
*
|
|
@@ -305,14 +305,14 @@ declare module "alepha/server" {
|
|
|
305
305
|
* Hook to access the i18n service.
|
|
306
306
|
*/
|
|
307
307
|
declare const useI18n: <S extends object, K$1 extends keyof ServiceDictionary<S>>() => I18nProvider<S, K$1>;
|
|
308
|
-
type ServiceDictionary<T extends object> = { [K in keyof T]: T[K] extends
|
|
308
|
+
type ServiceDictionary<T extends object> = { [K in keyof T]: T[K] extends DictionaryPrimitive<infer U> ? U : never };
|
|
309
309
|
//#endregion
|
|
310
310
|
//#region src/i18n/providers/I18nProvider.d.ts
|
|
311
311
|
declare class I18nProvider<S extends object, K$1 extends keyof ServiceDictionary<S>> {
|
|
312
312
|
protected log: Logger;
|
|
313
313
|
protected alepha: Alepha;
|
|
314
314
|
protected dateTimeProvider: DateTimeProvider;
|
|
315
|
-
protected cookie:
|
|
315
|
+
protected cookie: AbstractCookiePrimitive<alepha16.TString>;
|
|
316
316
|
readonly registry: Array<{
|
|
317
317
|
target: string;
|
|
318
318
|
name: string;
|
|
@@ -331,11 +331,11 @@ declare class I18nProvider<S extends object, K$1 extends keyof ServiceDictionary
|
|
|
331
331
|
};
|
|
332
332
|
get languages(): string[];
|
|
333
333
|
constructor();
|
|
334
|
-
protected readonly onRender: alepha16.
|
|
335
|
-
protected readonly onStart: alepha16.
|
|
334
|
+
protected readonly onRender: alepha16.HookPrimitive<"server:onRequest">;
|
|
335
|
+
protected readonly onStart: alepha16.HookPrimitive<"start">;
|
|
336
336
|
protected refreshLocale(): void;
|
|
337
337
|
setLang: (lang: string) => Promise<void>;
|
|
338
|
-
protected readonly mutate: alepha16.
|
|
338
|
+
protected readonly mutate: alepha16.HookPrimitive<"state:mutate">;
|
|
339
339
|
get lang(): string;
|
|
340
340
|
translate: (key: string, args?: string[]) => string;
|
|
341
341
|
readonly l: (value: I18nLocalizeType, options?: I18nLocalizeOptions) => string;
|
|
@@ -370,7 +370,7 @@ interface I18nLocalizeOptions {
|
|
|
370
370
|
timezone?: string;
|
|
371
371
|
}
|
|
372
372
|
//#endregion
|
|
373
|
-
//#region src/i18n/
|
|
373
|
+
//#region src/i18n/primitives/$dictionary.d.ts
|
|
374
374
|
/**
|
|
375
375
|
* Register a dictionary entry for translations.
|
|
376
376
|
*
|
|
@@ -403,17 +403,17 @@ interface I18nLocalizeOptions {
|
|
|
403
403
|
* ```
|
|
404
404
|
*/
|
|
405
405
|
declare const $dictionary: {
|
|
406
|
-
<T extends Record<string, string>>(options:
|
|
407
|
-
[KIND]: typeof
|
|
406
|
+
<T extends Record<string, string>>(options: DictionaryPrimitiveOptions<T>): DictionaryPrimitive<T>;
|
|
407
|
+
[KIND]: typeof DictionaryPrimitive;
|
|
408
408
|
};
|
|
409
|
-
interface
|
|
409
|
+
interface DictionaryPrimitiveOptions<T extends Record<string, string>> {
|
|
410
410
|
lang?: string;
|
|
411
411
|
name?: string;
|
|
412
412
|
lazy: () => Async<{
|
|
413
413
|
default: T;
|
|
414
414
|
}>;
|
|
415
415
|
}
|
|
416
|
-
declare class
|
|
416
|
+
declare class DictionaryPrimitive<T extends Record<string, string>> extends Primitive<DictionaryPrimitiveOptions<T>> {
|
|
417
417
|
protected provider: I18nProvider<object, never>;
|
|
418
418
|
protected onInit(): void;
|
|
419
419
|
}
|
|
@@ -433,5 +433,5 @@ declare module "alepha" {
|
|
|
433
433
|
*/
|
|
434
434
|
declare const AlephaReactI18n: alepha16.Service<alepha16.Module>;
|
|
435
435
|
//#endregion
|
|
436
|
-
export { $dictionary, AlephaReactI18n,
|
|
436
|
+
export { $dictionary, AlephaReactI18n, DictionaryPrimitive, DictionaryPrimitiveOptions, I18nLocalizeOptions, I18nLocalizeType, I18nProvider, Localize, type LocalizeProps, ServiceDictionary, useI18n };
|
|
437
437
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/i18n/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $hook, $inject, $module, Alepha,
|
|
1
|
+
import { $hook, $inject, $module, Alepha, KIND, Primitive, TypeBoxError, TypeProvider, createPrimitive, t } from "alepha";
|
|
2
2
|
import { DateTimeProvider } from "alepha/datetime";
|
|
3
3
|
import { $logger } from "alepha/logger";
|
|
4
4
|
import { $cookie } from "alepha/server/cookies";
|
|
@@ -30,7 +30,7 @@ var I18nProvider = class {
|
|
|
30
30
|
on: "server:onRequest",
|
|
31
31
|
priority: "last",
|
|
32
32
|
handler: async ({ request }) => {
|
|
33
|
-
this.alepha.
|
|
33
|
+
this.alepha.store.set("alepha.react.i18n.lang", this.cookie.get(request));
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
onStart = $hook({
|
|
@@ -38,7 +38,7 @@ var I18nProvider = class {
|
|
|
38
38
|
handler: async () => {
|
|
39
39
|
if (this.alepha.isBrowser()) {
|
|
40
40
|
const cookieLang = this.cookie.get();
|
|
41
|
-
if (cookieLang) this.alepha.
|
|
41
|
+
if (cookieLang) this.alepha.store.set("alepha.react.i18n.lang", cookieLang);
|
|
42
42
|
for (const item of this.registry) if (item.lang === this.lang || item.lang === this.options.fallbackLang) {
|
|
43
43
|
this.log.trace("Loading language", {
|
|
44
44
|
lang: item.lang,
|
|
@@ -66,7 +66,7 @@ var I18nProvider = class {
|
|
|
66
66
|
}
|
|
67
67
|
this.cookie.set(lang);
|
|
68
68
|
}
|
|
69
|
-
this.alepha.
|
|
69
|
+
this.alepha.store.set("alepha.react.i18n.lang", lang);
|
|
70
70
|
this.refreshLocale();
|
|
71
71
|
};
|
|
72
72
|
mutate = $hook({
|
|
@@ -80,12 +80,12 @@ var I18nProvider = class {
|
|
|
80
80
|
hasChanged = true;
|
|
81
81
|
}
|
|
82
82
|
this.refreshLocale();
|
|
83
|
-
if (hasChanged) this.alepha.
|
|
83
|
+
if (hasChanged) this.alepha.store.set("alepha.react.i18n.lang", value);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
});
|
|
87
87
|
get lang() {
|
|
88
|
-
return this.alepha.
|
|
88
|
+
return this.alepha.store.get("alepha.react.i18n.lang") || this.options.fallbackLang;
|
|
89
89
|
}
|
|
90
90
|
translate = (key, args = []) => {
|
|
91
91
|
for (const item of this.registry) if (item.lang === this.lang) {
|
|
@@ -128,7 +128,7 @@ var I18nProvider = class {
|
|
|
128
128
|
};
|
|
129
129
|
|
|
130
130
|
//#endregion
|
|
131
|
-
//#region src/i18n/
|
|
131
|
+
//#region src/i18n/primitives/$dictionary.ts
|
|
132
132
|
/**
|
|
133
133
|
* Register a dictionary entry for translations.
|
|
134
134
|
*
|
|
@@ -161,9 +161,9 @@ var I18nProvider = class {
|
|
|
161
161
|
* ```
|
|
162
162
|
*/
|
|
163
163
|
const $dictionary = (options) => {
|
|
164
|
-
return
|
|
164
|
+
return createPrimitive(DictionaryPrimitive, options);
|
|
165
165
|
};
|
|
166
|
-
var
|
|
166
|
+
var DictionaryPrimitive = class extends Primitive {
|
|
167
167
|
provider = $inject(I18nProvider);
|
|
168
168
|
onInit() {
|
|
169
169
|
this.provider.registry.push({
|
|
@@ -177,7 +177,7 @@ var DictionaryDescriptor = class extends Descriptor {
|
|
|
177
177
|
});
|
|
178
178
|
}
|
|
179
179
|
};
|
|
180
|
-
$dictionary[KIND] =
|
|
180
|
+
$dictionary[KIND] = DictionaryPrimitive;
|
|
181
181
|
|
|
182
182
|
//#endregion
|
|
183
183
|
//#region src/i18n/hooks/useI18n.ts
|
|
@@ -207,10 +207,10 @@ var Localize_default = Localize;
|
|
|
207
207
|
*/
|
|
208
208
|
const AlephaReactI18n = $module({
|
|
209
209
|
name: "alepha.react.i18n",
|
|
210
|
-
|
|
210
|
+
primitives: [$dictionary],
|
|
211
211
|
services: [I18nProvider]
|
|
212
212
|
});
|
|
213
213
|
|
|
214
214
|
//#endregion
|
|
215
|
-
export { $dictionary, AlephaReactI18n,
|
|
215
|
+
export { $dictionary, AlephaReactI18n, DictionaryPrimitive, I18nProvider, Localize_default as Localize, useI18n };
|
|
216
216
|
//# sourceMappingURL=index.js.map
|
package/dist/i18n/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/i18n/providers/I18nProvider.ts","../../src/i18n/descriptors/$dictionary.ts","../../src/i18n/hooks/useI18n.ts","../../src/i18n/components/Localize.tsx","../../src/i18n/index.ts"],"sourcesContent":["import { $hook, $inject, Alepha, TypeBoxError, TypeProvider, t } from \"alepha\";\nimport { type DateTime, DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $cookie } from \"alepha/server/cookies\";\nimport type { ServiceDictionary } from \"../hooks/useI18n.ts\";\n\nexport class I18nProvider<\n S extends object,\n K extends keyof ServiceDictionary<S>,\n> {\n protected log = $logger();\n protected alepha = $inject(Alepha);\n protected dateTimeProvider = $inject(DateTimeProvider);\n\n protected cookie = $cookie({\n name: \"lang\",\n schema: t.text(),\n });\n\n public readonly registry: Array<{\n target: string;\n name: string;\n lang: string;\n loader: () => Promise<Record<string, string>>;\n translations: Record<string, string>;\n }> = [];\n\n options = {\n fallbackLang: \"en\",\n };\n\n public dateFormat: { format: (value: Date) => string } =\n new Intl.DateTimeFormat(this.lang);\n\n public numberFormat: { format: (value: number) => string } =\n new Intl.NumberFormat(this.lang);\n\n public get languages() {\n const languages = new Set<string>();\n\n for (const item of this.registry) {\n languages.add(item.lang);\n }\n languages.add(this.options.fallbackLang);\n\n return Array.from(languages);\n }\n\n constructor() {\n this.refreshLocale();\n }\n\n protected readonly onRender = $hook({\n on: \"server:onRequest\",\n priority: \"last\",\n handler: async ({ request }) => {\n this.alepha.state.set(\"alepha.react.i18n.lang\", this.cookie.get(request));\n },\n });\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n if (this.alepha.isBrowser()) {\n // get cookie lang\n const cookieLang = this.cookie.get();\n if (cookieLang) {\n this.alepha.state.set(\"alepha.react.i18n.lang\", cookieLang);\n }\n\n for (const item of this.registry) {\n if (\n item.lang === this.lang ||\n item.lang === this.options.fallbackLang\n ) {\n this.log.trace(\"Loading language\", {\n lang: item.lang,\n name: item.name,\n target: item.target,\n });\n item.translations = await item.loader();\n }\n }\n return;\n }\n\n for (const item of this.registry) {\n item.translations = await item.loader();\n }\n },\n });\n\n protected refreshLocale() {\n this.numberFormat = new Intl.NumberFormat(this.lang);\n this.dateFormat = new Intl.DateTimeFormat(this.lang);\n this.dateTimeProvider.setLocale(this.lang);\n TypeProvider.setLocale(this.lang);\n }\n\n public setLang = async (lang: string) => {\n if (this.alepha.isBrowser()) {\n for (const item of this.registry) {\n if (lang === item.lang) {\n if (Object.keys(item.translations).length > 0) {\n continue; // already loaded\n }\n item.translations = await item.loader();\n }\n }\n this.cookie.set(lang);\n }\n\n this.alepha.state.set(\"alepha.react.i18n.lang\", lang);\n this.refreshLocale();\n };\n\n protected readonly mutate = $hook({\n on: \"state:mutate\",\n handler: async ({ key, value }) => {\n if (key === \"alepha.react.i18n.lang\" && this.alepha.isBrowser()) {\n let hasChanged = false;\n for (const item of this.registry) {\n if (value === item.lang) {\n if (Object.keys(item.translations).length > 0) {\n continue; // already loaded\n }\n item.translations = await item.loader();\n hasChanged = true;\n }\n }\n\n this.refreshLocale();\n\n if (hasChanged) {\n this.alepha.state.set(\"alepha.react.i18n.lang\", value);\n }\n }\n },\n });\n\n public get lang(): string {\n return (\n this.alepha.state.get(\"alepha.react.i18n.lang\") ||\n this.options.fallbackLang\n );\n }\n\n public translate = (key: string, args: string[] = []) => {\n for (const item of this.registry) {\n if (item.lang === this.lang) {\n if (item.translations[key]) {\n return this.render(item.translations[key], args); // append lang for fallback\n }\n }\n }\n\n for (const item of this.registry) {\n if (item.lang === this.options.fallbackLang) {\n if (item.translations[key]) {\n return this.render(item.translations[key], args); // append lang for fallback\n }\n }\n }\n\n return key; // fallback to the key itself if not found\n };\n\n public readonly l = (\n value: I18nLocalizeType,\n options: I18nLocalizeOptions = {},\n ) => {\n // Handle numbers\n if (typeof value === \"number\") {\n return new Intl.NumberFormat(this.lang, options.number).format(value);\n }\n\n // Handle dates\n if (\n value instanceof Date ||\n this.dateTimeProvider.isDateTime(value) ||\n (typeof value === \"string\" && options.date)\n ) {\n // convert to DateTime with locale applied\n let dt = this.dateTimeProvider.of(value);\n\n // apply timezone if specified\n if (options.timezone) {\n dt = dt.tz(options.timezone);\n }\n\n // format using dayjs format string\n if (typeof options.date === \"string\") {\n if (options.date === \"fromNow\") {\n return dt.locale(this.lang).fromNow();\n }\n return dt.locale(this.lang).format(options.date);\n }\n\n // format using Intl.DateTimeFormatOptions\n if (options.date) {\n return new Intl.DateTimeFormat(\n this.lang,\n options.timezone\n ? { ...options.date, timeZone: options.timezone }\n : options.date,\n ).format(dt.toDate());\n }\n\n // default formatting with timezone\n if (options.timezone) {\n return new Intl.DateTimeFormat(this.lang, {\n timeZone: options.timezone,\n }).format(dt.toDate());\n }\n\n // default formatting\n return new Intl.DateTimeFormat(this.lang).format(dt.toDate());\n }\n\n // handle TypeBox errors\n if (value instanceof TypeBoxError) {\n return TypeProvider.translateError(value, this.lang);\n }\n\n // return string values as-is\n return value;\n };\n\n public readonly tr = (\n key: keyof ServiceDictionary<S>[K],\n options: {\n args?: string[];\n default?: string;\n } = {},\n ) => {\n const translation = this.translate(key as string, options.args || []);\n if (translation === key && options.default) {\n return options.default;\n }\n return translation;\n };\n\n protected render(item: string, args: string[]): string {\n let result = item;\n for (let i = 0; i < args.length; i++) {\n result = result.replace(`$${i + 1}`, args[i]);\n }\n return result;\n }\n}\n\nexport type I18nLocalizeType = string | number | Date | DateTime | TypeBoxError;\n\nexport interface I18nLocalizeOptions {\n /**\n * Options for number formatting (when value is a number)\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\n */\n number?: Intl.NumberFormatOptions;\n /**\n * Options for date formatting (when value is a Date or DateTime)\n * Can be:\n * - A dayjs format string (e.g., \"LLL\", \"YYYY-MM-DD\", \"dddd, MMMM D YYYY\")\n * - \"fromNow\" for relative time (e.g., \"2 hours ago\")\n * - Intl.DateTimeFormatOptions for native formatting\n * @see https://day.js.org/docs/en/display/format\n * @see https://day.js.org/docs/en/display/from-now\n */\n date?: string | \"fromNow\" | Intl.DateTimeFormatOptions;\n /**\n * Timezone to display dates in (when value is a Date or DateTime)\n * Uses IANA timezone names (e.g., \"America/New_York\", \"Europe/Paris\", \"Asia/Tokyo\")\n * @see https://day.js.org/docs/en/timezone/timezone\n */\n timezone?: string;\n}\n","import { $inject, type Async, createDescriptor, Descriptor, KIND } from \"alepha\";\nimport { I18nProvider } from \"../providers/I18nProvider.ts\";\n\n/**\n * Register a dictionary entry for translations.\n *\n * It allows you to define a set of translations for a specific language.\n * Entry can be lazy-loaded, which is useful for large dictionaries or when translations are not needed immediately.\n *\n * @example\n * ```ts\n * import { $dictionary } from \"@alepha/react/i18n\";\n *\n * const Example = () => {\n * const { tr } = useI18n<App, \"en\">();\n * return <div>{tr(\"hello\")}</div>; //\n * }\n *\n * class App {\n *\n * en = $dictionary({\n * // { default: { hello: \"Hey\" } }\n * lazy: () => import(\"./translations/en.ts\"),\n * });\n *\n * home = $page({\n * path: \"/\",\n * component: Example,\n * })\n * }\n *\n * run(App);\n * ```\n */\nexport const $dictionary = <T extends Record<string, string>>(\n options: DictionaryDescriptorOptions<T>,\n): DictionaryDescriptor<T> => {\n return createDescriptor(DictionaryDescriptor<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface DictionaryDescriptorOptions<T extends Record<string, string>> {\n lang?: string;\n name?: string;\n lazy: () => Async<{ default: T }>;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class DictionaryDescriptor<\n T extends Record<string, string>,\n> extends Descriptor<DictionaryDescriptorOptions<T>> {\n protected provider = $inject(I18nProvider);\n protected onInit() {\n this.provider.registry.push({\n target: this.config.service.name,\n name: this.options.name ?? this.config.propertyKey,\n lang: this.options.lang ?? this.config.propertyKey,\n loader: async () => {\n const mod = await this.options.lazy();\n return mod.default;\n },\n translations: {},\n });\n }\n}\n\n$dictionary[KIND] = DictionaryDescriptor;\n","import { useInject, useStore } from \"@alepha/react\";\nimport type { DictionaryDescriptor } from \"../descriptors/$dictionary.ts\";\nimport { I18nProvider } from \"../providers/I18nProvider.ts\";\n\n/**\n * Hook to access the i18n service.\n */\nexport const useI18n = <\n S extends object,\n K extends keyof ServiceDictionary<S>,\n>(): I18nProvider<S, K> => {\n useStore(\"alepha.react.i18n.lang\");\n return useInject(I18nProvider<S, K>);\n};\n\nexport type ServiceDictionary<T extends object> = {\n [K in keyof T]: T[K] extends DictionaryDescriptor<infer U> ? U : never;\n};\n","import type { TypeBoxError } from \"alepha\";\nimport type { DateTime } from \"alepha/datetime\";\nimport { useI18n } from \"../hooks/useI18n.ts\";\n\nexport interface LocalizeProps {\n value: string | number | Date | DateTime | TypeBoxError;\n /**\n * Options for number formatting (when value is a number)\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\n */\n number?: Intl.NumberFormatOptions;\n /**\n * Options for date formatting (when value is a Date or DateTime)\n * Can be:\n * - A dayjs format string (e.g., \"LLL\", \"YYYY-MM-DD\", \"dddd, MMMM D YYYY\")\n * - \"fromNow\" for relative time (e.g., \"2 hours ago\")\n * - Intl.DateTimeFormatOptions for native formatting\n * @see https://day.js.org/docs/en/display/format\n * @see https://day.js.org/docs/en/display/from-now\n */\n date?: string | \"fromNow\" | Intl.DateTimeFormatOptions;\n /**\n * Timezone to display dates in (when value is a Date or DateTime)\n * Uses IANA timezone names (e.g., \"America/New_York\", \"Europe/Paris\", \"Asia/Tokyo\")\n * @see https://day.js.org/docs/en/timezone/timezone\n */\n timezone?: string;\n}\n\nconst Localize = (props: LocalizeProps) => {\n const i18n = useI18n();\n return i18n.l(props.value, props);\n};\n\nexport default Localize;\n","import { $module } from \"alepha\";\nimport { $dictionary } from \"./descriptors/$dictionary.ts\";\nimport { I18nProvider } from \"./providers/I18nProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type { LocalizeProps } from \"./components/Localize.tsx\";\nexport { default as Localize } from \"./components/Localize.tsx\";\nexport * from \"./descriptors/$dictionary.ts\";\nexport * from \"./hooks/useI18n.ts\";\nexport * from \"./providers/I18nProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n export interface State {\n \"alepha.react.i18n.lang\"?: string;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Add i18n support to your Alepha React application. SSR and CSR compatible.\n *\n * It supports lazy loading of translations and provides a context to access the current language.\n *\n * @module alepha.react.i18n\n */\nexport const AlephaReactI18n = $module({\n name: \"alepha.react.i18n\",\n descriptors: [$dictionary],\n services: [I18nProvider],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/i18n/providers/I18nProvider.ts","../../src/i18n/primitives/$dictionary.ts","../../src/i18n/hooks/useI18n.ts","../../src/i18n/components/Localize.tsx","../../src/i18n/index.ts"],"sourcesContent":["import { $hook, $inject, Alepha, TypeBoxError, TypeProvider, t } from \"alepha\";\nimport { type DateTime, DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $cookie } from \"alepha/server/cookies\";\nimport type { ServiceDictionary } from \"../hooks/useI18n.ts\";\n\nexport class I18nProvider<\n S extends object,\n K extends keyof ServiceDictionary<S>,\n> {\n protected log = $logger();\n protected alepha = $inject(Alepha);\n protected dateTimeProvider = $inject(DateTimeProvider);\n\n protected cookie = $cookie({\n name: \"lang\",\n schema: t.text(),\n });\n\n public readonly registry: Array<{\n target: string;\n name: string;\n lang: string;\n loader: () => Promise<Record<string, string>>;\n translations: Record<string, string>;\n }> = [];\n\n options = {\n fallbackLang: \"en\",\n };\n\n public dateFormat: { format: (value: Date) => string } =\n new Intl.DateTimeFormat(this.lang);\n\n public numberFormat: { format: (value: number) => string } =\n new Intl.NumberFormat(this.lang);\n\n public get languages() {\n const languages = new Set<string>();\n\n for (const item of this.registry) {\n languages.add(item.lang);\n }\n languages.add(this.options.fallbackLang);\n\n return Array.from(languages);\n }\n\n constructor() {\n this.refreshLocale();\n }\n\n protected readonly onRender = $hook({\n on: \"server:onRequest\",\n priority: \"last\",\n handler: async ({ request }) => {\n this.alepha.store.set(\"alepha.react.i18n.lang\", this.cookie.get(request));\n },\n });\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n if (this.alepha.isBrowser()) {\n // get cookie lang\n const cookieLang = this.cookie.get();\n if (cookieLang) {\n this.alepha.store.set(\"alepha.react.i18n.lang\", cookieLang);\n }\n\n for (const item of this.registry) {\n if (\n item.lang === this.lang ||\n item.lang === this.options.fallbackLang\n ) {\n this.log.trace(\"Loading language\", {\n lang: item.lang,\n name: item.name,\n target: item.target,\n });\n item.translations = await item.loader();\n }\n }\n return;\n }\n\n for (const item of this.registry) {\n item.translations = await item.loader();\n }\n },\n });\n\n protected refreshLocale() {\n this.numberFormat = new Intl.NumberFormat(this.lang);\n this.dateFormat = new Intl.DateTimeFormat(this.lang);\n this.dateTimeProvider.setLocale(this.lang);\n TypeProvider.setLocale(this.lang);\n }\n\n public setLang = async (lang: string) => {\n if (this.alepha.isBrowser()) {\n for (const item of this.registry) {\n if (lang === item.lang) {\n if (Object.keys(item.translations).length > 0) {\n continue; // already loaded\n }\n item.translations = await item.loader();\n }\n }\n this.cookie.set(lang);\n }\n\n this.alepha.store.set(\"alepha.react.i18n.lang\", lang);\n this.refreshLocale();\n };\n\n protected readonly mutate = $hook({\n on: \"state:mutate\",\n handler: async ({ key, value }) => {\n if (key === \"alepha.react.i18n.lang\" && this.alepha.isBrowser()) {\n let hasChanged = false;\n for (const item of this.registry) {\n if (value === item.lang) {\n if (Object.keys(item.translations).length > 0) {\n continue; // already loaded\n }\n item.translations = await item.loader();\n hasChanged = true;\n }\n }\n\n this.refreshLocale();\n\n if (hasChanged) {\n this.alepha.store.set(\"alepha.react.i18n.lang\", value);\n }\n }\n },\n });\n\n public get lang(): string {\n return (\n this.alepha.store.get(\"alepha.react.i18n.lang\") ||\n this.options.fallbackLang\n );\n }\n\n public translate = (key: string, args: string[] = []) => {\n for (const item of this.registry) {\n if (item.lang === this.lang) {\n if (item.translations[key]) {\n return this.render(item.translations[key], args); // append lang for fallback\n }\n }\n }\n\n for (const item of this.registry) {\n if (item.lang === this.options.fallbackLang) {\n if (item.translations[key]) {\n return this.render(item.translations[key], args); // append lang for fallback\n }\n }\n }\n\n return key; // fallback to the key itself if not found\n };\n\n public readonly l = (\n value: I18nLocalizeType,\n options: I18nLocalizeOptions = {},\n ) => {\n // Handle numbers\n if (typeof value === \"number\") {\n return new Intl.NumberFormat(this.lang, options.number).format(value);\n }\n\n // Handle dates\n if (\n value instanceof Date ||\n this.dateTimeProvider.isDateTime(value) ||\n (typeof value === \"string\" && options.date)\n ) {\n // convert to DateTime with locale applied\n let dt = this.dateTimeProvider.of(value);\n\n // apply timezone if specified\n if (options.timezone) {\n dt = dt.tz(options.timezone);\n }\n\n // format using dayjs format string\n if (typeof options.date === \"string\") {\n if (options.date === \"fromNow\") {\n return dt.locale(this.lang).fromNow();\n }\n return dt.locale(this.lang).format(options.date);\n }\n\n // format using Intl.DateTimeFormatOptions\n if (options.date) {\n return new Intl.DateTimeFormat(\n this.lang,\n options.timezone\n ? { ...options.date, timeZone: options.timezone }\n : options.date,\n ).format(dt.toDate());\n }\n\n // default formatting with timezone\n if (options.timezone) {\n return new Intl.DateTimeFormat(this.lang, {\n timeZone: options.timezone,\n }).format(dt.toDate());\n }\n\n // default formatting\n return new Intl.DateTimeFormat(this.lang).format(dt.toDate());\n }\n\n // handle TypeBox errors\n if (value instanceof TypeBoxError) {\n return TypeProvider.translateError(value, this.lang);\n }\n\n // return string values as-is\n return value;\n };\n\n public readonly tr = (\n key: keyof ServiceDictionary<S>[K],\n options: {\n args?: string[];\n default?: string;\n } = {},\n ) => {\n const translation = this.translate(key as string, options.args || []);\n if (translation === key && options.default) {\n return options.default;\n }\n return translation;\n };\n\n protected render(item: string, args: string[]): string {\n let result = item;\n for (let i = 0; i < args.length; i++) {\n result = result.replace(`$${i + 1}`, args[i]);\n }\n return result;\n }\n}\n\nexport type I18nLocalizeType = string | number | Date | DateTime | TypeBoxError;\n\nexport interface I18nLocalizeOptions {\n /**\n * Options for number formatting (when value is a number)\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\n */\n number?: Intl.NumberFormatOptions;\n /**\n * Options for date formatting (when value is a Date or DateTime)\n * Can be:\n * - A dayjs format string (e.g., \"LLL\", \"YYYY-MM-DD\", \"dddd, MMMM D YYYY\")\n * - \"fromNow\" for relative time (e.g., \"2 hours ago\")\n * - Intl.DateTimeFormatOptions for native formatting\n * @see https://day.js.org/docs/en/display/format\n * @see https://day.js.org/docs/en/display/from-now\n */\n date?: string | \"fromNow\" | Intl.DateTimeFormatOptions;\n /**\n * Timezone to display dates in (when value is a Date or DateTime)\n * Uses IANA timezone names (e.g., \"America/New_York\", \"Europe/Paris\", \"Asia/Tokyo\")\n * @see https://day.js.org/docs/en/timezone/timezone\n */\n timezone?: string;\n}\n","import { $inject, type Async, createPrimitive, Primitive, KIND } from \"alepha\";\nimport { I18nProvider } from \"../providers/I18nProvider.ts\";\n\n/**\n * Register a dictionary entry for translations.\n *\n * It allows you to define a set of translations for a specific language.\n * Entry can be lazy-loaded, which is useful for large dictionaries or when translations are not needed immediately.\n *\n * @example\n * ```ts\n * import { $dictionary } from \"@alepha/react/i18n\";\n *\n * const Example = () => {\n * const { tr } = useI18n<App, \"en\">();\n * return <div>{tr(\"hello\")}</div>; //\n * }\n *\n * class App {\n *\n * en = $dictionary({\n * // { default: { hello: \"Hey\" } }\n * lazy: () => import(\"./translations/en.ts\"),\n * });\n *\n * home = $page({\n * path: \"/\",\n * component: Example,\n * })\n * }\n *\n * run(App);\n * ```\n */\nexport const $dictionary = <T extends Record<string, string>>(\n options: DictionaryPrimitiveOptions<T>,\n): DictionaryPrimitive<T> => {\n return createPrimitive(DictionaryPrimitive<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface DictionaryPrimitiveOptions<T extends Record<string, string>> {\n lang?: string;\n name?: string;\n lazy: () => Async<{ default: T }>;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class DictionaryPrimitive<\n T extends Record<string, string>,\n> extends Primitive<DictionaryPrimitiveOptions<T>> {\n protected provider = $inject(I18nProvider);\n protected onInit() {\n this.provider.registry.push({\n target: this.config.service.name,\n name: this.options.name ?? this.config.propertyKey,\n lang: this.options.lang ?? this.config.propertyKey,\n loader: async () => {\n const mod = await this.options.lazy();\n return mod.default;\n },\n translations: {},\n });\n }\n}\n\n$dictionary[KIND] = DictionaryPrimitive;\n","import { useInject, useStore } from \"@alepha/react\";\nimport type { DictionaryPrimitive } from \"../primitives/$dictionary.ts\";\nimport { I18nProvider } from \"../providers/I18nProvider.ts\";\n\n/**\n * Hook to access the i18n service.\n */\nexport const useI18n = <\n S extends object,\n K extends keyof ServiceDictionary<S>,\n>(): I18nProvider<S, K> => {\n useStore(\"alepha.react.i18n.lang\");\n return useInject(I18nProvider<S, K>);\n};\n\nexport type ServiceDictionary<T extends object> = {\n [K in keyof T]: T[K] extends DictionaryPrimitive<infer U> ? U : never;\n};\n","import type { TypeBoxError } from \"alepha\";\nimport type { DateTime } from \"alepha/datetime\";\nimport { useI18n } from \"../hooks/useI18n.ts\";\n\nexport interface LocalizeProps {\n value: string | number | Date | DateTime | TypeBoxError;\n /**\n * Options for number formatting (when value is a number)\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\n */\n number?: Intl.NumberFormatOptions;\n /**\n * Options for date formatting (when value is a Date or DateTime)\n * Can be:\n * - A dayjs format string (e.g., \"LLL\", \"YYYY-MM-DD\", \"dddd, MMMM D YYYY\")\n * - \"fromNow\" for relative time (e.g., \"2 hours ago\")\n * - Intl.DateTimeFormatOptions for native formatting\n * @see https://day.js.org/docs/en/display/format\n * @see https://day.js.org/docs/en/display/from-now\n */\n date?: string | \"fromNow\" | Intl.DateTimeFormatOptions;\n /**\n * Timezone to display dates in (when value is a Date or DateTime)\n * Uses IANA timezone names (e.g., \"America/New_York\", \"Europe/Paris\", \"Asia/Tokyo\")\n * @see https://day.js.org/docs/en/timezone/timezone\n */\n timezone?: string;\n}\n\nconst Localize = (props: LocalizeProps) => {\n const i18n = useI18n();\n return i18n.l(props.value, props);\n};\n\nexport default Localize;\n","import { $module } from \"alepha\";\nimport { $dictionary } from \"./primitives/$dictionary.ts\";\nimport { I18nProvider } from \"./providers/I18nProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type { LocalizeProps } from \"./components/Localize.tsx\";\nexport { default as Localize } from \"./components/Localize.tsx\";\nexport * from \"./primitives/$dictionary.ts\";\nexport * from \"./hooks/useI18n.ts\";\nexport * from \"./providers/I18nProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n export interface State {\n \"alepha.react.i18n.lang\"?: string;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Add i18n support to your Alepha React application. SSR and CSR compatible.\n *\n * It supports lazy loading of translations and provides a context to access the current language.\n *\n * @module alepha.react.i18n\n */\nexport const AlephaReactI18n = $module({\n name: \"alepha.react.i18n\",\n primitives: [$dictionary],\n services: [I18nProvider],\n});\n"],"mappings":";;;;;;;AAMA,IAAa,eAAb,MAGE;CACA,AAAU,MAAM,SAAS;CACzB,AAAU,SAAS,QAAQ,OAAO;CAClC,AAAU,mBAAmB,QAAQ,iBAAiB;CAEtD,AAAU,SAAS,QAAQ;EACzB,MAAM;EACN,QAAQ,EAAE,MAAM;EACjB,CAAC;CAEF,AAAgB,WAMX,EAAE;CAEP,UAAU,EACR,cAAc,MACf;CAED,AAAO,aACL,IAAI,KAAK,eAAe,KAAK,KAAK;CAEpC,AAAO,eACL,IAAI,KAAK,aAAa,KAAK,KAAK;CAElC,IAAW,YAAY;EACrB,MAAM,4BAAY,IAAI,KAAa;AAEnC,OAAK,MAAM,QAAQ,KAAK,SACtB,WAAU,IAAI,KAAK,KAAK;AAE1B,YAAU,IAAI,KAAK,QAAQ,aAAa;AAExC,SAAO,MAAM,KAAK,UAAU;;CAG9B,cAAc;AACZ,OAAK,eAAe;;CAGtB,AAAmB,WAAW,MAAM;EAClC,IAAI;EACJ,UAAU;EACV,SAAS,OAAO,EAAE,cAAc;AAC9B,QAAK,OAAO,MAAM,IAAI,0BAA0B,KAAK,OAAO,IAAI,QAAQ,CAAC;;EAE5E,CAAC;CAEF,AAAmB,UAAU,MAAM;EACjC,IAAI;EACJ,SAAS,YAAY;AACnB,OAAI,KAAK,OAAO,WAAW,EAAE;IAE3B,MAAM,aAAa,KAAK,OAAO,KAAK;AACpC,QAAI,WACF,MAAK,OAAO,MAAM,IAAI,0BAA0B,WAAW;AAG7D,SAAK,MAAM,QAAQ,KAAK,SACtB,KACE,KAAK,SAAS,KAAK,QACnB,KAAK,SAAS,KAAK,QAAQ,cAC3B;AACA,UAAK,IAAI,MAAM,oBAAoB;MACjC,MAAM,KAAK;MACX,MAAM,KAAK;MACX,QAAQ,KAAK;MACd,CAAC;AACF,UAAK,eAAe,MAAM,KAAK,QAAQ;;AAG3C;;AAGF,QAAK,MAAM,QAAQ,KAAK,SACtB,MAAK,eAAe,MAAM,KAAK,QAAQ;;EAG5C,CAAC;CAEF,AAAU,gBAAgB;AACxB,OAAK,eAAe,IAAI,KAAK,aAAa,KAAK,KAAK;AACpD,OAAK,aAAa,IAAI,KAAK,eAAe,KAAK,KAAK;AACpD,OAAK,iBAAiB,UAAU,KAAK,KAAK;AAC1C,eAAa,UAAU,KAAK,KAAK;;CAGnC,AAAO,UAAU,OAAO,SAAiB;AACvC,MAAI,KAAK,OAAO,WAAW,EAAE;AAC3B,QAAK,MAAM,QAAQ,KAAK,SACtB,KAAI,SAAS,KAAK,MAAM;AACtB,QAAI,OAAO,KAAK,KAAK,aAAa,CAAC,SAAS,EAC1C;AAEF,SAAK,eAAe,MAAM,KAAK,QAAQ;;AAG3C,QAAK,OAAO,IAAI,KAAK;;AAGvB,OAAK,OAAO,MAAM,IAAI,0BAA0B,KAAK;AACrD,OAAK,eAAe;;CAGtB,AAAmB,SAAS,MAAM;EAChC,IAAI;EACJ,SAAS,OAAO,EAAE,KAAK,YAAY;AACjC,OAAI,QAAQ,4BAA4B,KAAK,OAAO,WAAW,EAAE;IAC/D,IAAI,aAAa;AACjB,SAAK,MAAM,QAAQ,KAAK,SACtB,KAAI,UAAU,KAAK,MAAM;AACvB,SAAI,OAAO,KAAK,KAAK,aAAa,CAAC,SAAS,EAC1C;AAEF,UAAK,eAAe,MAAM,KAAK,QAAQ;AACvC,kBAAa;;AAIjB,SAAK,eAAe;AAEpB,QAAI,WACF,MAAK,OAAO,MAAM,IAAI,0BAA0B,MAAM;;;EAI7D,CAAC;CAEF,IAAW,OAAe;AACxB,SACE,KAAK,OAAO,MAAM,IAAI,yBAAyB,IAC/C,KAAK,QAAQ;;CAIjB,AAAO,aAAa,KAAa,OAAiB,EAAE,KAAK;AACvD,OAAK,MAAM,QAAQ,KAAK,SACtB,KAAI,KAAK,SAAS,KAAK,MACrB;OAAI,KAAK,aAAa,KACpB,QAAO,KAAK,OAAO,KAAK,aAAa,MAAM,KAAK;;AAKtD,OAAK,MAAM,QAAQ,KAAK,SACtB,KAAI,KAAK,SAAS,KAAK,QAAQ,cAC7B;OAAI,KAAK,aAAa,KACpB,QAAO,KAAK,OAAO,KAAK,aAAa,MAAM,KAAK;;AAKtD,SAAO;;CAGT,AAAgB,KACd,OACA,UAA+B,EAAE,KAC9B;AAEH,MAAI,OAAO,UAAU,SACnB,QAAO,IAAI,KAAK,aAAa,KAAK,MAAM,QAAQ,OAAO,CAAC,OAAO,MAAM;AAIvE,MACE,iBAAiB,QACjB,KAAK,iBAAiB,WAAW,MAAM,IACtC,OAAO,UAAU,YAAY,QAAQ,MACtC;GAEA,IAAI,KAAK,KAAK,iBAAiB,GAAG,MAAM;AAGxC,OAAI,QAAQ,SACV,MAAK,GAAG,GAAG,QAAQ,SAAS;AAI9B,OAAI,OAAO,QAAQ,SAAS,UAAU;AACpC,QAAI,QAAQ,SAAS,UACnB,QAAO,GAAG,OAAO,KAAK,KAAK,CAAC,SAAS;AAEvC,WAAO,GAAG,OAAO,KAAK,KAAK,CAAC,OAAO,QAAQ,KAAK;;AAIlD,OAAI,QAAQ,KACV,QAAO,IAAI,KAAK,eACd,KAAK,MACL,QAAQ,WACJ;IAAE,GAAG,QAAQ;IAAM,UAAU,QAAQ;IAAU,GAC/C,QAAQ,KACb,CAAC,OAAO,GAAG,QAAQ,CAAC;AAIvB,OAAI,QAAQ,SACV,QAAO,IAAI,KAAK,eAAe,KAAK,MAAM,EACxC,UAAU,QAAQ,UACnB,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC;AAIxB,UAAO,IAAI,KAAK,eAAe,KAAK,KAAK,CAAC,OAAO,GAAG,QAAQ,CAAC;;AAI/D,MAAI,iBAAiB,aACnB,QAAO,aAAa,eAAe,OAAO,KAAK,KAAK;AAItD,SAAO;;CAGT,AAAgB,MACd,KACA,UAGI,EAAE,KACH;EACH,MAAM,cAAc,KAAK,UAAU,KAAe,QAAQ,QAAQ,EAAE,CAAC;AACrE,MAAI,gBAAgB,OAAO,QAAQ,QACjC,QAAO,QAAQ;AAEjB,SAAO;;CAGT,AAAU,OAAO,MAAc,MAAwB;EACrD,IAAI,SAAS;AACb,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,IAC/B,UAAS,OAAO,QAAQ,IAAI,IAAI,KAAK,KAAK,GAAG;AAE/C,SAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrNX,MAAa,eACX,YAC2B;AAC3B,QAAO,gBAAgB,qBAAwB,QAAQ;;AAazD,IAAa,sBAAb,cAEU,UAAyC;CACjD,AAAU,WAAW,QAAQ,aAAa;CAC1C,AAAU,SAAS;AACjB,OAAK,SAAS,SAAS,KAAK;GAC1B,QAAQ,KAAK,OAAO,QAAQ;GAC5B,MAAM,KAAK,QAAQ,QAAQ,KAAK,OAAO;GACvC,MAAM,KAAK,QAAQ,QAAQ,KAAK,OAAO;GACvC,QAAQ,YAAY;AAElB,YADY,MAAM,KAAK,QAAQ,MAAM,EAC1B;;GAEb,cAAc,EAAE;GACjB,CAAC;;;AAIN,YAAY,QAAQ;;;;;;;AC7DpB,MAAa,gBAGc;AACzB,UAAS,yBAAyB;AAClC,QAAO,UAAU,aAAmB;;;;;ACiBtC,MAAM,YAAY,UAAyB;AAEzC,QADa,SAAS,CACV,EAAE,MAAM,OAAO,MAAM;;AAGnC,uBAAe;;;;;;;;;;;ACLf,MAAa,kBAAkB,QAAQ;CACrC,MAAM;CACN,YAAY,CAAC,YAAY;CACzB,UAAU,CAAC,aAAa;CACzB,CAAC"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as alepha55 from "alepha";
|
|
2
|
-
import {
|
|
2
|
+
import { Primitive, Static, TObject, TString, TUnion } from "alepha";
|
|
3
3
|
|
|
4
|
-
//#region ../alepha/src/websocket/
|
|
4
|
+
//#region ../alepha/src/websocket/primitives/$channel.d.ts
|
|
5
5
|
type TWSObject = TObject | TUnion;
|
|
6
6
|
/**
|
|
7
|
-
* Channel
|
|
7
|
+
* Channel primitive options
|
|
8
8
|
*/
|
|
9
|
-
interface
|
|
9
|
+
interface ChannelPrimitiveOptions<TClient extends TWSObject, TServer extends TWSObject> {
|
|
10
10
|
/**
|
|
11
11
|
* WebSocket endpoint path (e.g., "/ws/chat")
|
|
12
12
|
*/
|
|
@@ -37,7 +37,7 @@ interface ChannelDescriptorOptions<TClient extends TWSObject, TServer extends TW
|
|
|
37
37
|
out: TServer;
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
declare class
|
|
40
|
+
declare class ChannelPrimitive<TClient extends TWSObject, TServer extends TWSObject> extends Primitive<ChannelPrimitiveOptions<TClient, TServer>> {}
|
|
41
41
|
//#endregion
|
|
42
42
|
//#region ../alepha/src/logger/schemas/logEntrySchema.d.ts
|
|
43
43
|
declare const logEntrySchema: alepha55.TObject<{
|
|
@@ -168,9 +168,9 @@ interface UseRoomOptions<TClient extends TWSObject, TServer extends TWSObject> {
|
|
|
168
168
|
*/
|
|
169
169
|
roomId: string;
|
|
170
170
|
/**
|
|
171
|
-
* Channel
|
|
171
|
+
* Channel primitive defining the schemas
|
|
172
172
|
*/
|
|
173
|
-
channel:
|
|
173
|
+
channel: ChannelPrimitive<TClient, TServer>;
|
|
174
174
|
/**
|
|
175
175
|
* Handler for incoming messages from the server
|
|
176
176
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/websocket/hooks/useRoom.tsx"],"sourcesContent":["import { useAlepha, useInject } from \"@alepha/react\";\nimport type { Static } from \"alepha\";\nimport type {
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/websocket/hooks/useRoom.tsx"],"sourcesContent":["import { useAlepha, useInject } from \"@alepha/react\";\nimport type { Static } from \"alepha\";\nimport type { ChannelPrimitive, TWSObject } from \"alepha/websocket\";\nimport { WebSocketClient } from \"alepha/websocket\";\nimport { useEffect, useRef, useState } from \"react\";\n\n/**\n * UseRoom options\n */\nexport interface UseRoomOptions<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n /**\n * Room ID to connect to\n */\n roomId: string;\n\n /**\n * Channel primitive defining the schemas\n */\n channel: ChannelPrimitive<TClient, TServer>;\n\n /**\n * Handler for incoming messages from the server\n */\n handler: (message: Static<TClient>) => void;\n\n /**\n * Optional WebSocket URL override\n * Defaults to auto-detected URL based on window.location\n */\n url?: string;\n\n /**\n * Enable automatic reconnection on disconnect\n * @default true\n */\n autoReconnect?: boolean;\n\n /**\n * Reconnection interval in milliseconds\n * @default 3000\n */\n reconnectInterval?: number;\n\n /**\n * Maximum reconnection attempts (-1 for infinite)\n * @default 10\n */\n maxReconnectAttempts?: number;\n\n /**\n * Called when connection is established\n */\n onConnect?: () => void;\n\n /**\n * Called when connection is closed\n */\n onDisconnect?: () => void;\n\n /**\n * Called on connection error\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * UseRoom return value\n */\nexport interface UseRoomReturn<TServer extends TWSObject> {\n /**\n * Send a message to the server\n */\n send: (message: Static<TServer>) => Promise<void>;\n\n /**\n * Whether the connection is established\n */\n isConnected: boolean;\n\n /**\n * Whether the connection is in progress\n */\n isConnecting: boolean;\n\n /**\n * Whether there was an error\n */\n isError: boolean;\n\n /**\n * The error object if any\n */\n error?: Error;\n\n /**\n * Manually reconnect\n */\n reconnect: () => void;\n\n /**\n * Manually disconnect\n */\n disconnect: () => void;\n}\n\n/**\n * React hook for WebSocket room communication\n *\n * Provides automatic connection management, reconnection, and type-safe messaging\n * for WebSocket rooms using the injected WebSocketClient service.\n *\n * Multiple useRoom hooks on the same channel will share a single WebSocket connection.\n *\n * @example\n * ```tsx\n * const chat = useRoom({\n * roomId: \"room-123\",\n * channel: chatChannel,\n * handler: (message) => {\n * if (message.type === \"append\") {\n * setMessages(prev => [...prev, message]);\n * }\n * }\n * }, [roomId]);\n *\n * const sendMessage = async () => {\n * await chat.send({\n * content: \"Hello, world!\"\n * });\n * };\n * ```\n */\nexport const useRoom = <TClient extends TWSObject, TServer extends TWSObject>(\n options: UseRoomOptions<TClient, TServer>,\n deps: unknown[],\n): UseRoomReturn<TServer> => {\n const webSocketClient = useInject(WebSocketClient);\n const unsubscribeRef = useRef<(() => void) | null>(null);\n\n const [isConnected, setIsConnected] = useState(false);\n const [isConnecting, setIsConnecting] = useState(false);\n const [isError, setIsError] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n const {\n roomId,\n channel,\n handler,\n url,\n autoReconnect,\n reconnectInterval,\n maxReconnectAttempts,\n onConnect,\n onDisconnect,\n onError,\n } = options;\n\n useEffect(() => {\n // Subscribe to room\n const unsubscribe = webSocketClient.subscribe(roomId, channel, handler, {\n url,\n autoReconnect,\n reconnectInterval,\n maxReconnectAttempts,\n onConnect: () => {\n setIsConnected(true);\n setIsConnecting(false);\n setIsError(false);\n setError(undefined);\n onConnect?.();\n },\n onDisconnect: () => {\n setIsConnected(false);\n setIsConnecting(false);\n onDisconnect?.();\n },\n onError: (err) => {\n setIsError(true);\n setError(err);\n setIsConnecting(false);\n onError?.(err);\n },\n });\n\n unsubscribeRef.current = unsubscribe;\n\n // Get initial state from connection\n const connection = webSocketClient.getConnection(channel);\n if (connection) {\n setIsConnected(connection.isConnected);\n setIsConnecting(connection.isConnecting);\n setIsError(connection.isError);\n setError(connection.error);\n }\n\n // Cleanup on unmount or deps change\n return () => {\n unsubscribe();\n unsubscribeRef.current = null;\n };\n }, deps);\n\n const alepha = useAlepha();\n\n if (!alepha.isBrowser()) {\n return {\n send: async (_message: Static<TServer>) => {\n // No-op on server\n },\n isConnected: false,\n isConnecting: false,\n isError: false,\n error: undefined,\n reconnect: () => {\n // No-op on server\n },\n disconnect: () => {\n // No-op on server\n },\n };\n }\n\n return {\n send: async (message: Static<TServer>) => {\n await webSocketClient.send(roomId, channel, message);\n },\n isConnected,\n isConnecting,\n isError,\n error,\n reconnect: () => {\n const connection = webSocketClient.getConnection(channel);\n connection?.reconnect();\n },\n disconnect: () => {\n unsubscribeRef.current?.();\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuIA,MAAa,WACX,SACA,SAC2B;CAC3B,MAAM,kBAAkB,UAAU,gBAAgB;CAClD,MAAM,iBAAiB,OAA4B,KAAK;CAExD,MAAM,CAAC,aAAa,kBAAkB,SAAS,MAAM;CACrD,MAAM,CAAC,cAAc,mBAAmB,SAAS,MAAM;CACvD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,SAA4B,OAAU;CAEhE,MAAM,EACJ,QACA,SACA,SACA,KACA,eACA,mBACA,sBACA,WACA,cACA,YACE;AAEJ,iBAAgB;EAEd,MAAM,cAAc,gBAAgB,UAAU,QAAQ,SAAS,SAAS;GACtE;GACA;GACA;GACA;GACA,iBAAiB;AACf,mBAAe,KAAK;AACpB,oBAAgB,MAAM;AACtB,eAAW,MAAM;AACjB,aAAS,OAAU;AACnB,iBAAa;;GAEf,oBAAoB;AAClB,mBAAe,MAAM;AACrB,oBAAgB,MAAM;AACtB,oBAAgB;;GAElB,UAAU,QAAQ;AAChB,eAAW,KAAK;AAChB,aAAS,IAAI;AACb,oBAAgB,MAAM;AACtB,cAAU,IAAI;;GAEjB,CAAC;AAEF,iBAAe,UAAU;EAGzB,MAAM,aAAa,gBAAgB,cAAc,QAAQ;AACzD,MAAI,YAAY;AACd,kBAAe,WAAW,YAAY;AACtC,mBAAgB,WAAW,aAAa;AACxC,cAAW,WAAW,QAAQ;AAC9B,YAAS,WAAW,MAAM;;AAI5B,eAAa;AACX,gBAAa;AACb,kBAAe,UAAU;;IAE1B,KAAK;AAIR,KAAI,CAFW,WAAW,CAEd,WAAW,CACrB,QAAO;EACL,MAAM,OAAO,aAA8B;EAG3C,aAAa;EACb,cAAc;EACd,SAAS;EACT,OAAO;EACP,iBAAiB;EAGjB,kBAAkB;EAGnB;AAGH,QAAO;EACL,MAAM,OAAO,YAA6B;AACxC,SAAM,gBAAgB,KAAK,QAAQ,SAAS,QAAQ;;EAEtD;EACA;EACA;EACA;EACA,iBAAiB;AAEf,GADmB,gBAAgB,cAAc,QAAQ,EAC7C,WAAW;;EAEzB,kBAAkB;AAChB,kBAAe,WAAW;;EAE7B"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@alepha/react",
|
|
3
3
|
"description": "React components and hooks for building Alepha applications.",
|
|
4
4
|
"author": "Nicolas Foures",
|
|
5
|
-
"version": "0.13.
|
|
5
|
+
"version": "0.13.2",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=22.0.0"
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"@testing-library/react": "^16.3.0",
|
|
26
26
|
"@types/react": "^19.2.7",
|
|
27
27
|
"@types/react-dom": "^19.2.3",
|
|
28
|
-
"alepha": "0.13.
|
|
28
|
+
"alepha": "0.13.2",
|
|
29
29
|
"jsdom": "^27.2.0",
|
|
30
30
|
"react": "^19.2.0",
|
|
31
31
|
"vitest": "^4.0.14"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"alepha": "0.13.
|
|
34
|
+
"alepha": "0.13.2",
|
|
35
35
|
"react": "^19.2.0"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
@@ -56,30 +56,39 @@
|
|
|
56
56
|
"exports": {
|
|
57
57
|
"./auth": {
|
|
58
58
|
"types": "./dist/auth/index.js",
|
|
59
|
+
"react-native": "./dist/auth/index.browser.js",
|
|
59
60
|
"browser": "./dist/auth/index.browser.js",
|
|
60
|
-
"import": "./dist/auth/index.js"
|
|
61
|
+
"import": "./dist/auth/index.js",
|
|
62
|
+
"default": "./dist/auth/index.js"
|
|
61
63
|
},
|
|
62
64
|
".": {
|
|
63
65
|
"types": "./dist/core/index.js",
|
|
66
|
+
"react-native": "./dist/core/index.native.js",
|
|
64
67
|
"browser": "./dist/core/index.browser.js",
|
|
65
|
-
"import": "./dist/core/index.js"
|
|
68
|
+
"import": "./dist/core/index.js",
|
|
69
|
+
"default": "./dist/core/index.js"
|
|
66
70
|
},
|
|
67
71
|
"./form": {
|
|
68
72
|
"types": "./dist/form/index.js",
|
|
69
|
-
"import": "./dist/form/index.js"
|
|
73
|
+
"import": "./dist/form/index.js",
|
|
74
|
+
"default": "./dist/form/index.js"
|
|
70
75
|
},
|
|
71
76
|
"./head": {
|
|
72
77
|
"types": "./dist/head/index.js",
|
|
78
|
+
"react-native": "./dist/head/index.browser.js",
|
|
73
79
|
"browser": "./dist/head/index.browser.js",
|
|
74
|
-
"import": "./dist/head/index.js"
|
|
80
|
+
"import": "./dist/head/index.js",
|
|
81
|
+
"default": "./dist/head/index.js"
|
|
75
82
|
},
|
|
76
83
|
"./i18n": {
|
|
77
84
|
"types": "./dist/i18n/index.js",
|
|
78
|
-
"import": "./dist/i18n/index.js"
|
|
85
|
+
"import": "./dist/i18n/index.js",
|
|
86
|
+
"default": "./dist/i18n/index.js"
|
|
79
87
|
},
|
|
80
88
|
"./websocket": {
|
|
81
89
|
"types": "./dist/websocket/index.js",
|
|
82
|
-
"import": "./dist/websocket/index.js"
|
|
90
|
+
"import": "./dist/websocket/index.js",
|
|
91
|
+
"default": "./dist/websocket/index.js"
|
|
83
92
|
}
|
|
84
93
|
},
|
|
85
94
|
"module": "./dist/index.js"
|
package/src/auth/index.ts
CHANGED
|
@@ -29,6 +29,6 @@ declare module "@alepha/react" {
|
|
|
29
29
|
*/
|
|
30
30
|
export const AlephaReactAuth = $module({
|
|
31
31
|
name: "alepha.react.auth",
|
|
32
|
-
|
|
32
|
+
primitives: [$auth],
|
|
33
33
|
services: [AlephaReact, AlephaServerLinks, AlephaServerAuth, ReactAuthProvider, ReactAuth],
|
|
34
34
|
});
|
|
@@ -8,7 +8,7 @@ export class ReactAuthProvider {
|
|
|
8
8
|
handler: async ({ request, state }) => {
|
|
9
9
|
if (request?.user) {
|
|
10
10
|
const { token, realm, ...user } = request.user; // do not send token and realm to the client
|
|
11
|
-
this.alepha.
|
|
11
|
+
this.alepha.store.set("alepha.server.request.user", user); // for hydration, browser, etc...
|
|
12
12
|
state.user = user;
|
|
13
13
|
}
|
|
14
14
|
},
|
|
@@ -40,7 +40,7 @@ export class ReactAuth {
|
|
|
40
40
|
* Alias for `alepha.state.get("user")`
|
|
41
41
|
*/
|
|
42
42
|
public get user(): UserAccountToken | undefined {
|
|
43
|
-
return this.alepha.
|
|
43
|
+
return this.alepha.store.get("alepha.server.request.user");
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
public async ping() {
|
|
@@ -48,8 +48,8 @@ export class ReactAuth {
|
|
|
48
48
|
schema: { response: userinfoResponseSchema },
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
this.alepha.
|
|
52
|
-
this.alepha.
|
|
51
|
+
this.alepha.store.set("alepha.server.request.apiLinks", data.api);
|
|
52
|
+
this.alepha.store.set("alepha.server.request.user", data.user);
|
|
53
53
|
|
|
54
54
|
return data.user;
|
|
55
55
|
}
|
|
@@ -78,8 +78,8 @@ export class ReactAuth {
|
|
|
78
78
|
},
|
|
79
79
|
);
|
|
80
80
|
|
|
81
|
-
this.alepha.
|
|
82
|
-
this.alepha.
|
|
81
|
+
this.alepha.store.set("alepha.server.request.apiLinks", data.api);
|
|
82
|
+
this.alepha.store.set("alepha.server.request.user", data.user);
|
|
83
83
|
|
|
84
84
|
return data;
|
|
85
85
|
}
|