@furystack/shades 3.7.2 → 4.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/component-factory.spec.js +68 -11
- package/dist/component-factory.spec.js.map +1 -1
- package/dist/components/lazy-load.spec.d.ts +2 -0
- package/dist/components/lazy-load.spec.d.ts.map +1 -0
- package/dist/components/lazy-load.spec.js +83 -0
- package/dist/components/lazy-load.spec.js.map +1 -0
- package/dist/components/route-link.d.ts.map +1 -1
- package/dist/components/route-link.js.map +1 -1
- package/dist/components/route-link.spec.d.ts +2 -0
- package/dist/components/route-link.spec.d.ts.map +1 -0
- package/dist/components/route-link.spec.js +38 -0
- package/dist/components/route-link.spec.js.map +1 -0
- package/dist/components/router.d.ts +4 -2
- package/dist/components/router.d.ts.map +1 -1
- package/dist/components/router.js +35 -26
- package/dist/components/router.js.map +1 -1
- package/dist/components/router.spec.d.ts +2 -0
- package/dist/components/router.spec.d.ts.map +1 -0
- package/dist/components/router.spec.js +93 -0
- package/dist/components/router.spec.js.map +1 -0
- package/dist/services/location-service.d.ts +1 -1
- package/dist/services/location-service.d.ts.map +1 -1
- package/dist/services/location-service.js +3 -2
- package/dist/services/location-service.js.map +1 -1
- package/dist/services/location-service.spec.js +29 -3
- package/dist/services/location-service.spec.js.map +1 -1
- package/dist/services/screen-service.d.ts +1 -1
- package/dist/services/screen-service.d.ts.map +1 -1
- package/dist/services/screen-service.js +5 -5
- package/dist/services/screen-service.js.map +1 -1
- package/dist/services/screen-service.spec.d.ts +2 -0
- package/dist/services/screen-service.spec.d.ts.map +1 -0
- package/dist/services/screen-service.spec.js +34 -0
- package/dist/services/screen-service.spec.js.map +1 -0
- package/dist/shade-resources.integration.spec.d.ts +2 -0
- package/dist/shade-resources.integration.spec.d.ts.map +1 -0
- package/dist/shade-resources.integration.spec.js +62 -0
- package/dist/shade-resources.integration.spec.js.map +1 -0
- package/dist/shade.d.ts +2 -0
- package/dist/shade.d.ts.map +1 -1
- package/dist/shade.js +22 -6
- package/dist/shade.js.map +1 -1
- package/dist/shades.integration.spec.d.ts +2 -0
- package/dist/shades.integration.spec.d.ts.map +1 -0
- package/dist/shades.integration.spec.js +123 -0
- package/dist/shades.integration.spec.js.map +1 -0
- package/package.json +8 -5
- package/src/component-factory.spec.tsx +97 -14
- package/src/components/lazy-load.spec.tsx +117 -0
- package/src/components/route-link.spec.tsx +50 -0
- package/src/components/route-link.tsx +2 -1
- package/src/components/router.spec.tsx +130 -0
- package/src/components/router.tsx +38 -29
- package/src/services/location-service.spec.ts +37 -3
- package/src/services/location-service.tsx +3 -2
- package/src/services/screen-service.spec.ts +39 -0
- package/src/services/screen-service.ts +1 -1
- package/src/shade-resources.integration.spec.tsx +94 -0
- package/src/shade.ts +29 -9
- package/src/shades.integration.spec.tsx +156 -0
package/src/shade.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ObservableValue } from '@furystack/utils'
|
|
1
|
+
import { Disposable, ObservableValue } from '@furystack/utils'
|
|
2
2
|
import { v4 } from 'uuid'
|
|
3
3
|
import { Injector } from '@furystack/inject'
|
|
4
|
-
import { ChildrenList, RenderOptions } from './models'
|
|
4
|
+
import { ChildrenList, PartialElement, RenderOptions } from './models'
|
|
5
5
|
|
|
6
6
|
export type ShadeOptions<TProps, TState> = {
|
|
7
7
|
/**
|
|
@@ -29,6 +29,8 @@ export type ShadeOptions<TProps, TState> = {
|
|
|
29
29
|
* Will be executed when the element is detached from the DOM.
|
|
30
30
|
*/
|
|
31
31
|
onDetach?: (options: RenderOptions<TProps, TState>) => void
|
|
32
|
+
|
|
33
|
+
resources?: (options: RenderOptions<TProps, TState>) => Disposable[]
|
|
32
34
|
} & (unknown extends TState
|
|
33
35
|
? {}
|
|
34
36
|
: {
|
|
@@ -64,6 +66,7 @@ export const Shade = <TProps, TState = unknown>(o: ShadeOptions<TProps, TState>)
|
|
|
64
66
|
this.state.dispose()
|
|
65
67
|
this.shadeChildren.dispose()
|
|
66
68
|
this.cleanup && this.cleanup()
|
|
69
|
+
Object.values(this.resources).forEach((s) => s.dispose())
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
/**
|
|
@@ -93,23 +96,35 @@ export const Shade = <TProps, TState = unknown>(o: ShadeOptions<TProps, TState>)
|
|
|
93
96
|
private getRenderOptions = () => {
|
|
94
97
|
const props = this.props.getValue() || {}
|
|
95
98
|
const getState = () => this.state.getValue()
|
|
96
|
-
|
|
99
|
+
const updateState = (stateChanges: PartialElement<TState>, skipRender?: boolean) => {
|
|
100
|
+
const currentState = this.state.getValue()
|
|
101
|
+
const newState = { ...currentState, ...stateChanges }
|
|
102
|
+
if (JSON.stringify(currentState) !== JSON.stringify(newState)) {
|
|
103
|
+
this.state.setValue(newState)
|
|
104
|
+
!skipRender && this.updateComponent()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const returnValue: RenderOptions<TProps, TState> = {
|
|
97
109
|
props,
|
|
98
110
|
getState,
|
|
99
111
|
injector: this.injector,
|
|
100
|
-
updateState
|
|
101
|
-
this.state.setValue({ ...this.state.getValue(), ...newState })
|
|
102
|
-
!skipRender && this.updateComponent()
|
|
103
|
-
},
|
|
112
|
+
updateState,
|
|
104
113
|
children: this.shadeChildren.getValue(),
|
|
105
114
|
element: this,
|
|
106
|
-
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return returnValue
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private createResources() {
|
|
121
|
+
this.resources.push(...(o.resources?.(this.getRenderOptions()) || []))
|
|
107
122
|
}
|
|
108
123
|
|
|
109
124
|
/**
|
|
110
125
|
* Updates the component in the DOM.
|
|
111
126
|
*/
|
|
112
|
-
public
|
|
127
|
+
public updateComponent() {
|
|
113
128
|
const newJsx = this.render(this.getRenderOptions())
|
|
114
129
|
|
|
115
130
|
// const selectionState = this.getSelectionState()
|
|
@@ -130,6 +145,7 @@ export const Shade = <TProps, TState = unknown>(o: ShadeOptions<TProps, TState>)
|
|
|
130
145
|
this.state.setValue((o as any).getInitialState({ props: this.props.getValue(), injector: this.injector }))
|
|
131
146
|
|
|
132
147
|
this.updateComponent()
|
|
148
|
+
this.createResources()
|
|
133
149
|
const cleanupResult = o.constructed && o.constructed(this.getRenderOptions())
|
|
134
150
|
if (cleanupResult instanceof Promise) {
|
|
135
151
|
cleanupResult.then((cleanup) => (this.cleanup = cleanup))
|
|
@@ -180,6 +196,8 @@ export const Shade = <TProps, TState = unknown>(o: ShadeOptions<TProps, TState>)
|
|
|
180
196
|
this._injector = i
|
|
181
197
|
}
|
|
182
198
|
|
|
199
|
+
private resources: Disposable[] = []
|
|
200
|
+
|
|
183
201
|
constructor(_props: TProps) {
|
|
184
202
|
super()
|
|
185
203
|
this.props = new ObservableValue()
|
|
@@ -187,6 +205,8 @@ export const Shade = <TProps, TState = unknown>(o: ShadeOptions<TProps, TState>)
|
|
|
187
205
|
}
|
|
188
206
|
} as any as CustomElementConstructor,
|
|
189
207
|
)
|
|
208
|
+
} else {
|
|
209
|
+
throw Error(`A custom shade with shadow DOM name '${o.shadowDomName}' has already been registered!`)
|
|
190
210
|
}
|
|
191
211
|
|
|
192
212
|
return (props: TProps, children: ChildrenList) => {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Injector } from '@furystack/inject'
|
|
2
|
+
|
|
3
|
+
import { TextEncoder, TextDecoder } from 'util'
|
|
4
|
+
|
|
5
|
+
global.TextEncoder = TextEncoder
|
|
6
|
+
global.TextDecoder = TextDecoder as any
|
|
7
|
+
|
|
8
|
+
import { JSDOM } from 'jsdom'
|
|
9
|
+
import { initializeShadeRoot } from './initialize'
|
|
10
|
+
import { Shade } from './shade'
|
|
11
|
+
import { createComponent } from './shade-component'
|
|
12
|
+
|
|
13
|
+
describe('Shades integration tests', () => {
|
|
14
|
+
const oldDoc = document
|
|
15
|
+
|
|
16
|
+
beforeAll(() => {
|
|
17
|
+
globalThis.document = new JSDOM().window.document
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
afterAll(() => {
|
|
21
|
+
globalThis.document = oldDoc
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
beforeEach(() => (document.body.innerHTML = '<div id="root"></div>'))
|
|
25
|
+
afterEach(() => (document.body.innerHTML = ''))
|
|
26
|
+
|
|
27
|
+
it('Should mount a custom component to a Shade root', () => {
|
|
28
|
+
const injector = new Injector()
|
|
29
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
30
|
+
|
|
31
|
+
const ExampleComponent = Shade({ render: () => <div>Hello</div>, shadowDomName: 'shades-example' })
|
|
32
|
+
|
|
33
|
+
initializeShadeRoot({
|
|
34
|
+
injector,
|
|
35
|
+
rootElement,
|
|
36
|
+
jsxElement: <ExampleComponent />,
|
|
37
|
+
})
|
|
38
|
+
expect(document.body.innerHTML).toBe('<div id="root"><shades-example><div>Hello</div></shades-example></div>')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('Should mount nested Shades components', () => {
|
|
42
|
+
const injector = new Injector()
|
|
43
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
44
|
+
|
|
45
|
+
const ExampleComponent = Shade({
|
|
46
|
+
render: ({ children }) => <div>{children}</div>,
|
|
47
|
+
shadowDomName: 'shades-example-2',
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const ExampleSubs = Shade<{ no: number }>({
|
|
51
|
+
render: ({ props }) => <div>{props.no}</div>,
|
|
52
|
+
shadowDomName: 'shades-example-sub',
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
initializeShadeRoot({
|
|
56
|
+
injector,
|
|
57
|
+
rootElement,
|
|
58
|
+
jsxElement: (
|
|
59
|
+
<ExampleComponent>
|
|
60
|
+
<ExampleSubs no={1} />
|
|
61
|
+
<ExampleSubs no={2} />
|
|
62
|
+
<ExampleSubs no={3} />
|
|
63
|
+
</ExampleComponent>
|
|
64
|
+
),
|
|
65
|
+
})
|
|
66
|
+
expect(document.body.innerHTML).toBe(
|
|
67
|
+
'<div id="root"><shades-example-2><div><shades-example-sub><div></div></shades-example-sub><shades-example-sub><div></div></shades-example-sub><shades-example-sub><div></div></shades-example-sub></div></shades-example-2></div>',
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it("Should execute the constructed and constructed's cleanup callback", () => {
|
|
72
|
+
const injector = new Injector()
|
|
73
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
74
|
+
const cleanup = jest.fn()
|
|
75
|
+
const constructed = jest.fn(() => cleanup)
|
|
76
|
+
|
|
77
|
+
const ExampleComponent = Shade({
|
|
78
|
+
constructed,
|
|
79
|
+
render: () => <div>Hello</div>,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
initializeShadeRoot({
|
|
83
|
+
injector,
|
|
84
|
+
rootElement,
|
|
85
|
+
jsxElement: <ExampleComponent />,
|
|
86
|
+
})
|
|
87
|
+
expect(constructed).toBeCalled()
|
|
88
|
+
expect(cleanup).not.toBeCalled()
|
|
89
|
+
document.body.innerHTML = ''
|
|
90
|
+
expect(cleanup).toBeCalled()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('Should execute the onAttach and onDetach callbacks', () => {
|
|
94
|
+
const injector = new Injector()
|
|
95
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
96
|
+
const onAttach = jest.fn()
|
|
97
|
+
const onDetach = jest.fn()
|
|
98
|
+
|
|
99
|
+
const ExampleComponent = Shade({
|
|
100
|
+
onAttach,
|
|
101
|
+
onDetach,
|
|
102
|
+
render: () => <div>Hello</div>,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
initializeShadeRoot({
|
|
106
|
+
injector,
|
|
107
|
+
rootElement,
|
|
108
|
+
jsxElement: <ExampleComponent />,
|
|
109
|
+
})
|
|
110
|
+
expect(onAttach).toBeCalled()
|
|
111
|
+
expect(onDetach).not.toBeCalled()
|
|
112
|
+
document.body.innerHTML = ''
|
|
113
|
+
expect(onDetach).toBeCalled()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('Should update state', () => {
|
|
117
|
+
const injector = new Injector()
|
|
118
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
119
|
+
|
|
120
|
+
const ExampleComponent = Shade({
|
|
121
|
+
getInitialState: () => ({ count: 0 }),
|
|
122
|
+
render: ({ getState, updateState }) => {
|
|
123
|
+
const { count } = getState()
|
|
124
|
+
return (
|
|
125
|
+
<div>
|
|
126
|
+
Count is {getState().count.toString()}
|
|
127
|
+
<button id="plus" onclick={() => updateState({ count: count + 1 })}>
|
|
128
|
+
+
|
|
129
|
+
</button>
|
|
130
|
+
<button id="minus" onclick={() => updateState({ count: count - 1 })}>
|
|
131
|
+
-
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
)
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
initializeShadeRoot({
|
|
138
|
+
injector,
|
|
139
|
+
rootElement,
|
|
140
|
+
jsxElement: <ExampleComponent />,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const plus = () => document.getElementById('plus')?.click()
|
|
144
|
+
const minus = () => document.getElementById('minus')?.click()
|
|
145
|
+
|
|
146
|
+
expect(document.body.innerHTML).toContain('Count is 0')
|
|
147
|
+
plus()
|
|
148
|
+
expect(document.body.innerHTML).toContain('Count is 1')
|
|
149
|
+
plus()
|
|
150
|
+
expect(document.body.innerHTML).toContain('Count is 2')
|
|
151
|
+
|
|
152
|
+
minus()
|
|
153
|
+
minus()
|
|
154
|
+
expect(document.body.innerHTML).toContain('Count is 0')
|
|
155
|
+
})
|
|
156
|
+
})
|