-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
121 lines (101 loc) · 4.13 KB
/
index.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
import { app } from "https://cdn.skypack.dev/hyperapp"
export default (name, fn) => {
customElements.define(name, class extends HTMLElement {
#dispatch
#prevExternalStateJson
constructor () {
super()
this.attachShadow({mode: 'open'}).appendChild(document.createElement('div'))
}
connectedCallback () {
const elem = this
const opts = fn(elem)
// to each event in elem.events add an effect trigering that event
Object.keys(elem.events || {}).forEach(n => {
elem.events[n].effect = (_, payload) => elem.dispatchEvent(new CustomEvent(n, payload))
})
// hook onStateChanged events
if (elem.events?.stateChange) {
const stateMiddleware = fn => dispatch => (action, payload) => {
if (Array.isArray(action) && typeof action[0] !== 'function') {
action = [fn(action[0]), ...action.slice(1)]
} else if (!Array.isArray(action) && typeof action !== 'function') {
action = fn(action)
}
dispatch(action, payload)
}
const stateChangeMw = stateMiddleware(state => {
if (state===undefined) return
if (opts.externalState) {
const newExternalState = opts.externalState(state)
const newExternalStateJson = JSON.stringify(newExternalState)
if (newExternalStateJson !== this.#prevExternalStateJson) {
this.#prevExternalStateJson = newExternalStateJson;
elem.dispatchEvent(new CustomEvent(`stateChange`, { detail: newExternalState }))
}
} else {
elem.dispatchEvent(new CustomEvent(`stateChange`, { detail: state }))
}
return state
})
const orgDispatch = opts.dispatch
opts.dispatch = orgDispatch ? d => stateChangeMw(orgDispatch(d)) : d => stateChangeMw(d)
}
// prevent view renders after disconnection
if (opts.view) {
const viewFn = opts.view
opts.view = s => s===undefined ? '' : viewFn(s)
}
let styleSheets = []
if (opts.style) {
let ss = new CSSStyleSheet()
ss.replaceSync(opts.style)
styleSheets = styleSheets.concat(ss)
}
if (!!opts.cloneCSS) {
const cloneStyleSheets = element => {
const sheets = [...(element.styleSheets || [])]
const styleSheets = sheets.map(styleSheet => {
try {
const rulesText = [...styleSheet.cssRules].map(rule => rule.cssText).join("")
let res = new CSSStyleSheet()
res.replaceSync(rulesText)
return res
} catch (e) {
}
}).filter(Boolean)
if (element === document) return styleSheets
if (!element.parentElement) return cloneStyleSheets(document).concat(styleSheets)
return cloneStyleSheets(element.parentElement).concat(styleSheets)
}
styleSheets = styleSheets.concat(cloneStyleSheets(this))
}
if (styleSheets.length > 0) elem.shadowRoot.adoptedStyleSheets = styleSheets
// start app and save disaptch fn
this.#dispatch = app({ init: opts.init, view: opts.view, subscriptions: opts.subscriptions, node: elem.shadowRoot.firstChild , dispatch: opts.dispatch })
// map any entry in methods to an elem property dispatching an action
if (opts.methods) {
const methodsProps = Object.fromEntries(
Object.entries(opts.methods).map(
([name, action]) => [name, (...args) => this.#dispatch(action, args.length>1 ? args : args[0]) ]
)
)
Object.assign(elem, methodsProps)
}
// map any entry in props to a property where set dispatches an action
const props = {}
Object.entries(opts.properties || {}).forEach(([name, action]) => {
Object.defineProperty(elem, name, {
get: () => { return props[name] },
set: (x) => {
props[name] = x
this.#dispatch(action, x)
}
})
})
}
disconnectedCallback () {
this.#dispatch()
}
})
}