@owlmeans/client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +688 -0
  3. package/build/app.d.ts +4 -0
  4. package/build/app.d.ts.map +1 -0
  5. package/build/app.js +10 -0
  6. package/build/app.js.map +1 -0
  7. package/build/components/index.d.ts +4 -0
  8. package/build/components/index.d.ts.map +1 -0
  9. package/build/components/index.js +3 -0
  10. package/build/components/index.js.map +1 -0
  11. package/build/components/modal.d.ts +7 -0
  12. package/build/components/modal.d.ts.map +1 -0
  13. package/build/components/modal.js +66 -0
  14. package/build/components/modal.js.map +1 -0
  15. package/build/components/toggle.d.ts +3 -0
  16. package/build/components/toggle.d.ts.map +1 -0
  17. package/build/components/toggle.js +20 -0
  18. package/build/components/toggle.js.map +1 -0
  19. package/build/components/types.d.ts +50 -0
  20. package/build/components/types.d.ts.map +1 -0
  21. package/build/components/types.js +2 -0
  22. package/build/components/types.js.map +1 -0
  23. package/build/consts.d.ts +5 -0
  24. package/build/consts.d.ts.map +1 -0
  25. package/build/consts.js +5 -0
  26. package/build/consts.js.map +1 -0
  27. package/build/context.d.ts +8 -0
  28. package/build/context.d.ts.map +1 -0
  29. package/build/context.js +47 -0
  30. package/build/context.js.map +1 -0
  31. package/build/debug.d.ts +4 -0
  32. package/build/debug.d.ts.map +1 -0
  33. package/build/debug.js +12 -0
  34. package/build/debug.js.map +1 -0
  35. package/build/errors.d.ts +18 -0
  36. package/build/errors.d.ts.map +1 -0
  37. package/build/errors.js +34 -0
  38. package/build/errors.js.map +1 -0
  39. package/build/helper.d.ts +4 -0
  40. package/build/helper.d.ts.map +1 -0
  41. package/build/helper.js +30 -0
  42. package/build/helper.js.map +1 -0
  43. package/build/index.d.ts +15 -0
  44. package/build/index.d.ts.map +1 -0
  45. package/build/index.js +15 -0
  46. package/build/index.js.map +1 -0
  47. package/build/module.d.ts +3 -0
  48. package/build/module.d.ts.map +1 -0
  49. package/build/module.js +4 -0
  50. package/build/module.js.map +1 -0
  51. package/build/navigate.d.ts +3 -0
  52. package/build/navigate.d.ts.map +1 -0
  53. package/build/navigate.js +39 -0
  54. package/build/navigate.js.map +1 -0
  55. package/build/router.d.ts +5 -0
  56. package/build/router.d.ts.map +1 -0
  57. package/build/router.js +53 -0
  58. package/build/router.js.map +1 -0
  59. package/build/services/debug.d.ts +6 -0
  60. package/build/services/debug.d.ts.map +1 -0
  61. package/build/services/debug.js +83 -0
  62. package/build/services/debug.js.map +1 -0
  63. package/build/services/index.d.ts +2 -0
  64. package/build/services/index.d.ts.map +1 -0
  65. package/build/services/index.js +2 -0
  66. package/build/services/index.js.map +1 -0
  67. package/build/store.d.ts +4 -0
  68. package/build/store.d.ts.map +1 -0
  69. package/build/store.js +49 -0
  70. package/build/store.js.map +1 -0
  71. package/build/types.d.ts +57 -0
  72. package/build/types.d.ts.map +1 -0
  73. package/build/types.js +2 -0
  74. package/build/types.js.map +1 -0
  75. package/build/utils/index.d.ts +4 -0
  76. package/build/utils/index.d.ts.map +1 -0
  77. package/build/utils/index.js +4 -0
  78. package/build/utils/index.js.map +1 -0
  79. package/build/utils/module.d.ts +3 -0
  80. package/build/utils/module.d.ts.map +1 -0
  81. package/build/utils/module.js +8 -0
  82. package/build/utils/module.js.map +1 -0
  83. package/build/utils/route.d.ts +16 -0
  84. package/build/utils/route.d.ts.map +1 -0
  85. package/build/utils/route.js +65 -0
  86. package/build/utils/route.js.map +1 -0
  87. package/build/utils/router.d.ts +16 -0
  88. package/build/utils/router.d.ts.map +1 -0
  89. package/build/utils/router.js +36 -0
  90. package/build/utils/router.js.map +1 -0
  91. package/build/value.d.ts +4 -0
  92. package/build/value.d.ts.map +1 -0
  93. package/build/value.js +48 -0
  94. package/build/value.js.map +1 -0
  95. package/package.json +66 -0
  96. package/src/app.tsx +17 -0
  97. package/src/components/index.ts +4 -0
  98. package/src/components/modal.ts +89 -0
  99. package/src/components/toggle.ts +22 -0
  100. package/src/components/types.ts +57 -0
  101. package/src/consts.ts +7 -0
  102. package/src/context.ts +61 -0
  103. package/src/debug.ts +14 -0
  104. package/src/errors.ts +44 -0
  105. package/src/helper.tsx +51 -0
  106. package/src/index.ts +15 -0
  107. package/src/module.ts +6 -0
  108. package/src/navigate.ts +51 -0
  109. package/src/router.ts +78 -0
  110. package/src/services/debug.ts +102 -0
  111. package/src/services/index.ts +2 -0
  112. package/src/store.ts +57 -0
  113. package/src/types.ts +72 -0
  114. package/src/utils/index.ts +4 -0
  115. package/src/utils/module.ts +14 -0
  116. package/src/utils/route.tsx +82 -0
  117. package/src/utils/router.ts +60 -0
  118. package/src/value.ts +60 -0
  119. package/tsconfig.json +15 -0
  120. package/tsconfig.tsbuildinfo +1 -0
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@owlmeans/client",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc -b",
7
+ "dev": "sleep 54 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
8
+ "watch": "tsc -b -w --preserveWatchOutput --pretty"
9
+ },
10
+ "main": "build/index.js",
11
+ "module": "build/index.js",
12
+ "types": "build/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./build/index.js",
16
+ "require": "./build/index.js",
17
+ "default": "./build/index.js",
18
+ "module": "./build/index.js",
19
+ "types": "./build/index.d.ts"
20
+ },
21
+ "./utils": {
22
+ "import": "./build/utils/index.js",
23
+ "require": "./build/utils/index.js",
24
+ "default": "./build/utils/index.js",
25
+ "module": "./build/utils/index.js",
26
+ "types": "./build/utils/index.d.ts"
27
+ }
28
+ },
29
+ "dependencies": {
30
+ "@owlmeans/auth": "0.1.0",
31
+ "@owlmeans/client-context": "0.1.0",
32
+ "@owlmeans/client-module": "0.1.0",
33
+ "@owlmeans/client-resource": "0.1.0",
34
+ "@owlmeans/config": "0.1.0",
35
+ "@owlmeans/context": "0.1.0",
36
+ "@owlmeans/error": "0.1.0",
37
+ "@owlmeans/module": "0.1.0",
38
+ "@owlmeans/resource": "0.1.0",
39
+ "@owlmeans/state": "0.1.0"
40
+ },
41
+ "peerDependencies": {
42
+ "@remix-run/router": "*",
43
+ "react": "*",
44
+ "react-router": "*",
45
+ "react-router-dom": "*"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "@remix-run/router": {
49
+ "optional": true
50
+ },
51
+ "react-router-dom": {
52
+ "optional": true
53
+ }
54
+ },
55
+ "devDependencies": {
56
+ "@types/react": "^18.3.11",
57
+ "nodemon": "^3.1.7",
58
+ "npm-check": "^6.0.1",
59
+ "react-router": "^6.26.2",
60
+ "typescript": "^5.6.3"
61
+ },
62
+ "private": false,
63
+ "publishConfig": {
64
+ "access": "public"
65
+ }
66
+ }
package/src/app.tsx ADDED
@@ -0,0 +1,17 @@
1
+
2
+ import type { FC } from 'react'
3
+ import type { AppProps } from './types.js'
4
+ import { useEffect, useState } from 'react'
5
+ import { Context } from './context.js'
6
+ import { Router } from './router.js'
7
+
8
+ export const App: FC<AppProps> = ({ context, provide, children }) => {
9
+ const [render, rerender] = useState(0)
10
+
11
+ useEffect(() => context.registerRerenderer(() => rerender(render + 1)), [])
12
+
13
+ return <Context value={context}>
14
+ {children}
15
+ {provide != null ? <Router provide={provide} /> : undefined}
16
+ </Context>
17
+ }
@@ -0,0 +1,4 @@
1
+
2
+ export type * from './types.js'
3
+ export * from './toggle.js'
4
+ export * from './modal.js'
@@ -0,0 +1,89 @@
1
+ import { createService } from '@owlmeans/context'
2
+ import { DEF_MODAL_ALIAS } from '../consts.js'
3
+ import type { ModalService, ModalServiceAppend, ModalStackLayer } from './types.js'
4
+ import type { ClientConfig } from '@owlmeans/client-context'
5
+ import type { ClientContext } from '../types.js'
6
+ import { useContext } from '../context.js'
7
+ import { useNavigate } from '../navigate.js'
8
+ import { useEffect } from 'react'
9
+
10
+ export const createModalService = (alias: string = DEF_MODAL_ALIAS): ModalService => {
11
+ const close = () => {
12
+ service.stack.pop()
13
+ service.toggle.close()
14
+ if (service.stack.length > 0) {
15
+ // @TODO Figure out how to remove this hack
16
+ setTimeout(() => service.toggle.open(), 500)
17
+ }
18
+ }
19
+
20
+ const service: ModalService = createService<ModalService>(alias, {
21
+ stack: [],
22
+
23
+ link: toggle => service.toggle = toggle,
24
+
25
+ layer: () => {
26
+ if (service.stack.length === 0) {
27
+ return undefined
28
+ }
29
+
30
+ return service.stack[service.stack.length - 1]
31
+ },
32
+
33
+ request: async Com => {
34
+ const layer: ModalStackLayer = { Com }
35
+ const defer = new Promise<any | null>((resolve, reject) => {
36
+ layer.resolve = resolve
37
+ layer.reject = reject
38
+ })
39
+ service.stack.push(layer)
40
+
41
+ service.toggle.open()
42
+
43
+ const result = await defer
44
+ service.stack.pop()
45
+
46
+ return result
47
+ },
48
+
49
+ error: error => {
50
+ service.layer()?.reject?.(error)
51
+ close()
52
+ },
53
+
54
+ response: result => {
55
+ service.layer()?.resolve?.(result)
56
+ close()
57
+ },
58
+
59
+ cancel: () => {
60
+ service.layer()?.resolve?.(null)
61
+ close()
62
+ }
63
+ }, service => async () => { service.initialized = true })
64
+
65
+ return service
66
+ }
67
+
68
+ export const appendModalService = <C extends ClientConfig, T extends ClientContext<C>>(
69
+ ctx: T, alias: string = DEF_MODAL_ALIAS
70
+ ): T & ModalServiceAppend => {
71
+ const service = createModalService(alias)
72
+
73
+ const _ctx = ctx as T & ModalServiceAppend
74
+
75
+ _ctx.registerService(service)
76
+
77
+ _ctx.modal = () => _ctx.service(alias)
78
+
79
+ return _ctx
80
+ }
81
+
82
+ export const useSetupModalNavigator = () => {
83
+ const context = useContext()
84
+ const navigator = useNavigate()
85
+ useEffect(() => {
86
+ const modal = context.modal()
87
+ modal.navigator = navigator
88
+ }, [])
89
+ }
@@ -0,0 +1,22 @@
1
+ import { useMemo, useState } from 'react'
2
+ import type { Toggleable } from './types.js'
3
+
4
+ export const useToggle = (opened?: boolean): Toggleable => {
5
+ if (opened === undefined) {
6
+ opened = false
7
+ }
8
+ const [_opened, setOpened] = useState<boolean>(opened)
9
+ return useMemo<Toggleable>(() => {
10
+ const _handler: Toggleable = {
11
+ opened: _opened === undefined ? false : _opened,
12
+ set: opened => {
13
+ setOpened(_handler.opened = opened)
14
+ },
15
+ open: () => { _handler.set(true) },
16
+ close: () => { _handler.set(false) },
17
+ toggle: () => { _handler.set(!_handler.opened) }
18
+ }
19
+
20
+ return _handler
21
+ }, [])
22
+ }
@@ -0,0 +1,57 @@
1
+ import type { InitializedService, LazyService } from '@owlmeans/context'
2
+ import type { FC } from 'react'
3
+ import type { ClientContext, Navigator } from '../types.js'
4
+
5
+ export interface Toggleable {
6
+ opened: boolean
7
+ open: () => void
8
+ close: () => void
9
+ set: (opened: boolean) => void
10
+ toggle: () => void
11
+ }
12
+
13
+ export interface ModalService extends InitializedService {
14
+ toggle: Toggleable
15
+ stack: ModalStackLayer[]
16
+ navigator?: Navigator
17
+ layer: () => ModalStackLayer | undefined
18
+ link: (toggle: Toggleable) => void
19
+ request: <T>(Com: FC<ModalBodyProps>) => Promise<T | null>
20
+ error: (error: Error) => void
21
+ response: <T>(value: T) => void
22
+ cancel: () => void
23
+ }
24
+
25
+ export interface ModalStackLayer {
26
+ Com: FC<ModalBodyProps>
27
+ resolve?: (v: unknown | null) => void,
28
+ reject?: (e: Error) => void
29
+ }
30
+
31
+ export interface ModalBodyProps {
32
+ modal?: ModalService
33
+ }
34
+
35
+ export interface ModalServiceAppend {
36
+ modal: () => ModalService
37
+ }
38
+
39
+ export interface DebugService extends LazyService {
40
+ Debug: FC<ModalBodyProps>
41
+ items: DebugMenuItem[]
42
+ addItem: (item: DebugMenuItem) => void
43
+ erase: (states: string[]) => Promise<void>
44
+ select: (alias: string) => void
45
+ open: () => void
46
+ }
47
+
48
+ export interface DebugMenuItem {
49
+ alias: string
50
+ title: string
51
+ Com?: FC<ModalBodyProps>
52
+ action?: (modal: ModalService, context: ClientContext) => Promise<void | string>
53
+ }
54
+
55
+ export interface DebugServiceAppend {
56
+ debug: () => DebugService | undefined
57
+ }
package/src/consts.ts ADDED
@@ -0,0 +1,7 @@
1
+
2
+ export const DEF_MODAL_ALIAS = 'modal'
3
+ export const DEF_DEBUG_ALIAS = 'debug'
4
+
5
+ export const DEBUGGER_FLAG = 'debugger'
6
+ export const DEBUG_CONFIG_KEY = 'debugger'
7
+
package/src/context.ts ADDED
@@ -0,0 +1,61 @@
1
+
2
+ import { createContext, useContext as useCtx } from 'react'
3
+ import type { Context as ReactContext } from 'react'
4
+ import { makeClientContext as makeBasicContext, PLUGINS } from '@owlmeans/client-context'
5
+ import type { ClientConfig } from '@owlmeans/client-context'
6
+ import { AppType, CONFIG_RECORD, Layer } from '@owlmeans/context'
7
+ import type { ClientContext } from './types.js'
8
+ import { appendStateResource } from '@owlmeans/state'
9
+ import { appendModalService } from './components/modal.js'
10
+ import { appendDebugService } from './services/debug.js'
11
+ import { appendConfigResource, PLUGIN_RECORD } from '@owlmeans/config'
12
+
13
+ const defaultCfg: ClientConfig = {
14
+ services: {},
15
+ brand: {},
16
+ layer: Layer.Service,
17
+ trusted: [],
18
+ [CONFIG_RECORD]: [],
19
+ ready: false,
20
+ service: '',
21
+ debug: {},
22
+ type: AppType.Frontend,
23
+ }
24
+
25
+ export const makeClientContext = <C extends ClientConfig, T extends ClientContext<C> = ClientContext<C>>(cfg: C): T => {
26
+ const context = makeBasicContext(cfg) as T
27
+ appendStateResource<C, T>(context)
28
+ appendModalService<C, T>(context)
29
+ appendConfigResource<C, T>(context)
30
+ appendConfigResource<C, T>(context, PLUGINS, PLUGIN_RECORD)
31
+ appendDebugService<C, T>(context)
32
+
33
+ if (context.registerRerenderer == null) {
34
+ const rerenderers: CallableFunction[] = []
35
+
36
+ context.registerRerenderer = listener => {
37
+ rerenderers.push(listener)
38
+ return () => {
39
+ const index = rerenderers.indexOf(listener)
40
+ if (index >= 0) {
41
+ rerenderers.splice(index, 1)
42
+ }
43
+ }
44
+ }
45
+ context.rerender = () => {
46
+ rerenderers.forEach(callback => callback())
47
+ }
48
+ }
49
+
50
+ context.makeContext = makeClientContext as typeof context.makeContext
51
+
52
+ return context
53
+ }
54
+
55
+ export const ClientContextContainer = createContext(makeClientContext(defaultCfg))
56
+
57
+ export const Context = ClientContextContainer.Provider
58
+
59
+ export const useContext = <C extends ClientConfig, T extends ClientContext<C>>() => useCtx<T>(
60
+ ClientContextContainer as unknown as ReactContext<T>
61
+ )
package/src/debug.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { ClientConfig } from '@owlmeans/client-context'
2
+ import { DEBUG_CONFIG_KEY } from './consts.js'
3
+ import type { ClientContext, DebugConfigRecord } from './types.js'
4
+
5
+ export const appendStateDebug = <C extends ClientConfig, T extends ClientContext<C>>(ctx: T, state: string) => {
6
+ let debugRecord = ctx.cfg.records
7
+ .find(record => record.id === DEBUG_CONFIG_KEY) as DebugConfigRecord
8
+ if (debugRecord == null) {
9
+ debugRecord = { id: DEBUG_CONFIG_KEY }
10
+ ctx.cfg.records.push(debugRecord)
11
+ }
12
+ debugRecord.states = debugRecord.states ?? []
13
+ debugRecord.states.push(state)
14
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,44 @@
1
+
2
+ import { ResilientError } from '@owlmeans/error'
3
+
4
+ export class ClientError extends ResilientError {
5
+ public static override typeName: string = 'ClientError'
6
+
7
+ constructor(message: string = 'error') {
8
+ super(ClientError.typeName, `client:${message}`)
9
+ }
10
+ }
11
+
12
+ export class ComponentError extends ClientError {
13
+ public static override typeName: string = `${ClientError.typeName}Component`
14
+
15
+ constructor(message: string = 'error') {
16
+ super(`component:${message}`)
17
+ this.type = ComponentError.typeName
18
+ }
19
+ }
20
+
21
+ export class ComponentPropError extends ComponentError {
22
+ public static override typeName: string = `${ComponentError.typeName}Prop`
23
+
24
+ constructor(message: string = 'error') {
25
+ super(`prop:${message}`)
26
+ this.type = ComponentPropError.typeName
27
+ }
28
+ }
29
+
30
+ export class ComponentPropUndefined extends ComponentPropError {
31
+ public static override typeName: string = `${ComponentPropError.typeName}Undefined`
32
+
33
+ constructor(message: string = 'error') {
34
+ super(`undefined:${message}`)
35
+ this.type = ComponentPropUndefined.typeName
36
+ }
37
+ }
38
+
39
+
40
+ ResilientError.registerErrorClass(ClientError)
41
+ ResilientError.registerErrorClass(ComponentError)
42
+ ResilientError.registerErrorClass(ComponentPropError)
43
+ ResilientError.registerErrorClass(ComponentPropUndefined)
44
+ ResilientError.registerErrorClass(ComponentPropUndefined)
package/src/helper.tsx ADDED
@@ -0,0 +1,51 @@
1
+ import type { AbstractRequest, AbstractResponse } from '@owlmeans/module'
2
+ import type { ModuleContextParams, RoutedComponent, ClientContext } from './types.js'
3
+ import type { RefedModuleHandler } from '@owlmeans/client-module'
4
+ import { HandledRenderer } from './utils/route.js'
5
+ import { isValidElement } from 'react'
6
+ import type { PropsWithChildren } from 'react'
7
+ import { ModuleContext } from './utils/module.js'
8
+ import type { ClientConfig } from '@owlmeans/client-context'
9
+ import { assertContext } from '@owlmeans/context'
10
+
11
+ type Config = ClientConfig
12
+ interface Context<C extends Config = Config> extends ClientContext<C> { }
13
+
14
+ export const handler = <T extends {}>(
15
+ Component: HandledRenderer<T>, preprender?: boolean
16
+ ): RefedModuleHandler<T> => ref => <
17
+ R extends AbstractRequest = AbstractRequest,
18
+ P extends AbstractResponse<HandledRenderer<T>> = AbstractResponse<HandledRenderer<T>>
19
+ >(req: R, res: P): any => {
20
+ const location = `client-handler:${ref.ref?.getAlias() ?? 'unknown'}`
21
+ if (ref.ref == null) {
22
+ throw new SyntaxError('Module reference is not provided')
23
+ }
24
+ const ctx = assertContext<Config, Context>(ref.ref.ctx as Context, location)
25
+ if (ctx == null) {
26
+ throw new SyntaxError('Module context is not provided')
27
+ }
28
+ if (isValidElement(Component)) {
29
+ res.resolve(Component)
30
+ return Component
31
+ }
32
+
33
+ if (preprender === true) {
34
+ const Renderer = Component as RendererType
35
+ const element = <Renderer {...req} context={ctx} />
36
+ res.resolve(element as HandledRenderer<T>)
37
+
38
+ return element
39
+ }
40
+
41
+ const Renderer: RoutedComponent = ({ children, ...props }) => {
42
+ const Renderer = Component as RendererType
43
+ return <ModuleContext.Provider value={props}>
44
+ <Renderer {...props}>{children}</Renderer>
45
+ </ModuleContext.Provider>
46
+ }
47
+
48
+ return Renderer
49
+ }
50
+
51
+ type RendererType = HandledRenderer<PropsWithChildren<ModuleContextParams>> & RoutedComponent
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+
2
+ export * from './context.js'
3
+ export * from './components/index.js'
4
+ export * from './services/index.js'
5
+ export * from './helper.js'
6
+ export * from './navigate.js'
7
+ export * from './module.js'
8
+ export * from './router.js'
9
+ export * from './types.js'
10
+ export * from './store.js'
11
+ export * from './value.js'
12
+ export * from './consts.js'
13
+ export * from './debug.js'
14
+ export * from './app.js'
15
+ export * from './errors.js'
package/src/module.ts ADDED
@@ -0,0 +1,6 @@
1
+
2
+ import { useContext } from 'react'
3
+ import { ModuleContext } from './utils/index.js'
4
+ import type { ModuleContextParams } from './types.js'
5
+
6
+ export const useModule = <T extends {} = {}>() => useContext(ModuleContext) as ModuleContextParams<T>
@@ -0,0 +1,51 @@
1
+ import { useMemo } from 'react'
2
+ import { useLocation, useNavigate as useNav } from 'react-router'
3
+ import type { Navigator } from './types.js'
4
+ import { ModuleOutcome } from '@owlmeans/module'
5
+ import { useContext } from './context.js'
6
+ import type { ClientModule } from '@owlmeans/client-module'
7
+
8
+ export const useNavigate = (): Navigator => {
9
+ const context = useContext()
10
+ const navigate = useNav()
11
+ const location = useLocation()
12
+ const navigator: Navigator = useMemo(() => {
13
+ const navigator: Navigator = {
14
+ _navigate: navigate,
15
+
16
+ navigate: async (module, request) => {
17
+ const [url, ok] = await module.call(request)
18
+
19
+ if (ok === ModuleOutcome.Ok) {
20
+ navigate(url, {
21
+ state: {
22
+ ...module.route.route, silent: request?.silent
23
+ },
24
+ replace: request?.replace ?? false
25
+ })
26
+ }
27
+ },
28
+
29
+ go: async (alias, request) =>
30
+ navigator.navigate(context.module<ClientModule<string>>(alias), request),
31
+
32
+ press: (alias, request) => () => {
33
+ void navigator.go(alias, request)
34
+ },
35
+
36
+ back: async () => {
37
+ navigate(-1)
38
+ },
39
+
40
+ pressBack: () => () => {
41
+ void navigator.back()
42
+ },
43
+
44
+ location: () => location
45
+ }
46
+
47
+ return navigator
48
+ }, [navigate])
49
+
50
+ return navigator
51
+ }
package/src/router.ts ADDED
@@ -0,0 +1,78 @@
1
+ import type { FC } from 'react'
2
+ import type { RouterModel, RouterProps } from './types.js'
3
+ import type { RouteObject } from 'react-router'
4
+ import { createElement, useEffect, useRef, useState } from 'react'
5
+ import { buildModuleTree, initializeRouter, visitModuleTree } from './utils/router.js'
6
+ import { createRouteRenderer } from './utils/route.js'
7
+ import { RouterProvider } from 'react-router'
8
+ import type { Router as RemixRouter } from '@remix-run/router'
9
+ import { useContext } from './context.js'
10
+ import type { ClientConfig } from '@owlmeans/client-context'
11
+ import type { ClientContext } from './types.js'
12
+ import { assertContext } from '@owlmeans/context'
13
+ import type { BasicConfig, BasicContext } from '@owlmeans/context'
14
+
15
+ type Config = ClientConfig
16
+ interface Context<C extends Config = Config> extends ClientContext<C> { }
17
+
18
+ export const Router: FC<RouterProps> = ({ provide }) => {
19
+ const progress = useRef(false)
20
+ const context = useContext()
21
+
22
+ const [router, setRouter] = useState<RemixRouter>(
23
+ (typeof provide === 'function' ? undefined : provide) as RemixRouter
24
+ )
25
+ // @TODO We expect that this use memo will do the trick and we don't need to useEffect
26
+ // @TODO Show debug only in debug mode
27
+ useEffect(() => {
28
+ if (!progress.current && !context.cfg.ready) {
29
+ progress.current = true
30
+
31
+ if (typeof provide === 'function') {
32
+ initializeRouter(context as any).then(router => provide(router))
33
+ .then(routes => setRouter(routes))
34
+ }
35
+ }
36
+ }, [])
37
+
38
+ // @TODO We need to allow drawing something here :)
39
+ if (router == null) {
40
+ return undefined
41
+ }
42
+
43
+ return createElement(RouterProvider, { router })
44
+ }
45
+
46
+ export const makeRouterModel = (): RouterModel => {
47
+ const location = `client-router`
48
+ const model: RouterModel = {
49
+ routes: [],
50
+ resolve: async (ctx) => {
51
+ const context = assertContext<Config, Context>(ctx as Context, location)
52
+ const moduleTree = buildModuleTree(context as Context)
53
+
54
+ const reactRoutes: RouteObject[] = await visitModuleTree(moduleTree, async (module, children) => {
55
+ const ctx = assertContext<BasicConfig, BasicContext<BasicConfig>>(
56
+ (module.ctx ?? context) as BasicContext<BasicConfig>, location
57
+ )
58
+ await module.route.resolve(ctx)
59
+
60
+ const renderer = module.handle != null
61
+ ? { Component: createRouteRenderer({ context, module, hasChildren: children.length > 0 }) }
62
+ : undefined
63
+
64
+ const route: RouteObject = {
65
+ ...(module.route.route.default ? { index: true } as any : undefined),
66
+ ...(!module.route.route.default ? { path: module.getPath(true), children } : undefined),
67
+ ...renderer
68
+ }
69
+
70
+ return route
71
+ })
72
+
73
+ return model.routes = reactRoutes
74
+ }
75
+ }
76
+
77
+ return model
78
+ }