@alepha/react 0.13.6 → 0.13.8
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 +105 -103
- package/dist/auth/index.js +5 -5
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.browser.js +407 -142
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +144 -116
- package/dist/core/index.js +409 -145
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +24 -2
- package/dist/core/index.native.js.map +1 -1
- package/dist/form/index.d.ts +14 -6
- package/dist/form/index.js +32 -12
- package/dist/form/index.js.map +1 -1
- package/dist/head/index.d.ts +18 -18
- package/dist/head/index.js +5 -1
- package/dist/head/index.js.map +1 -1
- package/dist/i18n/index.d.ts +25 -25
- package/dist/i18n/index.js +4 -3
- package/dist/i18n/index.js.map +1 -1
- package/dist/websocket/index.d.ts +1 -1
- package/package.json +22 -23
- package/src/auth/hooks/useAuth.ts +1 -0
- package/src/auth/services/ReactAuth.ts +6 -4
- package/src/core/components/ErrorViewer.tsx +378 -130
- package/src/core/components/NestedView.tsx +16 -11
- package/src/core/contexts/AlephaProvider.tsx +41 -0
- package/src/core/contexts/RouterLayerContext.ts +2 -0
- package/src/core/hooks/useAction.ts +4 -1
- package/src/core/index.shared.ts +1 -0
- package/src/core/primitives/$page.ts +15 -2
- package/src/core/providers/ReactPageProvider.ts +6 -7
- package/src/core/providers/ReactServerProvider.ts +2 -6
- package/src/form/services/FormModel.ts +81 -26
- package/src/head/index.ts +2 -1
- package/src/i18n/providers/I18nProvider.ts +4 -2
package/dist/head/index.js
CHANGED
|
@@ -234,7 +234,11 @@ const useHead = (options) => {
|
|
|
234
234
|
const AlephaReactHead = $module({
|
|
235
235
|
name: "alepha.react.head",
|
|
236
236
|
primitives: [$head],
|
|
237
|
-
services: [
|
|
237
|
+
services: [
|
|
238
|
+
AlephaReact,
|
|
239
|
+
ServerHeadProvider,
|
|
240
|
+
HeadProvider
|
|
241
|
+
]
|
|
238
242
|
});
|
|
239
243
|
|
|
240
244
|
//#endregion
|
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/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?: Array<Head | (() => Head)> = [];\n\n public fillHead(state: ReactRouterState) {\n state.head = {\n ...state.head,\n };\n\n for (const h of this.global ?? []) {\n const head =\n typeof h === \"function\" ? h() : h;\n state.head = {\n ...state.head,\n ...head,\n meta: [...(state.head.meta ?? []), ...(head.meta ?? [])],\n };\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 = [\n ...(this.provider.global ?? []),\n this.options,\n ];\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 if (head.link) {\n for (const link of head.link) {\n headContent += `<link rel=\"${this.escapeHtml(link.rel)}\" href=\"${this.escapeHtml(link.href)}\">\\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 if (head.link) {\n for (const it of head.link) {\n const { rel, href } = it;\n let link = document.querySelector(`link[rel=\"${rel}\"][href=\"${href}\"]`);\n if (!link) {\n link = document.createElement(\"link\");\n link.setAttribute(\"rel\", rel);\n link.setAttribute(\"href\", href);\n document.head.appendChild(link);\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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"}
|
|
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?: Array<Head | (() => Head)> = [];\n\n public fillHead(state: ReactRouterState) {\n state.head = {\n ...state.head,\n };\n\n for (const h of this.global ?? []) {\n const head =\n typeof h === \"function\" ? h() : h;\n state.head = {\n ...state.head,\n ...head,\n meta: [...(state.head.meta ?? []), ...(head.meta ?? [])],\n };\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 = [\n ...(this.provider.global ?? []),\n this.options,\n ];\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 if (head.link) {\n for (const link of head.link) {\n headContent += `<link rel=\"${this.escapeHtml(link.rel)}\" href=\"${this.escapeHtml(link.href)}\">\\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 if (head.link) {\n for (const it of head.link) {\n const { rel, href } = it;\n let link = document.querySelector(`link[rel=\"${rel}\"][href=\"${href}\"]`);\n if (!link) {\n link = document.createElement(\"link\");\n link.setAttribute(\"rel\", rel);\n link.setAttribute(\"href\", href);\n document.head.appendChild(link);\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\";\nimport { HeadProvider } from \"./providers/HeadProvider.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, HeadProvider],\n});\n"],"mappings":"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"}
|
package/dist/i18n/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as alepha7 from "alepha";
|
|
2
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";
|
|
@@ -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:
|
|
19
|
-
protected readonly onStop:
|
|
18
|
+
protected readonly onStart: alepha7.HookPrimitive<"start">;
|
|
19
|
+
protected readonly onStop: alepha7.HookPrimitive<"stop">;
|
|
20
20
|
setLocale(locale: string): void;
|
|
21
21
|
isDateTime(value: unknown): value is DateTime;
|
|
22
22
|
/**
|
|
@@ -135,18 +135,18 @@ interface LocalizeProps {
|
|
|
135
135
|
*/
|
|
136
136
|
timezone?: string;
|
|
137
137
|
}
|
|
138
|
-
declare const Localize: (props: LocalizeProps) => string;
|
|
138
|
+
declare const Localize: (props: LocalizeProps) => string | number;
|
|
139
139
|
//#endregion
|
|
140
140
|
//#region ../../../alepha/src/logger/schemas/logEntrySchema.d.ts
|
|
141
|
-
declare const logEntrySchema:
|
|
142
|
-
level:
|
|
143
|
-
message:
|
|
144
|
-
service:
|
|
145
|
-
module:
|
|
146
|
-
context:
|
|
147
|
-
app:
|
|
148
|
-
data:
|
|
149
|
-
timestamp:
|
|
141
|
+
declare const logEntrySchema: alepha7.TObject<{
|
|
142
|
+
level: alepha7.TUnsafe<"SILENT" | "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR">;
|
|
143
|
+
message: alepha7.TString;
|
|
144
|
+
service: alepha7.TString;
|
|
145
|
+
module: alepha7.TString;
|
|
146
|
+
context: alepha7.TOptional<alepha7.TString>;
|
|
147
|
+
app: alepha7.TOptional<alepha7.TString>;
|
|
148
|
+
data: alepha7.TOptional<alepha7.TAny>;
|
|
149
|
+
timestamp: alepha7.TNumber;
|
|
150
150
|
}>;
|
|
151
151
|
type LogEntry = Static<typeof logEntrySchema>;
|
|
152
152
|
//#endregion
|
|
@@ -188,7 +188,7 @@ declare class Logger implements LoggerInterface {
|
|
|
188
188
|
}
|
|
189
189
|
//#endregion
|
|
190
190
|
//#region ../../../alepha/src/logger/index.d.ts
|
|
191
|
-
declare const envSchema:
|
|
191
|
+
declare const envSchema: alepha7.TObject<{
|
|
192
192
|
/**
|
|
193
193
|
* Default log level for the application.
|
|
194
194
|
*
|
|
@@ -205,14 +205,14 @@ declare const envSchema: alepha16.TObject<{
|
|
|
205
205
|
* LOG_LEVEL=my.module.name:debug,info # Set debug level for my.module.name and info for all other modules
|
|
206
206
|
* LOG_LEVEL=alepha:trace, info # Set trace level for all alepha modules and info for all other modules
|
|
207
207
|
*/
|
|
208
|
-
LOG_LEVEL:
|
|
208
|
+
LOG_LEVEL: alepha7.TOptional<alepha7.TString>;
|
|
209
209
|
/**
|
|
210
210
|
* Built-in log formats.
|
|
211
211
|
* - "json" - JSON format, useful for structured logging and log aggregation. {@link JsonFormatterProvider}
|
|
212
|
-
* - "pretty" - Simple text format, human-readable, with colors. {@link
|
|
212
|
+
* - "pretty" - Simple text format, human-readable, with colors. {@link PrettyFormatterProvider}
|
|
213
213
|
* - "raw" - Raw format, no formatting, just the message. {@link RawFormatterProvider}
|
|
214
214
|
*/
|
|
215
|
-
LOG_FORMAT:
|
|
215
|
+
LOG_FORMAT: alepha7.TOptional<alepha7.TUnsafe<"json" | "pretty" | "raw">>;
|
|
216
216
|
}>;
|
|
217
217
|
declare module "alepha" {
|
|
218
218
|
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
@@ -230,7 +230,7 @@ declare module "alepha" {
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
//#endregion
|
|
233
|
-
//#region ../../../alepha/src/server
|
|
233
|
+
//#region ../../../alepha/src/server/cookies/primitives/$cookie.d.ts
|
|
234
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;
|
|
@@ -283,7 +283,7 @@ interface Cookie {
|
|
|
283
283
|
domain?: string;
|
|
284
284
|
}
|
|
285
285
|
//#endregion
|
|
286
|
-
//#region ../../../alepha/src/server
|
|
286
|
+
//#region ../../../alepha/src/server/cookies/index.d.ts
|
|
287
287
|
declare module "alepha/server" {
|
|
288
288
|
interface ServerRequest {
|
|
289
289
|
cookies: Cookies;
|
|
@@ -312,7 +312,7 @@ declare class I18nProvider<S extends object, K$1 extends keyof ServiceDictionary
|
|
|
312
312
|
protected log: Logger;
|
|
313
313
|
protected alepha: Alepha;
|
|
314
314
|
protected dateTimeProvider: DateTimeProvider;
|
|
315
|
-
protected cookie: AbstractCookiePrimitive<
|
|
315
|
+
protected cookie: AbstractCookiePrimitive<alepha7.TString>;
|
|
316
316
|
readonly registry: Array<{
|
|
317
317
|
target: string;
|
|
318
318
|
name: string;
|
|
@@ -331,14 +331,14 @@ 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:
|
|
335
|
-
protected readonly onStart:
|
|
334
|
+
protected readonly onRender: alepha7.HookPrimitive<"server:onRequest">;
|
|
335
|
+
protected readonly onStart: alepha7.HookPrimitive<"start">;
|
|
336
336
|
protected refreshLocale(): void;
|
|
337
337
|
setLang: (lang: string) => Promise<void>;
|
|
338
|
-
protected readonly mutate:
|
|
338
|
+
protected readonly mutate: alepha7.HookPrimitive<"state:mutate">;
|
|
339
339
|
get lang(): string;
|
|
340
340
|
translate: (key: string, args?: string[]) => string;
|
|
341
|
-
readonly l: (value: I18nLocalizeType, options?: I18nLocalizeOptions) => string;
|
|
341
|
+
readonly l: (value: I18nLocalizeType, options?: I18nLocalizeOptions) => string | number;
|
|
342
342
|
readonly tr: (key: keyof ServiceDictionary<S>[K$1], options?: {
|
|
343
343
|
args?: string[];
|
|
344
344
|
default?: string;
|
|
@@ -431,7 +431,7 @@ declare module "alepha" {
|
|
|
431
431
|
*
|
|
432
432
|
* @module alepha.react.i18n
|
|
433
433
|
*/
|
|
434
|
-
declare const AlephaReactI18n:
|
|
434
|
+
declare const AlephaReactI18n: alepha7.Service<alepha7.Module>;
|
|
435
435
|
//#endregion
|
|
436
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
|
@@ -11,7 +11,8 @@ var I18nProvider = class {
|
|
|
11
11
|
dateTimeProvider = $inject(DateTimeProvider);
|
|
12
12
|
cookie = $cookie({
|
|
13
13
|
name: "lang",
|
|
14
|
-
schema: t.text()
|
|
14
|
+
schema: t.text(),
|
|
15
|
+
ttl: [1, "year"]
|
|
15
16
|
});
|
|
16
17
|
registry = [];
|
|
17
18
|
options = { fallbackLang: "en" };
|
|
@@ -97,8 +98,8 @@ var I18nProvider = class {
|
|
|
97
98
|
return key;
|
|
98
99
|
};
|
|
99
100
|
l = (value, options = {}) => {
|
|
100
|
-
if (typeof value === "number") return new Intl.NumberFormat(this.lang, options.number).format(value);
|
|
101
|
-
if (value instanceof Date || this.dateTimeProvider.isDateTime(value) || typeof value === "string" && options.date) {
|
|
101
|
+
if (typeof value === "number" && !options.date) return new Intl.NumberFormat(this.lang, options.number).format(value);
|
|
102
|
+
if (value instanceof Date || this.dateTimeProvider.isDateTime(value) || typeof value === "string" && options.date || typeof value === "number" && options.date) {
|
|
102
103
|
let dt = this.dateTimeProvider.of(value);
|
|
103
104
|
if (options.timezone) dt = dt.tz(options.timezone);
|
|
104
105
|
if (typeof options.date === "string") {
|
package/dist/i18n/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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":"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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 ttl: [1, \"year\"]\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\" && !options.date) {\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 (typeof value === \"number\" && 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":"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"}
|
|
@@ -75,7 +75,7 @@ declare const envSchema$2: alepha55.TObject<{
|
|
|
75
75
|
/**
|
|
76
76
|
* Built-in log formats.
|
|
77
77
|
* - "json" - JSON format, useful for structured logging and log aggregation. {@link JsonFormatterProvider}
|
|
78
|
-
* - "pretty" - Simple text format, human-readable, with colors. {@link
|
|
78
|
+
* - "pretty" - Simple text format, human-readable, with colors. {@link PrettyFormatterProvider}
|
|
79
79
|
* - "raw" - Raw format, no formatting, just the message. {@link RawFormatterProvider}
|
|
80
80
|
*/
|
|
81
81
|
LOG_FORMAT: alepha55.TOptional<alepha55.TUnsafe<"json" | "pretty" | "raw">>;
|
package/package.json
CHANGED
|
@@ -2,37 +2,37 @@
|
|
|
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.8",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=22.0.0"
|
|
9
9
|
},
|
|
10
10
|
"license": "MIT",
|
|
11
|
-
"main": "./dist/index.js",
|
|
12
|
-
"types": "./dist/index.d.ts",
|
|
11
|
+
"main": "./dist/core/index.js",
|
|
12
|
+
"types": "./dist/core/index.d.ts",
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
15
|
"src",
|
|
16
16
|
"assets"
|
|
17
17
|
],
|
|
18
|
-
"bin": "./dist/index.js",
|
|
19
18
|
"dependencies": {
|
|
20
|
-
"@vitejs/plugin-react": "^5.1.
|
|
21
|
-
"react-dom": "^19
|
|
19
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
20
|
+
"react-dom": "^19"
|
|
22
21
|
},
|
|
23
22
|
"devDependencies": {
|
|
23
|
+
"@biomejs/biome": "^2.3.10",
|
|
24
24
|
"@testing-library/dom": "^10.4.1",
|
|
25
|
-
"@testing-library/react": "^16.3.
|
|
26
|
-
"@types/react": "^19
|
|
27
|
-
"@types/react-dom": "^19
|
|
28
|
-
"alepha": "0.13.
|
|
29
|
-
"jsdom": "^27.
|
|
30
|
-
"react": "^19.2.
|
|
31
|
-
"vitest": "^4.0.
|
|
25
|
+
"@testing-library/react": "^16.3.1",
|
|
26
|
+
"@types/react": "^19",
|
|
27
|
+
"@types/react-dom": "^19",
|
|
28
|
+
"alepha": "0.13.8",
|
|
29
|
+
"jsdom": "^27.3.0",
|
|
30
|
+
"react": "^19.2.3",
|
|
31
|
+
"vitest": "^4.0.16"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"alepha": "0.13.
|
|
35
|
-
"react": "^19
|
|
34
|
+
"alepha": "0.13.8",
|
|
35
|
+
"react": "^19"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"lint": "alepha lint",
|
|
@@ -55,41 +55,40 @@
|
|
|
55
55
|
],
|
|
56
56
|
"exports": {
|
|
57
57
|
"./auth": {
|
|
58
|
-
"types": "./dist/auth/index.
|
|
58
|
+
"types": "./dist/auth/index.d.ts",
|
|
59
59
|
"react-native": "./dist/auth/index.browser.js",
|
|
60
60
|
"browser": "./dist/auth/index.browser.js",
|
|
61
61
|
"import": "./dist/auth/index.js",
|
|
62
62
|
"default": "./dist/auth/index.js"
|
|
63
63
|
},
|
|
64
64
|
".": {
|
|
65
|
-
"types": "./dist/core/index.
|
|
65
|
+
"types": "./dist/core/index.d.ts",
|
|
66
66
|
"react-native": "./dist/core/index.native.js",
|
|
67
67
|
"browser": "./dist/core/index.browser.js",
|
|
68
68
|
"import": "./dist/core/index.js",
|
|
69
69
|
"default": "./dist/core/index.js"
|
|
70
70
|
},
|
|
71
71
|
"./form": {
|
|
72
|
-
"types": "./dist/form/index.
|
|
72
|
+
"types": "./dist/form/index.d.ts",
|
|
73
73
|
"import": "./dist/form/index.js",
|
|
74
74
|
"default": "./dist/form/index.js"
|
|
75
75
|
},
|
|
76
76
|
"./head": {
|
|
77
|
-
"types": "./dist/head/index.
|
|
77
|
+
"types": "./dist/head/index.d.ts",
|
|
78
78
|
"react-native": "./dist/head/index.browser.js",
|
|
79
79
|
"browser": "./dist/head/index.browser.js",
|
|
80
80
|
"import": "./dist/head/index.js",
|
|
81
81
|
"default": "./dist/head/index.js"
|
|
82
82
|
},
|
|
83
83
|
"./i18n": {
|
|
84
|
-
"types": "./dist/i18n/index.
|
|
84
|
+
"types": "./dist/i18n/index.d.ts",
|
|
85
85
|
"import": "./dist/i18n/index.js",
|
|
86
86
|
"default": "./dist/i18n/index.js"
|
|
87
87
|
},
|
|
88
88
|
"./websocket": {
|
|
89
|
-
"types": "./dist/websocket/index.
|
|
89
|
+
"types": "./dist/websocket/index.d.ts",
|
|
90
90
|
"import": "./dist/websocket/index.js",
|
|
91
91
|
"default": "./dist/websocket/index.js"
|
|
92
92
|
}
|
|
93
|
-
}
|
|
94
|
-
"module": "./dist/index.js"
|
|
93
|
+
}
|
|
95
94
|
}
|
|
@@ -71,18 +71,20 @@ export class ReactAuth {
|
|
|
71
71
|
username?: string;
|
|
72
72
|
password?: string;
|
|
73
73
|
redirect?: string;
|
|
74
|
+
realm?: string;
|
|
74
75
|
[extra: string]: any;
|
|
75
76
|
},
|
|
76
77
|
): Promise<Tokens> {
|
|
78
|
+
const realmParam = options.realm ? `&realm=${encodeURIComponent(options.realm)}` : "";
|
|
79
|
+
|
|
77
80
|
if (options.username || options.password) {
|
|
78
81
|
const { data } = await this.httpClient.fetch(
|
|
79
|
-
`${options.hostname || ""}${alephaServerAuthRoutes.token}?provider=${provider}`,
|
|
82
|
+
`${options.hostname || ""}${alephaServerAuthRoutes.token}?provider=${provider}${realmParam}`,
|
|
80
83
|
{
|
|
81
84
|
method: "POST",
|
|
82
85
|
body: JSON.stringify({
|
|
83
86
|
username: options.username,
|
|
84
87
|
password: options.password,
|
|
85
|
-
...options,
|
|
86
88
|
}),
|
|
87
89
|
schema: { response: tokenResponseSchema },
|
|
88
90
|
},
|
|
@@ -102,7 +104,7 @@ export class ReactAuth {
|
|
|
102
104
|
? window.location.origin + browser.transitioning.to
|
|
103
105
|
: window.location.href);
|
|
104
106
|
|
|
105
|
-
const href = `${window.location.origin}${alephaServerAuthRoutes.login}?provider=${provider}&redirect_uri=${encodeURIComponent(redirect)}`;
|
|
107
|
+
const href = `${window.location.origin}${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${encodeURIComponent(redirect)}`;
|
|
106
108
|
|
|
107
109
|
if (browser.transitioning) {
|
|
108
110
|
throw new Redirection(href);
|
|
@@ -113,7 +115,7 @@ export class ReactAuth {
|
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
throw new Redirection(
|
|
116
|
-
`${alephaServerAuthRoutes.login}?provider=${provider}&redirect_uri=${options.redirect || "/"}`,
|
|
118
|
+
`${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${options.redirect || "/"}`,
|
|
117
119
|
);
|
|
118
120
|
}
|
|
119
121
|
|