@symbo.ls/element 3.14.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.
@@ -0,0 +1,163 @@
1
+ 'use strict'
2
+
3
+ // Event Delegation - single root listener per event type
4
+ // Uses WeakMap to associate DOM nodes with their handlers
5
+
6
+ import { DELEGATED_EVENTS, LIFECYCLE_EVENTS, PASSIVE_EVENTS } from './registry.js'
7
+ import { pushConfig, popConfig } from '@symbo.ls/scratch'
8
+
9
+ const delegateRoots = new WeakMap()
10
+
11
+ // Reused listener-option objects — browsers treat `{ passive: true }` as
12
+ // read-only metadata, so a single shared instance per shape is safe and
13
+ // avoids a per-listener allocation.
14
+ const PASSIVE_BUBBLE = { passive: true }
15
+ const PASSIVE_CAPTURE = { passive: true, capture: true }
16
+
17
+ const listenerOptions = (eventName, useCapture) => {
18
+ if (PASSIVE_EVENTS.has(eventName)) {
19
+ return useCapture ? PASSIVE_CAPTURE : PASSIVE_BUBBLE
20
+ }
21
+ return useCapture || false
22
+ }
23
+
24
+ function getDelegateRoot(rootNode) {
25
+ let root = delegateRoots.get(rootNode)
26
+ if (!root) {
27
+ root = { handlers: new Map(), installed: new Set() }
28
+ delegateRoots.set(rootNode, root)
29
+ }
30
+ return root
31
+ }
32
+
33
+ function installDelegatedEvent(eventName, rootNode) {
34
+ const root = getDelegateRoot(rootNode)
35
+ if (root.installed.has(eventName)) return
36
+
37
+ root.installed.add(eventName)
38
+
39
+ // Use capture for non-bubbling events (focus/blur, mouseenter/mouseleave)
40
+ const useCapture = eventName === 'focus' || eventName === 'blur' ||
41
+ eventName === 'focusin' || eventName === 'focusout' ||
42
+ eventName === 'mouseenter' || eventName === 'mouseleave'
43
+
44
+ const isEnterLeave = eventName === 'mouseenter' || eventName === 'mouseleave'
45
+
46
+ rootNode.addEventListener(eventName, (event) => {
47
+ let target = event.target
48
+
49
+ while (target && target !== rootNode) {
50
+ const handlerMap = root.handlers.get(eventName)
51
+ if (handlerMap) {
52
+ const handler = handlerMap.get(target)
53
+ if (handler) {
54
+ // For mouseenter/mouseleave: only fire if relatedTarget is outside this node
55
+ // (prevents firing when moving between children inside the element)
56
+ if (isEnterLeave && event.relatedTarget && target.contains(event.relatedTarget)) {
57
+ target = target.parentNode
58
+ continue
59
+ }
60
+ handler(event)
61
+ if (event.cancelBubble) return
62
+ }
63
+ }
64
+ target = target.parentNode
65
+ }
66
+ }, listenerOptions(eventName, useCapture))
67
+ }
68
+
69
+ // Register a delegated event handler for a DOM node
70
+ export function addDelegatedEvent(eventName, node, handler, rootNode) {
71
+ if (!rootNode) rootNode = node.ownerDocument?.documentElement || node.getRootNode()
72
+
73
+ const root = getDelegateRoot(rootNode)
74
+
75
+ // Ensure the root listener is installed
76
+ installDelegatedEvent(eventName, rootNode)
77
+
78
+ // Get or create the handler map for this event
79
+ if (!root.handlers.has(eventName)) {
80
+ root.handlers.set(eventName, new WeakMap())
81
+ }
82
+
83
+ root.handlers.get(eventName).set(node, handler)
84
+ }
85
+
86
+ // Remove a delegated event handler
87
+ export function removeDelegatedEvent(eventName, node, rootNode) {
88
+ if (!rootNode) rootNode = node.ownerDocument?.documentElement || node.getRootNode()
89
+
90
+ const root = delegateRoots.get(rootNode)
91
+ if (!root) return
92
+
93
+ const handlerMap = root.handlers.get(eventName)
94
+ if (handlerMap) {
95
+ handlerMap.delete(node)
96
+ }
97
+ }
98
+
99
+ // Check if an event name should be delegated
100
+ export function isDelegatable(eventName) {
101
+ return DELEGATED_EVENTS.has(eventName) && !LIFECYCLE_EVENTS.has(eventName)
102
+ }
103
+
104
+ // Register event from element's `on` object or props
105
+ export function registerEvent(eventName, element, handler) {
106
+ const { node, context } = element
107
+ if (!node) return
108
+
109
+ // Capture config reference at registration time so it remains
110
+ // stable across async boundaries. The document is stored on the
111
+ // config, so getActiveDocument() resolves it via the config stack.
112
+ const ds = context?.designSystem
113
+
114
+ const wrappedHandler = (event) => {
115
+ // Push the element's design system config so runtime CSS injections
116
+ // and token resolution use the correct app scope. The document is
117
+ // stored on the config, so getActiveDocument() resolves it too.
118
+ if (ds) pushConfig(ds)
119
+ try {
120
+ const result = handler.call(element, event, element, element.state, context)
121
+ if (result && typeof result.then === 'function') {
122
+ // For async handlers, re-scope config around the promise
123
+ // resolution so continuations see the right config.
124
+ result
125
+ .catch(err => {
126
+ element.error = err
127
+ console.error('[DOMQL] Async event error:', err)
128
+ })
129
+ .finally(() => {
130
+ if (ds) popConfig()
131
+ })
132
+ // Don't pop in the outer finally — the promise chain owns it
133
+ return
134
+ }
135
+ } catch (err) {
136
+ element.error = err
137
+ console.error('[DOMQL] Event error:', err)
138
+ if (context?.strictMode) throw err
139
+ }
140
+ // Sync path — pop immediately
141
+ if (ds) popConfig()
142
+ }
143
+
144
+ if (isDelegatable(eventName)) {
145
+ // Find root node for delegation
146
+ const rootNode = context?.rootNode || node.ownerDocument?.documentElement
147
+ addDelegatedEvent(eventName, node, wrappedHandler, rootNode)
148
+ } else {
149
+ // Non-delegatable events (e.g., transitionend, animationend, resize)
150
+ node.addEventListener(eventName, wrappedHandler, listenerOptions(eventName, false))
151
+ }
152
+
153
+ // Track for cleanup
154
+ if (!element.__ref.__eventCleanup) element.__ref.__eventCleanup = []
155
+ element.__ref.__eventCleanup.push(() => {
156
+ if (isDelegatable(eventName)) {
157
+ const rootNode = context?.rootNode || node.ownerDocument?.documentElement
158
+ removeDelegatedEvent(eventName, node, rootNode)
159
+ } else {
160
+ node.removeEventListener(eventName, wrappedHandler, listenerOptions(eventName, false))
161
+ }
162
+ })
163
+ }
package/src/dispose.js ADDED
@@ -0,0 +1,76 @@
1
+ 'use strict'
2
+
3
+ // Element disposal - cleanup effects, events, and children
4
+
5
+ export function dispose(element) {
6
+ if (!element) return
7
+
8
+ const ref = element.__ref
9
+ if (!ref) return
10
+
11
+ // User-defined cleanup hooks fire BEFORE we tear refs/state/effects down,
12
+ // so handlers can still read state and call methods on the element.
13
+ // `onBeforeRemove` is for last-chance cleanup (close sockets, cancel
14
+ // in-flight requests); `onRemove` is the canonical post-unmount hook.
15
+ try { if (typeof element.onBeforeRemove === 'function') element.onBeforeRemove(element, element.state, element.context) } catch (e) { console.error('[smbls] onBeforeRemove error:', e) }
16
+
17
+ // Dispose all reactive effects
18
+ if (ref.__effects) {
19
+ for (let i = 0; i < ref.__effects.length; i++) {
20
+ ref.__effects[i].dispose()
21
+ }
22
+ ref.__effects.length = 0
23
+ }
24
+
25
+ // Cleanup event listeners
26
+ if (ref.__eventCleanup) {
27
+ for (let i = 0; i < ref.__eventCleanup.length; i++) {
28
+ ref.__eventCleanup[i]()
29
+ }
30
+ ref.__eventCleanup.length = 0
31
+ }
32
+
33
+ // Recursively dispose children
34
+ if (ref.__children) {
35
+ for (let i = 0; i < ref.__children.length; i++) {
36
+ const childKey = ref.__children[i]
37
+ const child = element[childKey]
38
+ if (child && child.__ref) {
39
+ dispose(child)
40
+ }
41
+ }
42
+ ref.__children.length = 0
43
+ }
44
+
45
+ // Remove DOM node
46
+ if (element.node?.parentNode) {
47
+ element.node.remove()
48
+ }
49
+
50
+ // Destroy state if owned
51
+ if (element.state?.destroy && element.state.__element === element) {
52
+ element.state.destroy()
53
+ }
54
+
55
+ // Remove from parent's tracking
56
+ if (element.parent?.__ref?.__children) {
57
+ const idx = element.parent.__ref.__children.indexOf(element.key)
58
+ if (idx !== -1) {
59
+ element.parent.__ref.__children.splice(idx, 1)
60
+ }
61
+ delete element.parent[element.key]
62
+ }
63
+
64
+ // Fire onRemove AFTER DOM detach + state.destroy but BEFORE clearing refs,
65
+ // so the hook can still read element.key / element.parent for logging.
66
+ try { if (typeof element.onRemove === 'function') element.onRemove(element) } catch (e) { console.error('[smbls] onRemove error:', e) }
67
+
68
+ // Clear references
69
+ element.node = null
70
+ element.parent = null
71
+ element.__ref = null
72
+ }
73
+
74
+ export function remove() {
75
+ dispose(this)
76
+ }
package/src/extends.js ADDED
@@ -0,0 +1,105 @@
1
+ 'use strict'
2
+
3
+ // Extends resolution - merges component inheritance chains
4
+ // Uses structural sharing where possible (no deepClone)
5
+
6
+ const extendsCache = new WeakMap()
7
+
8
+ export function applyExtends(element, definition, options) {
9
+ let ext = definition.extends || definition.extend
10
+ if (!ext) return
11
+
12
+ const context = element.context || options?.context || {}
13
+ const components = context.components || {}
14
+
15
+ // Normalize to array
16
+ if (!Array.isArray(ext)) ext = [ext]
17
+
18
+ // Resolve extends chain
19
+ for (let i = 0; i < ext.length; i++) {
20
+ const base = resolveExtend(ext[i], components)
21
+ // Skip self-references (component extends itself by same name)
22
+ if (base && base !== definition) {
23
+ mergeBase(element, base, options, new Set([definition]))
24
+ }
25
+ }
26
+ }
27
+
28
+ function resolveExtend(ext, components) {
29
+ if (typeof ext === 'string') {
30
+ // Support nested child access: 'Component > Child > SubChild'
31
+ if (ext.includes('>')) {
32
+ const parts = ext.split('>').map(s => s.trim())
33
+ let resolved = components[parts[0]] || components[`smbls.${parts[0]}`]
34
+ for (let i = 1; i < parts.length && resolved; i++) {
35
+ resolved = resolved[parts[i]]
36
+ }
37
+ return resolved || null
38
+ }
39
+ return components[ext] || components[`smbls.${ext}`] || null
40
+ }
41
+ if (typeof ext === 'object' && ext !== null) {
42
+ return ext
43
+ }
44
+ return null
45
+ }
46
+
47
+ function mergeBase(element, base, options, visited = new Set()) {
48
+ if (visited.has(base)) return
49
+ visited.add(base)
50
+
51
+ // Recursively resolve base's own extends first (support both 'extends' and 'extend')
52
+ const baseExtends = base.extends || base.extend
53
+ if (baseExtends) {
54
+ const context = element.context || options?.context || {}
55
+ const components = context.components || {}
56
+ let baseExt = baseExtends
57
+ if (!Array.isArray(baseExt)) baseExt = [baseExt]
58
+ for (let i = 0; i < baseExt.length; i++) {
59
+ const ancestor = resolveExtend(baseExt[i], components)
60
+ if (ancestor) mergeBase(element, ancestor, options, visited)
61
+ }
62
+ }
63
+
64
+ // Merge properties from base onto element (later extends override earlier)
65
+ for (const key in base) {
66
+ if (key === 'extends' || key === 'extend') continue
67
+
68
+ const val = base[key]
69
+ if (element[key] === undefined) {
70
+ if (val && typeof val === 'object' && !Array.isArray(val) && typeof val !== 'function') {
71
+ element[key] = { ...val }
72
+ } else {
73
+ element[key] = val
74
+ }
75
+ } else if (typeof element[key] === 'object' && typeof val === 'object' &&
76
+ !Array.isArray(element[key]) && !Array.isArray(val) &&
77
+ element[key] !== null && val !== null) {
78
+ // Deep merge objects — element (child) values override base values
79
+ // element[key] is already set from a previous extends; val is the newer/more-specific definition
80
+ // The newer definition (val) should override the older ancestor (element[key])
81
+ element[key] = deepMergeChild(element[key], val)
82
+ } else {
83
+ // Later extends override earlier for primitives/functions
84
+ element[key] = val
85
+ }
86
+ }
87
+ }
88
+
89
+ // Deep merge where override wins over base at every level
90
+ function deepMergeChild(base, override) {
91
+ const result = { ...base }
92
+ for (const key in override) {
93
+ const baseVal = result[key]
94
+ const overVal = override[key]
95
+ if (baseVal && overVal &&
96
+ typeof baseVal === 'object' && typeof overVal === 'object' &&
97
+ !Array.isArray(baseVal) && !Array.isArray(overVal) &&
98
+ typeof baseVal !== 'function' && typeof overVal !== 'function') {
99
+ result[key] = deepMergeChild(baseVal, overVal)
100
+ } else {
101
+ result[key] = overVal
102
+ }
103
+ }
104
+ return result
105
+ }
package/src/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { create, default } from './create.js'
2
+ export { update } from './update.js'
3
+ export { dispose, remove } from './dispose.js'
4
+ export { REGISTRY, LIFECYCLE_EVENTS, DELEGATED_EVENTS, MIXINS } from './registry.js'
5
+ export { addDelegatedEvent, removeDelegatedEvent, registerEvent } from './delegate.js'
6
+ export { cacheNode, detectTag, createHTMLNode, isValidTag, isSvgTag, SVG_TAGS } from './cache.js'
7
+ export { applyExtends } from './extends.js'
8
+ export { addMethods } from './methods.js'
package/src/methods.js ADDED
@@ -0,0 +1,257 @@
1
+ 'use strict'
2
+
3
+ import { update } from './update.js'
4
+ import { remove } from './dispose.js'
5
+ import { CSS_PROPS_REGISTRY } from 'css-in-props/src/props'
6
+ import {
7
+ getDB,
8
+ getQuery,
9
+ getRootData,
10
+ getRootState as _getRootState,
11
+ getRoot as _getRoot,
12
+ getRootContext as _getRootContext,
13
+ getContext as _getContext,
14
+ getChildren,
15
+ getPath,
16
+ nextElement,
17
+ previousElement,
18
+ verbose as _verbose,
19
+ warn as _warn,
20
+ error as _error,
21
+ variables as _variables,
22
+ spotByPath
23
+ } from '@symbo.ls/utils'
24
+
25
+ // Forward warnings + errors to the analyze plugin (when present) so they
26
+ // appear in the audit log alongside lifecycle events. Falls through to the
27
+ // original env-gated console.warn / throw.
28
+ function emitToAnalyze(element, level, params) {
29
+ const a = element?.context?.analyze
30
+ if (!a || !a.emit) return
31
+ if (level === 'warn' && !a.shouldCapture('warnings')) return
32
+ if (level === 'error' && !a.shouldCapture('errors')) return
33
+ const msg = params.map(p => p?.message || (typeof p === 'string' ? p : null)).filter(Boolean).join(' ')
34
+ a.emit({
35
+ id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,
36
+ ts: Date.now(),
37
+ type: level === 'error' ? 'error' : 'warning',
38
+ level,
39
+ hook: level === 'error' ? 'el.error' : 'el.warn',
40
+ element: {
41
+ key: element.key != null ? String(element.key) : null,
42
+ name: element.__name || null,
43
+ tag: element.tag || null
44
+ },
45
+ message: msg || null,
46
+ args: params
47
+ })
48
+ }
49
+
50
+ function elWarn(...params) {
51
+ emitToAnalyze(this, 'warn', params)
52
+ return _warn.apply(this, params)
53
+ }
54
+
55
+ function elError(...params) {
56
+ emitToAnalyze(this, 'error', params)
57
+ return _error.apply(this, params)
58
+ }
59
+
60
+ // Shared prototype for all elements — methods are defined once, shared by all instances
61
+ const elementProto = {
62
+ update,
63
+ remove,
64
+ getDB,
65
+ getQuery,
66
+ getRootData,
67
+ getChildren,
68
+ getPath,
69
+ nextElement,
70
+ previousElement,
71
+ verbose: _verbose,
72
+ warn: elWarn,
73
+ error: elError,
74
+ variables: _variables,
75
+ spotByPath,
76
+
77
+ set(content, options) {
78
+ this.removeContent()
79
+ if (content != null) {
80
+ const createFn = this.context?.__create
81
+ if (createFn) createFn(content, this, 'content', options)
82
+ }
83
+ },
84
+
85
+ reset(options) {
86
+ const createFn = this.context?.__create
87
+ if (!createFn) return this
88
+ const parent = this.parent
89
+ const key = this.key
90
+ const def = this.parse()
91
+ this.remove()
92
+ return createFn(def, parent, key, options)
93
+ },
94
+
95
+ lookup(predicate) {
96
+ let current = this.parent
97
+ while (current) {
98
+ if (typeof predicate === 'function') {
99
+ if (predicate(current)) return current
100
+ } else if (typeof predicate === 'string') {
101
+ if (current.key === predicate) return current
102
+ if (current[predicate] && current[predicate].__ref)
103
+ return current[predicate]
104
+ }
105
+ current = current.parent
106
+ }
107
+ return null
108
+ },
109
+
110
+ lookdown(predicate) {
111
+ const ref = this.__ref
112
+ if (!ref?.__children) return null
113
+ for (const key of ref.__children) {
114
+ const child = this[key]
115
+ if (!child) continue
116
+ if (typeof predicate === 'function') {
117
+ if (predicate(child)) return child
118
+ } else if (typeof predicate === 'string') {
119
+ if (child.key === predicate) return child
120
+ }
121
+ const found = child.lookdown?.(predicate)
122
+ if (found) return found
123
+ }
124
+ return null
125
+ },
126
+
127
+ lookdownAll(predicate, results = []) {
128
+ const ref = this.__ref
129
+ if (!ref?.__children) return results
130
+ for (const key of ref.__children) {
131
+ const child = this[key]
132
+ if (!child) continue
133
+ if (typeof predicate === 'function') {
134
+ if (predicate(child)) results.push(child)
135
+ } else if (typeof predicate === 'string') {
136
+ if (child.key === predicate) results.push(child)
137
+ }
138
+ child.lookdownAll?.(predicate, results)
139
+ }
140
+ return results
141
+ },
142
+
143
+ getRef() {
144
+ return this.__ref
145
+ },
146
+
147
+ getRoot: _getRoot,
148
+ getRootState: _getRootState,
149
+ getRootContext: _getRootContext,
150
+ getContext: _getContext,
151
+
152
+ parse(exclude) {
153
+ const result = {}
154
+ const excludeSet = exclude ? new Set(exclude) : null
155
+ for (const key in this) {
156
+ if (key === 'parent' || key === 'node' || key === '__ref') continue
157
+ if (typeof this[key] === 'function') continue
158
+ if (excludeSet?.has(key)) continue
159
+ result[key] = this[key]
160
+ }
161
+ return result
162
+ },
163
+
164
+ parseDeep(exclude) {
165
+ const result = this.parse(exclude)
166
+ const ref = this.__ref
167
+ if (ref?.__children) {
168
+ for (const key of ref.__children) {
169
+ if (this[key]?.parseDeep) {
170
+ result[key] = this[key].parseDeep(exclude)
171
+ }
172
+ }
173
+ }
174
+ return result
175
+ },
176
+
177
+ keys() {
178
+ return Object.keys(this)
179
+ },
180
+
181
+ log(...keys) {
182
+ if (keys.length) {
183
+ const data = {}
184
+ for (const k of keys) data[k] = this[k]
185
+ console.log(data)
186
+ } else {
187
+ console.log(this)
188
+ }
189
+ },
190
+
191
+ setProps(props, options) {
192
+ return this.update(props, options)
193
+ },
194
+
195
+ setNodeStyles(styles) {
196
+ if (!this.node?.style) return
197
+ for (const prop in styles) {
198
+ this.node.style[prop] = styles[prop]
199
+ }
200
+ },
201
+
202
+ call(methodName, ...args) {
203
+ const ctx = this.context
204
+ const method =
205
+ ctx?.methods?.[methodName] ||
206
+ ctx?.functions?.[methodName] ||
207
+ ctx?.utils?.[methodName] ||
208
+ this[methodName]
209
+ if (typeof method === 'function') {
210
+ return method.call(this, ...args)
211
+ }
212
+ },
213
+
214
+ removeContent() {
215
+ const ref = this.__ref
216
+ if (!ref?.__children) return
217
+ const contentChild = this.content
218
+ if (contentChild?.remove) {
219
+ contentChild.remove()
220
+ delete this.content
221
+ const idx = ref.__children.indexOf('content')
222
+ if (idx !== -1) ref.__children.splice(idx, 1)
223
+ }
224
+ if (contentChild?.node?.parentNode) {
225
+ contentChild.node.parentNode.removeChild(contentChild.node)
226
+ }
227
+ }
228
+ }
229
+
230
+ // Cache per-context prototypes so we don't recreate them for every element
231
+ const contextProtoCache = new WeakMap()
232
+
233
+ function getContextProto(ctx) {
234
+ if (!ctx) return elementProto
235
+ let proto = contextProtoCache.get(ctx)
236
+ if (proto) return proto
237
+
238
+ // Create a context-specific prototype that chains from elementProto
239
+ proto = Object.create(elementProto)
240
+ const methods = ctx.methods
241
+ if (methods) {
242
+ for (const k in methods) {
243
+ // Functions become prototype methods; non-function values (e.g. an SDK
244
+ // instance) are exposed as shared properties so `el.sdk.x` keeps working.
245
+ proto[k] = methods[k]
246
+ }
247
+ }
248
+ contextProtoCache.set(ctx, proto)
249
+ return proto
250
+ }
251
+
252
+ export function addMethods(element) {
253
+ const proto = getContextProto(element.context)
254
+ Object.setPrototypeOf(element, proto)
255
+ }
256
+
257
+ export { elementProto }
@@ -0,0 +1,79 @@
1
+ 'use strict'
2
+
3
+ // REGISTRY defines all framework-reserved property keys.
4
+ // Any key NOT in this set that starts with uppercase is treated as a child element.
5
+
6
+ export const REGISTRY = new Set([
7
+ // Extends/Inheritance
8
+ 'extends',
9
+ // Children/Content
10
+ 'children', 'content',
11
+ 'childExtends',
12
+ 'childExtendsRecursive',
13
+ 'childProps', 'inheritParentProps',
14
+ // State
15
+ 'state', 'if',
16
+ // Define
17
+ 'define', 'deps',
18
+ // DOM
19
+ 'tag', 'attr', 'style', 'text', 'html', 'value',
20
+ 'data', 'classlist', 'class',
21
+ // Scope/Context
22
+ 'scope', 'root', 'context',
23
+ // Routing/Fetching
24
+ '$router', 'routes', 'fetch',
25
+ // Meta
26
+ 'variables', 'query', 'key',
27
+ 'component', 'preventContentUpdate',
28
+ 'preventContentRecreate',
29
+ // Internal
30
+ '__name', '__ref', '__hash', '__text',
31
+ 'parent', 'node'
32
+ ])
33
+
34
+ // Mixin handlers - these get special processing during create/update
35
+ export const MIXINS = {
36
+ text: 'text',
37
+ html: 'html',
38
+ style: 'style',
39
+ attr: 'attr',
40
+ data: 'data',
41
+ classlist: 'classlist',
42
+ state: 'state',
43
+ scope: 'scope'
44
+ }
45
+
46
+ // Events that are lifecycle events (not DOM events)
47
+ export const LIFECYCLE_EVENTS = new Set([
48
+ 'start', 'init', 'complete', 'attachNode',
49
+ 'stateInit', 'stateCreated',
50
+ 'beforeUpdate', 'update', 'afterUpdate',
51
+ 'beforeClassAssign', 'lazyLoad',
52
+ 'render', 'renderRouter', 'done', 'create', 'frame',
53
+ 'startUpdate', 'stateUpdate', 'beforeStateUpdate',
54
+ 'initInspect', 'initSync', 'error',
55
+ 'beforeRemove', 'remove'
56
+ ])
57
+
58
+ // DOM events that block scroll performance unless registered `{ passive: true }`.
59
+ // Chrome logs a "Added non-passive event listener to a scroll-blocking" warning
60
+ // for each non-passive listener on these.
61
+ export const PASSIVE_EVENTS = new Set([
62
+ 'wheel', 'mousewheel',
63
+ 'touchstart', 'touchmove', 'touchend', 'touchcancel'
64
+ ])
65
+
66
+ // DOM events that should be delegated
67
+ export const DELEGATED_EVENTS = new Set([
68
+ 'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
69
+ 'mouseenter', 'mouseleave', 'mouseover', 'mouseout',
70
+ 'pointerdown', 'pointerup', 'pointermove',
71
+ 'pointerenter', 'pointerleave', 'pointerover', 'pointerout',
72
+ 'touchstart', 'touchend', 'touchmove',
73
+ 'keydown', 'keyup', 'keypress',
74
+ 'input', 'change', 'submit', 'reset',
75
+ 'focus', 'blur', 'focusin', 'focusout',
76
+ 'scroll', 'wheel',
77
+ 'contextmenu', 'drag', 'dragstart', 'dragend',
78
+ 'dragenter', 'dragleave', 'dragover', 'drop'
79
+ ])