@furystack/shades 10.0.6 → 11.0.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.
- package/esm/compile-route.d.ts +2 -0
- package/esm/compile-route.d.ts.map +1 -0
- package/esm/compile-route.js +4 -0
- package/esm/compile-route.js.map +1 -0
- package/esm/components/lazy-load.d.ts +2 -2
- package/esm/components/lazy-load.d.ts.map +1 -1
- package/esm/components/lazy-load.spec.js +2 -2
- package/esm/components/lazy-load.spec.js.map +1 -1
- package/esm/components/link-to-route.d.ts +1 -1
- package/esm/components/link-to-route.d.ts.map +1 -1
- package/esm/components/link-to-route.js +3 -3
- package/esm/components/link-to-route.js.map +1 -1
- package/esm/components/route-link.d.ts +2 -2
- package/esm/components/route-link.d.ts.map +1 -1
- package/esm/components/router.d.ts +4 -4
- package/esm/components/router.d.ts.map +1 -1
- package/esm/components/router.js +4 -4
- package/esm/components/router.js.map +1 -1
- package/esm/components/router.spec.js +1 -1
- package/esm/components/router.spec.js.map +1 -1
- package/esm/index.d.ts +5 -4
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +5 -4
- package/esm/index.js.map +1 -1
- package/esm/models/render-options.d.ts +2 -2
- package/esm/models/render-options.d.ts.map +1 -1
- package/esm/models/shade-component.d.ts.map +1 -1
- package/esm/services/location-service.d.ts +6 -7
- package/esm/services/location-service.d.ts.map +1 -1
- package/esm/services/location-service.js +24 -26
- package/esm/services/location-service.js.map +1 -1
- package/esm/services/location-service.spec.js +21 -21
- package/esm/services/location-service.spec.js.map +1 -1
- package/esm/services/resource-manager.d.ts +4 -5
- package/esm/services/resource-manager.d.ts.map +1 -1
- package/esm/services/resource-manager.js +18 -5
- package/esm/services/resource-manager.js.map +1 -1
- package/esm/services/resource-manager.spec.js +55 -7
- package/esm/services/resource-manager.spec.js.map +1 -1
- package/esm/services/screen-service.d.ts +1 -2
- package/esm/services/screen-service.d.ts.map +1 -1
- package/esm/services/screen-service.js +1 -1
- package/esm/services/screen-service.js.map +1 -1
- package/esm/shade-component.d.ts +5 -5
- package/esm/shade-component.d.ts.map +1 -1
- package/esm/shade-component.js +8 -5
- package/esm/shade-component.js.map +1 -1
- package/esm/shade-resources.integration.spec.js +6 -5
- package/esm/shade-resources.integration.spec.js.map +1 -1
- package/esm/shade.d.ts +1 -1
- package/esm/shade.d.ts.map +1 -1
- package/esm/shade.js +5 -5
- package/esm/shade.js.map +1 -1
- package/esm/shades.integration.spec.js +79 -4
- package/esm/shades.integration.spec.js.map +1 -1
- package/esm/styled-element.d.ts.map +1 -1
- package/esm/styled-shade.d.ts.map +1 -1
- package/esm/styled-shade.js +13 -7
- package/esm/styled-shade.js.map +1 -1
- package/package.json +8 -8
- package/src/compile-route.ts +6 -0
- package/src/components/lazy-load.spec.tsx +3 -3
- package/src/components/link-to-route.tsx +4 -4
- package/src/components/router.spec.tsx +1 -1
- package/src/components/router.tsx +7 -7
- package/src/index.ts +5 -4
- package/src/models/render-options.ts +2 -2
- package/src/services/location-service.spec.ts +23 -23
- package/src/services/location-service.tsx +29 -31
- package/src/services/resource-manager.spec.ts +74 -7
- package/src/services/resource-manager.ts +29 -10
- package/src/services/screen-service.ts +1 -2
- package/src/shade-component.ts +16 -12
- package/src/shade-resources.integration.spec.tsx +7 -5
- package/src/shade.ts +6 -6
- package/src/shades.integration.spec.tsx +107 -4
- package/src/styled-shade.ts +13 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styled-element.d.ts","sourceRoot":"","sources":["../src/styled-element.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAEjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"styled-element.d.ts","sourceRoot":"","sources":["../src/styled-element.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAEjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,SAAS,MAAM,GAAG,CAAC,iBAAiB,WAC/D,QAAQ,UACT,OAAO,CAAC,mBAAmB,CAAC,KACnC,CAAC,CAAC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,EAAE,YAAY,EAAE,YAAY,KAAK,GAAG,CAAC,OAAO,CAWtG,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styled-shade.d.ts","sourceRoot":"","sources":["../src/styled-shade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"styled-shade.d.ts","sourceRoot":"","sources":["../src/styled-shade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAG7D;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,YAAY,KAAK,GAAG,CAAC,OAAO,WAC/E,CAAC,UACF,OAAO,CAAC,mBAAmB,CAAC,KAgB9B,CACP,CAAA"}
|
package/esm/styled-shade.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasStyle } from './shade-component.js';
|
|
1
2
|
/**
|
|
2
3
|
* Creates a shortcut for a specific custom Shade element with additional styles
|
|
3
4
|
* @param element The element instance
|
|
@@ -6,13 +7,18 @@
|
|
|
6
7
|
*/
|
|
7
8
|
export const styledShade = (element, styles) => {
|
|
8
9
|
return ((props, childrenList) => {
|
|
9
|
-
const mergedProps =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
const mergedProps = hasStyle(props)
|
|
11
|
+
? {
|
|
12
|
+
...props,
|
|
13
|
+
style: {
|
|
14
|
+
...props.style,
|
|
15
|
+
...styles,
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
: {
|
|
19
|
+
...props,
|
|
20
|
+
style: styles,
|
|
21
|
+
};
|
|
16
22
|
return element(mergedProps, childrenList);
|
|
17
23
|
});
|
|
18
24
|
};
|
package/esm/styled-shade.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styled-shade.js","sourceRoot":"","sources":["../src/styled-shade.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"styled-shade.js","sourceRoot":"","sources":["../src/styled-shade.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAE/C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,OAAU,EACV,MAAoC,EACpC,EAAE;IACF,OAAO,CAAC,CAAC,KAAU,EAAE,YAA2B,EAAE,EAAE;QAClD,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC;YACjC,CAAC,CAAC;gBACE,GAAG,KAAK;gBACR,KAAK,EAAE;oBACL,GAAG,KAAK,CAAC,KAAK;oBACd,GAAG,MAAM;iBACV;aACF;YACH,CAAC,CAAC;gBACE,GAAG,KAAK;gBACR,KAAK,EAAE,MAAM;aACd,CAAA;QACL,OAAO,OAAO,CAAC,WAAW,EAAE,YAAY,CAAC,CAAA;IAC3C,CAAC,CAAM,CAAA;AACT,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furystack/shades",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.0",
|
|
4
4
|
"description": "Google Authentication Provider for FuryStack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -38,16 +38,16 @@
|
|
|
38
38
|
"homepage": "https://github.com/furystack/furystack",
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/jsdom": "^21.1.7",
|
|
41
|
-
"@types/node": "^20.14.
|
|
41
|
+
"@types/node": "^20.14.10",
|
|
42
42
|
"jsdom": "^24.1.0",
|
|
43
|
-
"typescript": "^5.
|
|
44
|
-
"vitest": "^
|
|
43
|
+
"typescript": "^5.5.3",
|
|
44
|
+
"vitest": "^2.0.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@furystack/inject": "^
|
|
48
|
-
"@furystack/rest": "^
|
|
49
|
-
"@furystack/utils": "^
|
|
50
|
-
"path-to-regexp": "^
|
|
47
|
+
"@furystack/inject": "^12.0.0",
|
|
48
|
+
"@furystack/rest": "^8.0.0",
|
|
49
|
+
"@furystack/utils": "^8.0.0",
|
|
50
|
+
"path-to-regexp": "^7.0.0",
|
|
51
51
|
"semaphore-async-await": "^1.5.1"
|
|
52
52
|
},
|
|
53
53
|
"gitHead": "76e1d17a71b981984935c9a7a5791cf61ebf5213"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { compile } from 'path-to-regexp'
|
|
2
|
+
|
|
3
|
+
const stringifyObjectValues = (params: Record<string, any>) =>
|
|
4
|
+
Object.fromEntries(Object.entries(params).map(([key, value]) => [key, value?.toString()]))
|
|
5
|
+
|
|
6
|
+
export const compileRoute = <T extends Object>(url: string, params: T) => compile(url)(stringifyObjectValues(params))
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TextDecoder, TextEncoder } from 'util'
|
|
2
2
|
|
|
3
3
|
global.TextEncoder = TextEncoder
|
|
4
|
-
global.TextDecoder = TextDecoder as
|
|
4
|
+
global.TextDecoder = TextDecoder as typeof global.TextDecoder
|
|
5
5
|
|
|
6
6
|
import { Injector } from '@furystack/inject'
|
|
7
7
|
import { sleepAsync } from '@furystack/utils'
|
|
8
8
|
import { LazyLoad } from './lazy-load.js'
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
11
11
|
import { initializeShadeRoot } from '../initialize.js'
|
|
12
12
|
import { createComponent } from '../shade-component.js'
|
|
13
13
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Route } from './router.js'
|
|
3
|
-
import { Shade } from '../shade.js'
|
|
1
|
+
import { compileRoute } from '../compile-route.js'
|
|
4
2
|
import type { ChildrenList } from '../models/children-list.js'
|
|
5
3
|
import { createComponent } from '../shade-component.js'
|
|
4
|
+
import { Shade } from '../shade.js'
|
|
5
|
+
import type { Route } from './router.js'
|
|
6
6
|
|
|
7
7
|
export type LinkToRouteProps<T extends {}> = {
|
|
8
8
|
route: Route<T>
|
|
@@ -16,7 +16,7 @@ export const LinkToRoute: <T extends {}>(props: LinkToRouteProps<T>, children?:
|
|
|
16
16
|
render: ({ props, element, children }) => {
|
|
17
17
|
const { route, params } = props
|
|
18
18
|
|
|
19
|
-
const url =
|
|
19
|
+
const url = compileRoute(route.url, params)
|
|
20
20
|
element.setAttribute('href', url)
|
|
21
21
|
return <>{children}</>
|
|
22
22
|
},
|
|
@@ -60,7 +60,7 @@ describe('Router', () => {
|
|
|
60
60
|
<Router
|
|
61
61
|
routes={[
|
|
62
62
|
{ url: '/route-a', component: () => <div id="content">route-a</div>, onVisit, onLeave },
|
|
63
|
-
{ url: '/route-b/:id?', component: ({ match }) => <div id="content">route-b{match.params.id}</div> },
|
|
63
|
+
{ url: '/route-b{/:id}?', component: ({ match }) => <div id="content">route-b{match.params.id}</div> },
|
|
64
64
|
{
|
|
65
65
|
url: '/route-c',
|
|
66
66
|
component: () => <div id="content">route-c</div>,
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { LocationService } from '../services/location-service.js'
|
|
4
|
-
import type { MatchResult, TokensToRegexpOptions } from 'path-to-regexp'
|
|
1
|
+
import { ObservableAlreadyDisposedError } from '@furystack/utils'
|
|
2
|
+
import type { MatchOptions, MatchResult } from 'path-to-regexp'
|
|
5
3
|
import { match } from 'path-to-regexp'
|
|
6
|
-
import type { RenderOptions } from '../models/render-options.js'
|
|
7
4
|
import { Lock } from 'semaphore-async-await'
|
|
8
|
-
import {
|
|
5
|
+
import type { RenderOptions } from '../models/render-options.js'
|
|
6
|
+
import { LocationService } from '../services/location-service.js'
|
|
7
|
+
import { createComponent } from '../shade-component.js'
|
|
8
|
+
import { Shade } from '../shade.js'
|
|
9
9
|
|
|
10
10
|
export interface Route<TMatchResult extends object> {
|
|
11
11
|
url: string
|
|
12
12
|
component: (options: { currentUrl: string; match: MatchResult<TMatchResult> }) => JSX.Element
|
|
13
|
-
routingOptions?:
|
|
13
|
+
routingOptions?: MatchOptions
|
|
14
14
|
onVisit?: (options: RenderOptions<unknown>) => Promise<void>
|
|
15
15
|
onLeave?: (options: RenderOptions<unknown>) => Promise<void>
|
|
16
16
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
export * from './compile-route.js'
|
|
2
|
+
export * from './components/index.js'
|
|
3
|
+
export * from './initialize.js'
|
|
4
|
+
export * from './models/index.js'
|
|
1
5
|
export * from './services/index.js'
|
|
2
|
-
import './jsx.js'
|
|
3
6
|
export * from './shade-component.js'
|
|
4
7
|
export * from './shade.js'
|
|
5
|
-
export * from './models/index.js'
|
|
6
|
-
export * from './components/index.js'
|
|
7
|
-
export * from './initialize.js'
|
|
8
8
|
export * from './styled-element.js'
|
|
9
9
|
export * from './styled-shade.js'
|
|
10
|
+
import './jsx.js'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Injector } from '@furystack/inject'
|
|
2
|
+
import type { ObservableValue, ValueObserverOptions } from '@furystack/utils'
|
|
2
3
|
import type { ChildrenList } from './children-list.js'
|
|
3
|
-
import type { Disposable, ObservableValue, ValueObserverOptions } from '@furystack/utils'
|
|
4
4
|
import type { PartialElement } from './partial-element.js'
|
|
5
5
|
|
|
6
6
|
export type RenderOptions<TProps, TElementBase extends HTMLElement = HTMLElement> = {
|
|
@@ -15,7 +15,7 @@ export type RenderOptions<TProps, TElementBase extends HTMLElement = HTMLElement
|
|
|
15
15
|
* @param factory A factory method for creating the disposable resource
|
|
16
16
|
* @returns The Disposable instance
|
|
17
17
|
*/
|
|
18
|
-
useDisposable: <T extends Disposable>(key: string, factory: () => T) => T
|
|
18
|
+
useDisposable: <T extends Disposable | AsyncDisposable>(key: string, factory: () => T) => T
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
*
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TextDecoder, TextEncoder } from 'util'
|
|
2
2
|
|
|
3
3
|
global.TextEncoder = TextEncoder
|
|
4
4
|
global.TextDecoder = TextDecoder as any
|
|
5
5
|
|
|
6
6
|
import { Injector } from '@furystack/inject'
|
|
7
|
-
import { using } from '@furystack/utils'
|
|
8
7
|
import { deserializeQueryString, serializeToQueryString, serializeValue } from '@furystack/rest'
|
|
8
|
+
import { usingAsync } from '@furystack/utils'
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
9
10
|
import { LocationService, useCustomSearchStateSerializer } from './location-service.js'
|
|
10
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
11
11
|
|
|
12
12
|
describe('LocationService', () => {
|
|
13
13
|
beforeEach(() => {
|
|
@@ -17,15 +17,15 @@ describe('LocationService', () => {
|
|
|
17
17
|
document.body.innerHTML = ''
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
-
it('Shuld be constructed', () => {
|
|
21
|
-
|
|
20
|
+
it('Shuld be constructed', async () => {
|
|
21
|
+
await usingAsync(new Injector(), async (i) => {
|
|
22
22
|
const s = i.getInstance(LocationService)
|
|
23
23
|
expect(s).toBeInstanceOf(LocationService)
|
|
24
24
|
})
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
-
it('Shuld update state on events', () => {
|
|
28
|
-
|
|
27
|
+
it('Shuld update state on events', async () => {
|
|
28
|
+
await usingAsync(new Injector(), async (i) => {
|
|
29
29
|
const onLocaionChanged = vi.fn()
|
|
30
30
|
const s = i.getInstance(LocationService)
|
|
31
31
|
s.onLocationPathChanged.subscribe(onLocaionChanged)
|
|
@@ -44,8 +44,8 @@ describe('LocationService', () => {
|
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
describe('useSearchParam', () => {
|
|
47
|
-
it('Should create observables lazily', () => {
|
|
48
|
-
|
|
47
|
+
it('Should create observables lazily', async () => {
|
|
48
|
+
await usingAsync(new Injector(), async (i) => {
|
|
49
49
|
const service = i.getInstance(LocationService)
|
|
50
50
|
const observables = service.searchParamObservables
|
|
51
51
|
|
|
@@ -64,16 +64,16 @@ describe('LocationService', () => {
|
|
|
64
64
|
})
|
|
65
65
|
})
|
|
66
66
|
|
|
67
|
-
it('Should return the default value, if not present in the query string', () => {
|
|
68
|
-
|
|
67
|
+
it('Should return the default value, if not present in the query string', async () => {
|
|
68
|
+
await usingAsync(new Injector(), async (i) => {
|
|
69
69
|
const service = i.getInstance(LocationService)
|
|
70
70
|
const testSearchParam = service.useSearchParam('test', { value: 'foo' })
|
|
71
71
|
expect(testSearchParam.getValue()).toEqual({ value: 'foo' })
|
|
72
72
|
})
|
|
73
73
|
})
|
|
74
74
|
|
|
75
|
-
it('Should return the value from the query string', () => {
|
|
76
|
-
|
|
75
|
+
it('Should return the value from the query string', async () => {
|
|
76
|
+
await usingAsync(new Injector(), async (i) => {
|
|
77
77
|
const service = i.getInstance(LocationService)
|
|
78
78
|
history.pushState(null, '', `/loc1?test=${serializeValue(1)}`)
|
|
79
79
|
const testSearchParam = service.useSearchParam('test', 123)
|
|
@@ -81,8 +81,8 @@ describe('LocationService', () => {
|
|
|
81
81
|
})
|
|
82
82
|
})
|
|
83
83
|
|
|
84
|
-
it('should update the observable value on push / replace states', () => {
|
|
85
|
-
|
|
84
|
+
it('should update the observable value on push / replace states', async () => {
|
|
85
|
+
await usingAsync(new Injector(), async (i) => {
|
|
86
86
|
const service = i.getInstance(LocationService)
|
|
87
87
|
history.pushState(null, '', `/loc1?test=${serializeValue(1)}`)
|
|
88
88
|
const testSearchParam = service.useSearchParam('test', 234)
|
|
@@ -92,8 +92,8 @@ describe('LocationService', () => {
|
|
|
92
92
|
})
|
|
93
93
|
})
|
|
94
94
|
|
|
95
|
-
it('Should update the URL based on search value change', () => {
|
|
96
|
-
|
|
95
|
+
it('Should update the URL based on search value change', async () => {
|
|
96
|
+
await usingAsync(new Injector(), async (i) => {
|
|
97
97
|
const service = i.getInstance(LocationService)
|
|
98
98
|
history.pushState(null, '', `/loc1?test=${serializeValue('2')}`)
|
|
99
99
|
const testSearchParam = service.useSearchParam('test', '')
|
|
@@ -102,10 +102,10 @@ describe('LocationService', () => {
|
|
|
102
102
|
})
|
|
103
103
|
})
|
|
104
104
|
|
|
105
|
-
it('Should throw an error when trying to use a custom serializer after LocationService has been instantiated', () => {
|
|
106
|
-
|
|
105
|
+
it('Should throw an error when trying to use a custom serializer after LocationService has been instantiated', async () => {
|
|
106
|
+
await usingAsync(new Injector(), async (i) => {
|
|
107
107
|
const customSerializer = vi.fn((value: any) => serializeToQueryString(value))
|
|
108
|
-
const customDeserializer = vi.fn((value:
|
|
108
|
+
const customDeserializer = vi.fn((value: string) => deserializeQueryString(value))
|
|
109
109
|
i.getInstance(LocationService)
|
|
110
110
|
expect(() => useCustomSearchStateSerializer(i, customSerializer, customDeserializer)).toThrowError(
|
|
111
111
|
'useCustomSearchStateSerializer must be called before the LocationService is instantiated',
|
|
@@ -113,10 +113,10 @@ describe('LocationService', () => {
|
|
|
113
113
|
})
|
|
114
114
|
})
|
|
115
115
|
|
|
116
|
-
it('Should use custom serializer and deserializer', () => {
|
|
117
|
-
|
|
116
|
+
it('Should use custom serializer and deserializer', async () => {
|
|
117
|
+
await usingAsync(new Injector(), async (i) => {
|
|
118
118
|
const customSerializer = vi.fn((value: any) => serializeToQueryString(value))
|
|
119
|
-
const customDeserializer = vi.fn((value:
|
|
119
|
+
const customDeserializer = vi.fn((value: string) => deserializeQueryString(value))
|
|
120
120
|
|
|
121
121
|
useCustomSearchStateSerializer(i, customSerializer, customDeserializer)
|
|
122
122
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import type { Disposable } from '@furystack/utils'
|
|
2
|
-
import { ObservableValue, Trace } from '@furystack/utils'
|
|
3
1
|
import { Injectable, type Injector } from '@furystack/inject'
|
|
4
2
|
import {
|
|
5
3
|
deserializeQueryString as defaultDeserializeQueryString,
|
|
6
4
|
serializeToQueryString as defaultSerializeToQueryString,
|
|
7
5
|
} from '@furystack/rest'
|
|
6
|
+
import { ObservableValue } from '@furystack/utils'
|
|
8
7
|
@Injectable({ lifetime: 'singleton' })
|
|
9
8
|
export class LocationService implements Disposable {
|
|
10
9
|
constructor(
|
|
@@ -15,32 +14,34 @@ export class LocationService implements Disposable {
|
|
|
15
14
|
window.addEventListener('popstate', this.popStateListener)
|
|
16
15
|
window.addEventListener('hashchange', this.hashChangeListener)
|
|
17
16
|
|
|
18
|
-
this.pushStateTracer = Trace.method({
|
|
19
|
-
object: history,
|
|
20
|
-
method: history.pushState,
|
|
21
|
-
isAsync: false,
|
|
22
|
-
onFinished: () => this.updateState(),
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
this.replaceStateTracer = Trace.method({
|
|
26
|
-
object: history,
|
|
27
|
-
method: history.replaceState,
|
|
28
|
-
isAsync: false,
|
|
29
|
-
onFinished: () => this.updateState(),
|
|
30
|
-
})
|
|
31
|
-
|
|
32
17
|
this.onDeserializedLocationSearchChanged = new ObservableValue(this.deserializeQueryString(location.search))
|
|
18
|
+
|
|
19
|
+
this.originalPushState = window.history.pushState.bind(window.history)
|
|
20
|
+
window.history.pushState = ((...args: Parameters<typeof window.history.pushState>) => {
|
|
21
|
+
this.originalPushState(...args)
|
|
22
|
+
this.updateState()
|
|
23
|
+
}).bind(this)
|
|
24
|
+
|
|
25
|
+
this.originalReplaceState = window.history.replaceState.bind(window.history)
|
|
26
|
+
window.history.replaceState = ((...args: Parameters<typeof window.history.replaceState>) => {
|
|
27
|
+
this.originalReplaceState(...args)
|
|
28
|
+
this.updateState()
|
|
29
|
+
}).bind(this)
|
|
33
30
|
}
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
private originalPushState: typeof window.history.pushState
|
|
33
|
+
private originalReplaceState: typeof window.history.replaceState
|
|
34
|
+
|
|
35
|
+
public [Symbol.dispose]() {
|
|
36
36
|
window.removeEventListener('popstate', this.popStateListener)
|
|
37
37
|
window.removeEventListener('hashchange', this.hashChangeListener)
|
|
38
|
-
this.
|
|
39
|
-
this.
|
|
40
|
-
this.
|
|
41
|
-
this.
|
|
42
|
-
|
|
43
|
-
this.
|
|
38
|
+
this.onLocationPathChanged[Symbol.dispose]()
|
|
39
|
+
this.onLocationSearchChanged[Symbol.dispose]()
|
|
40
|
+
this.onDeserializedLocationSearchChanged[Symbol.dispose]()
|
|
41
|
+
this.locationDeserializerObserver[Symbol.dispose]()
|
|
42
|
+
|
|
43
|
+
window.history.pushState = this.originalPushState
|
|
44
|
+
window.history.replaceState = this.originalReplaceState
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/**
|
|
@@ -51,7 +52,7 @@ export class LocationService implements Disposable {
|
|
|
51
52
|
/**
|
|
52
53
|
* Observable value that will be updated when the location hash (e.g. #hash) changes
|
|
53
54
|
*/
|
|
54
|
-
public onLocationHashChanged = new ObservableValue(location.hash)
|
|
55
|
+
public onLocationHashChanged = new ObservableValue(location.hash.replace('#', ''))
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
58
|
* Observable value that will be updated when the location search (e.g. ?search=1) changes
|
|
@@ -66,7 +67,7 @@ export class LocationService implements Disposable {
|
|
|
66
67
|
|
|
67
68
|
public updateState = (() => {
|
|
68
69
|
this.onLocationPathChanged.setValue(location.pathname)
|
|
69
|
-
this.onLocationHashChanged.setValue(location.hash)
|
|
70
|
+
this.onLocationHashChanged.setValue(location.hash.replace('#', ''))
|
|
70
71
|
this.onLocationSearchChanged.setValue(location.search)
|
|
71
72
|
}).bind(this)
|
|
72
73
|
|
|
@@ -102,23 +103,20 @@ export class LocationService implements Disposable {
|
|
|
102
103
|
|
|
103
104
|
this.onDeserializedLocationSearchChanged.subscribe((search) => {
|
|
104
105
|
const value = (search[key] as T) ?? defaultValue
|
|
105
|
-
this.searchParamObservables.get(key)?.setValue(value
|
|
106
|
+
this.searchParamObservables.get(key)?.setValue(value)
|
|
106
107
|
})
|
|
107
108
|
return newObservable
|
|
108
109
|
}
|
|
109
110
|
return existing as ObservableValue<T>
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
private pushStateTracer: Disposable
|
|
113
|
-
private replaceStateTracer: Disposable
|
|
114
|
-
|
|
115
113
|
private popStateListener = (_ev: PopStateEvent) => {
|
|
116
114
|
this.updateState()
|
|
117
115
|
}
|
|
118
116
|
|
|
119
|
-
private hashChangeListener = (_ev: HashChangeEvent) => {
|
|
117
|
+
private hashChangeListener = ((_ev: HashChangeEvent) => {
|
|
120
118
|
this.updateState()
|
|
121
|
-
}
|
|
119
|
+
}).bind(this)
|
|
122
120
|
}
|
|
123
121
|
|
|
124
122
|
export const useCustomSearchStateSerializer = (
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { ObservableValue,
|
|
1
|
+
import { ObservableValue, usingAsync } from '@furystack/utils'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
3
|
import { ResourceManager } from './resource-manager.js'
|
|
3
|
-
|
|
4
|
+
|
|
4
5
|
describe('ResourceManager', () => {
|
|
5
|
-
it('Should return an observable from cache', () => {
|
|
6
|
-
|
|
6
|
+
it('Should return an observable from cache', async () => {
|
|
7
|
+
await usingAsync(new ResourceManager(), async (rm) => {
|
|
7
8
|
const o = new ObservableValue(1)
|
|
8
9
|
const [value1] = rm.useObservable('test', o, () => {
|
|
9
10
|
/** ignore */
|
|
@@ -18,10 +19,10 @@ describe('ResourceManager', () => {
|
|
|
18
19
|
})
|
|
19
20
|
})
|
|
20
21
|
|
|
21
|
-
it('Should return a disposable from cache', () => {
|
|
22
|
-
|
|
22
|
+
it('Should return a disposable from cache', async () => {
|
|
23
|
+
await usingAsync(new ResourceManager(), async (rm) => {
|
|
23
24
|
const factory = vi.fn(() => ({
|
|
24
|
-
dispose: () => {
|
|
25
|
+
[Symbol.dispose]: () => {
|
|
25
26
|
/** ignore */
|
|
26
27
|
},
|
|
27
28
|
}))
|
|
@@ -32,4 +33,70 @@ describe('ResourceManager', () => {
|
|
|
32
33
|
expect(factory).toHaveBeenCalledTimes(1)
|
|
33
34
|
})
|
|
34
35
|
})
|
|
36
|
+
|
|
37
|
+
it('Should dispose all disposables on dispose', async () => {
|
|
38
|
+
const disposable = {
|
|
39
|
+
[Symbol.dispose]: vi.fn(),
|
|
40
|
+
}
|
|
41
|
+
const factory = vi.fn(() => disposable)
|
|
42
|
+
await usingAsync(new ResourceManager(), async (rm) => {
|
|
43
|
+
rm.useDisposable('test', factory)
|
|
44
|
+
expect(factory).toHaveBeenCalledTimes(1)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
expect(disposable[Symbol.dispose]).toHaveBeenCalledTimes(1)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('Should dispose all async disposables on dispose', async () => {
|
|
51
|
+
const disposable = {
|
|
52
|
+
[Symbol.asyncDispose]: vi.fn(),
|
|
53
|
+
}
|
|
54
|
+
const factory = vi.fn(() => disposable)
|
|
55
|
+
await usingAsync(new ResourceManager(), async (rm) => {
|
|
56
|
+
rm.useDisposable('test', factory)
|
|
57
|
+
expect(factory).toHaveBeenCalledTimes(1)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
expect(disposable[Symbol.asyncDispose]).toHaveBeenCalledTimes(1)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('Should throw an aggregated error when failed to dispose something', async () => {
|
|
64
|
+
const disposable = {
|
|
65
|
+
[Symbol.dispose]: vi.fn(() => {
|
|
66
|
+
throw new Error('Failed to dispose')
|
|
67
|
+
}),
|
|
68
|
+
}
|
|
69
|
+
const factory = vi.fn(() => disposable)
|
|
70
|
+
await expect(
|
|
71
|
+
async () =>
|
|
72
|
+
await usingAsync(new ResourceManager(), async (rm) => {
|
|
73
|
+
rm.useDisposable('test', factory)
|
|
74
|
+
expect(factory).toHaveBeenCalledTimes(1)
|
|
75
|
+
}),
|
|
76
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
77
|
+
`[Error: There was an error during disposing 1 stores: Error: Failed to dispose]`,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
expect(disposable[Symbol.dispose]).toHaveBeenCalledTimes(1)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('Should throw an aggregated error when failed to async dispose something', async () => {
|
|
84
|
+
const disposable = {
|
|
85
|
+
[Symbol.asyncDispose]: vi.fn(async () => {
|
|
86
|
+
throw new Error('Failed to dispose')
|
|
87
|
+
}),
|
|
88
|
+
}
|
|
89
|
+
const factory = vi.fn(() => disposable)
|
|
90
|
+
await expect(
|
|
91
|
+
async () =>
|
|
92
|
+
await usingAsync(new ResourceManager(), async (rm) => {
|
|
93
|
+
rm.useDisposable('test', factory)
|
|
94
|
+
expect(factory).toHaveBeenCalledTimes(1)
|
|
95
|
+
}),
|
|
96
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
97
|
+
`[Error: There was an error during disposing 1 stores: Error: Failed to dispose]`,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
expect(disposable[Symbol.asyncDispose]).toHaveBeenCalledTimes(1)
|
|
101
|
+
})
|
|
35
102
|
})
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
import
|
|
1
|
+
import { AggregatedError } from '@furystack/core'
|
|
2
|
+
import type { ValueChangeCallback, ValueObserver, ValueObserverOptions } from '@furystack/utils'
|
|
3
|
+
import { ObservableValue, isAsyncDisposable, isDisposable } from '@furystack/utils'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Class for managing observables and disposables for components, based on key-value maps
|
|
7
7
|
*/
|
|
8
|
-
export class ResourceManager {
|
|
9
|
-
private readonly disposables = new Map<string, Disposable>()
|
|
8
|
+
export class ResourceManager implements AsyncDisposable {
|
|
9
|
+
private readonly disposables = new Map<string, Disposable | AsyncDisposable>()
|
|
10
10
|
|
|
11
|
-
public useDisposable<T extends Disposable>(key: string, factory: () => T): T {
|
|
11
|
+
public useDisposable<T extends Disposable | AsyncDisposable>(key: string, factory: () => T): T {
|
|
12
12
|
const existing = this.disposables.get(key)
|
|
13
13
|
if (!existing) {
|
|
14
14
|
const created = factory()
|
|
@@ -51,13 +51,32 @@ export class ResourceManager {
|
|
|
51
51
|
return [observable.getValue(), observable.setValue.bind(observable)]
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
public
|
|
55
|
-
|
|
54
|
+
public async [Symbol.asyncDispose]() {
|
|
55
|
+
const disposeResult = await Promise.allSettled(
|
|
56
|
+
[...this.disposables].map(async ([_key, resource]) => {
|
|
57
|
+
if (isDisposable(resource)) {
|
|
58
|
+
resource[Symbol.dispose]()
|
|
59
|
+
}
|
|
60
|
+
if (isAsyncDisposable(resource)) {
|
|
61
|
+
await resource[Symbol.asyncDispose]()
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const fails = disposeResult.filter((r) => r.status === 'rejected')
|
|
67
|
+
if (fails && fails.length) {
|
|
68
|
+
const error = new AggregatedError(
|
|
69
|
+
`There was an error during disposing ${fails.length} stores: ${fails.map((f) => f.reason)}`,
|
|
70
|
+
fails,
|
|
71
|
+
)
|
|
72
|
+
throw error
|
|
73
|
+
}
|
|
74
|
+
|
|
56
75
|
this.disposables.clear()
|
|
57
|
-
this.observers.forEach((r) => r.dispose())
|
|
76
|
+
this.observers.forEach((r) => r[Symbol.dispose]())
|
|
58
77
|
this.observers.clear()
|
|
59
78
|
|
|
60
|
-
this.stateObservers.forEach((r) => r.dispose())
|
|
79
|
+
this.stateObservers.forEach((r) => r[Symbol.dispose]())
|
|
61
80
|
this.stateObservers.clear()
|
|
62
81
|
}
|
|
63
82
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Injectable } from '@furystack/inject'
|
|
2
|
-
import type { Disposable } from '@furystack/utils'
|
|
3
2
|
import { ObservableValue } from '@furystack/utils'
|
|
4
3
|
|
|
5
4
|
export const ScreenSizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const
|
|
@@ -26,7 +25,7 @@ export class ScreenService implements Disposable {
|
|
|
26
25
|
xs: { minSize: 0 },
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
public dispose() {
|
|
28
|
+
public [Symbol.dispose]() {
|
|
30
29
|
window.removeEventListener('resize', this.onResizeListener)
|
|
31
30
|
}
|
|
32
31
|
|