@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.
Files changed (37) hide show
  1. package/dist/auth/index.browser.js +5 -5
  2. package/dist/auth/index.browser.js.map +1 -1
  3. package/dist/auth/index.d.ts +105 -103
  4. package/dist/auth/index.js +5 -5
  5. package/dist/auth/index.js.map +1 -1
  6. package/dist/core/index.browser.js +407 -142
  7. package/dist/core/index.browser.js.map +1 -1
  8. package/dist/core/index.d.ts +144 -116
  9. package/dist/core/index.js +409 -145
  10. package/dist/core/index.js.map +1 -1
  11. package/dist/core/index.native.js +24 -2
  12. package/dist/core/index.native.js.map +1 -1
  13. package/dist/form/index.d.ts +14 -6
  14. package/dist/form/index.js +32 -12
  15. package/dist/form/index.js.map +1 -1
  16. package/dist/head/index.d.ts +18 -18
  17. package/dist/head/index.js +5 -1
  18. package/dist/head/index.js.map +1 -1
  19. package/dist/i18n/index.d.ts +25 -25
  20. package/dist/i18n/index.js +4 -3
  21. package/dist/i18n/index.js.map +1 -1
  22. package/dist/websocket/index.d.ts +1 -1
  23. package/package.json +22 -23
  24. package/src/auth/hooks/useAuth.ts +1 -0
  25. package/src/auth/services/ReactAuth.ts +6 -4
  26. package/src/core/components/ErrorViewer.tsx +378 -130
  27. package/src/core/components/NestedView.tsx +16 -11
  28. package/src/core/contexts/AlephaProvider.tsx +41 -0
  29. package/src/core/contexts/RouterLayerContext.ts +2 -0
  30. package/src/core/hooks/useAction.ts +4 -1
  31. package/src/core/index.shared.ts +1 -0
  32. package/src/core/primitives/$page.ts +15 -2
  33. package/src/core/providers/ReactPageProvider.ts +6 -7
  34. package/src/core/providers/ReactServerProvider.ts +2 -6
  35. package/src/form/services/FormModel.ts +81 -26
  36. package/src/head/index.ts +2 -1
  37. package/src/i18n/providers/I18nProvider.ts +4 -2
@@ -234,7 +234,11 @@ const useHead = (options) => {
234
234
  const AlephaReactHead = $module({
235
235
  name: "alepha.react.head",
236
236
  primitives: [$head],
237
- services: [AlephaReact, ServerHeadProvider]
237
+ services: [
238
+ AlephaReact,
239
+ ServerHeadProvider,
240
+ HeadProvider
241
+ ]
238
242
  });
239
243
 
240
244
  //#endregion
@@ -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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\");\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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\");\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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"}
@@ -1,4 +1,4 @@
1
- import * as alepha16 from "alepha";
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: alepha16.HookPrimitive<"start">;
19
- protected readonly onStop: alepha16.HookPrimitive<"stop">;
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: alepha16.TObject<{
142
- level: alepha16.TUnsafe<"SILENT" | "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR">;
143
- message: alepha16.TString;
144
- service: alepha16.TString;
145
- module: alepha16.TString;
146
- context: alepha16.TOptional<alepha16.TString>;
147
- app: alepha16.TOptional<alepha16.TString>;
148
- data: alepha16.TOptional<alepha16.TAny>;
149
- timestamp: alepha16.TNumber;
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: alepha16.TObject<{
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: alepha16.TOptional<alepha16.TString>;
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 SimpleFormatterProvider}
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: alepha16.TOptional<alepha16.TUnsafe<"json" | "pretty" | "raw">>;
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-cookies/primitives/$cookie.d.ts
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-cookies/index.d.ts
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<alepha16.TString>;
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: alepha16.HookPrimitive<"server:onRequest">;
335
- protected readonly onStart: alepha16.HookPrimitive<"start">;
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: alepha16.HookPrimitive<"state: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: alepha16.Service<alepha16.Module>;
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
@@ -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") {
@@ -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 SimpleFormatterProvider}
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.6",
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.1",
21
- "react-dom": "^19.2.1"
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.0",
26
- "@types/react": "^19.2.7",
27
- "@types/react-dom": "^19.2.3",
28
- "alepha": "0.13.6",
29
- "jsdom": "^27.2.0",
30
- "react": "^19.2.1",
31
- "vitest": "^4.0.15"
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.6",
35
- "react": "^19.2.0"
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.js",
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.js",
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.js",
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.js",
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.js",
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.js",
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
  }
@@ -17,6 +17,7 @@ export const useAuth = <T extends object = any>() => {
17
17
  username?: string;
18
18
  password?: string;
19
19
  redirect?: string;
20
+ realm?: string;
20
21
  [extra: string]: any;
21
22
  } = {},
22
23
  ) => {
@@ -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