-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathsvelte-state-renderer.js
141 lines (119 loc) · 3.53 KB
/
svelte-state-renderer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import { tick } from 'svelte'
import View from './View.svelte'
View.from = (target, asr) => {
if (target instanceof HTMLElement) {
// it's a target element
return new View({ target, props: { asr } })
} else {
// it's alreay a View
return target
}
}
const ASR = Symbol('asr')
const hijackStateRouter = stateRouter => {
const resolveComponent = template => {
if (typeof template === 'function') {
return template()
} else {
return template.component()
}
}
const addState = ({ register, template, ...config }) => {
const convertedCfg = {
...config,
template,
async resolve(...args) {
const Cmp = await resolveComponent(template)
if (Cmp.resolve) {
return Cmp.resolve(...args)
}
return null
},
}
stateRouter.addState(convertedCfg)
if (register) {
addStates(register)
}
}
const addStates = (...states) =>
states.forEach(arg => {
if (Array.isArray(arg)) {
arg.forEach(states => addStates(states))
} else {
addState(arg)
}
})
stateRouter.addStates = addStates
}
// relies on compile result, made available by HMR plugin
const filterExpectedProps = (Component, props) => {
// $$hmrCompileData is the legacy name (older versions of hmr deps)
const $compile = Component.$compile || Component.$$hmrCompileData
const vars = $compile && $compile.vars
// guard: compile result not available
if (!vars) return props
const expectedProps = {}
for (const { export_name: k } of vars) {
if (k != null && props.hasOwnProperty(k)) {
expectedProps[k] = props[k]
}
}
return expectedProps
}
export default (defaultOptions = {}) => stateRouter => {
hijackStateRouter(stateRouter)
const { props: defaultProps } = defaultOptions
const asr = {
makePath: stateRouter.makePath,
stateIsActive: stateRouter.stateIsActive,
go: stateRouter.go,
}
const render = async context => {
const { element: target, template, content } = context
// NOTE letting defaults & resolve override asr (if they do that, they
// probably know what they're doing -- even if it might no be commandable)
const props = { asr, ...content, ...defaultProps }
const view = View.from(target, asr)
const construct = async (getComponent, props) => {
const { default: Component } = await getComponent()
const expectedProps = filterExpectedProps(Component, props)
const component = await view.setComponent(Component, expectedProps)
return component
}
let cmp
if (typeof template === `function`) {
cmp = await construct(template, props)
} else {
cmp = await construct(template.component, { ...props, ...template.props })
}
function onRouteChange() {
// FIXME what's this? force refresh? why?
// cmp.$set({ asr })
}
stateRouter.on(`stateChangeEnd`, onRouteChange)
const onDestroy = () =>
stateRouter.removeListener(`stateChangeEnd`, onRouteChange)
cmp[ASR] = { view, onDestroy }
return cmp
}
return {
render,
async reset(context, cb) {
const cmp = context.domApi
const { view, onDestroy } = cmp[ASR]
onDestroy()
const renderContext = { ...context, element: view }
return render(renderContext, cb)
},
async destroy(cmp) {
const { view, onDestroy } = cmp[ASR]
onDestroy()
view.$destroy()
await tick()
},
async getChildElement(cmp) {
const { [ASR]: {view} } = cmp
return view.getChild()
},
}
}