@symbo.ls/element 3.14.2 → 3.14.4
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/CHANGELOG.md +10 -0
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/src/cache.cjs +1 -0
- package/dist/cjs/src/create.cjs +6 -0
- package/dist/cjs/src/delegate.cjs +1 -0
- package/dist/cjs/src/dispose.cjs +1 -0
- package/dist/cjs/src/extends.cjs +1 -0
- package/dist/cjs/src/index.cjs +1 -0
- package/dist/cjs/src/methods.cjs +1 -0
- package/dist/cjs/src/registry.cjs +1 -0
- package/dist/cjs/src/update.cjs +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/src/cache.js +1 -0
- package/dist/esm/src/create.js +6 -0
- package/dist/esm/src/delegate.js +1 -0
- package/dist/esm/src/dispose.js +1 -0
- package/dist/esm/src/extends.js +1 -0
- package/dist/esm/src/index.js +1 -0
- package/dist/esm/src/methods.js +1 -0
- package/dist/esm/src/registry.js +1 -0
- package/dist/esm/src/update.js +1 -0
- package/package.json +31 -10
- package/index.js +0 -1
- package/src/cache.js +0 -124
- package/src/create.js +0 -1967
- package/src/delegate.js +0 -163
- package/src/dispose.js +0 -76
- package/src/extends.js +0 -123
- package/src/index.js +0 -8
- package/src/methods.js +0 -271
- package/src/registry.js +0 -79
- package/src/update.js +0 -132
package/src/delegate.js
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
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
|
-
// FT-FRAMEWORK-EXTEND-CUSTOM: collect entries from BOTH `extends:`
|
|
9
|
-
// (plural) and `extend:` (singular). Previously the resolver did
|
|
10
|
-
// `definition.extends || definition.extend`, silently dropping one when
|
|
11
|
-
// the other was set. childsafetynet's blog-detail pages used both
|
|
12
|
-
// (`extends: 'Page'` for route-mode + `extend: 'BlogArticle'` for the
|
|
13
|
-
// article shell) and only Page resolved — BlogArticle's child keys
|
|
14
|
-
// never reached the render tree.
|
|
15
|
-
function collectExtendsList (definition) {
|
|
16
|
-
const list = []
|
|
17
|
-
if (definition.extends) {
|
|
18
|
-
if (Array.isArray(definition.extends)) list.push(...definition.extends)
|
|
19
|
-
else list.push(definition.extends)
|
|
20
|
-
}
|
|
21
|
-
if (definition.extend) {
|
|
22
|
-
if (Array.isArray(definition.extend)) list.push(...definition.extend)
|
|
23
|
-
else list.push(definition.extend)
|
|
24
|
-
}
|
|
25
|
-
return list
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function applyExtends(element, definition, options) {
|
|
29
|
-
const ext = collectExtendsList(definition)
|
|
30
|
-
if (!ext.length) return
|
|
31
|
-
|
|
32
|
-
const context = element.context || options?.context || {}
|
|
33
|
-
const components = context.components || {}
|
|
34
|
-
|
|
35
|
-
// Resolve extends chain
|
|
36
|
-
for (let i = 0; i < ext.length; i++) {
|
|
37
|
-
const base = resolveExtend(ext[i], components)
|
|
38
|
-
// Skip self-references (component extends itself by same name)
|
|
39
|
-
if (base && base !== definition) {
|
|
40
|
-
mergeBase(element, base, options, new Set([definition]))
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function resolveExtend(ext, components) {
|
|
46
|
-
if (typeof ext === 'string') {
|
|
47
|
-
// Support nested child access: 'Component > Child > SubChild'
|
|
48
|
-
if (ext.includes('>')) {
|
|
49
|
-
const parts = ext.split('>').map(s => s.trim())
|
|
50
|
-
let resolved = components[parts[0]] || components[`smbls.${parts[0]}`]
|
|
51
|
-
for (let i = 1; i < parts.length && resolved; i++) {
|
|
52
|
-
resolved = resolved[parts[i]]
|
|
53
|
-
}
|
|
54
|
-
return resolved || null
|
|
55
|
-
}
|
|
56
|
-
return components[ext] || components[`smbls.${ext}`] || null
|
|
57
|
-
}
|
|
58
|
-
if (typeof ext === 'object' && ext !== null) {
|
|
59
|
-
return ext
|
|
60
|
-
}
|
|
61
|
-
return null
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function mergeBase(element, base, options, visited = new Set()) {
|
|
65
|
-
if (visited.has(base)) return
|
|
66
|
-
visited.add(base)
|
|
67
|
-
|
|
68
|
-
// Recursively resolve base's own extends first — combining both
|
|
69
|
-
// `extends:` (plural) and `extend:` (singular). Mirrors the entry-point
|
|
70
|
-
// collectExtendsList logic so chains like Page → BlogArticle → Flex
|
|
71
|
-
// resolve correctly when intermediate components mix the two forms.
|
|
72
|
-
const baseExt = collectExtendsList(base)
|
|
73
|
-
if (baseExt.length) {
|
|
74
|
-
const context = element.context || options?.context || {}
|
|
75
|
-
const components = context.components || {}
|
|
76
|
-
for (let i = 0; i < baseExt.length; i++) {
|
|
77
|
-
const ancestor = resolveExtend(baseExt[i], components)
|
|
78
|
-
if (ancestor) mergeBase(element, ancestor, options, visited)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Merge properties from base onto element (later extends override earlier)
|
|
83
|
-
for (const key in base) {
|
|
84
|
-
if (key === 'extends' || key === 'extend') continue
|
|
85
|
-
|
|
86
|
-
const val = base[key]
|
|
87
|
-
if (element[key] === undefined) {
|
|
88
|
-
if (val && typeof val === 'object' && !Array.isArray(val) && typeof val !== 'function') {
|
|
89
|
-
element[key] = { ...val }
|
|
90
|
-
} else {
|
|
91
|
-
element[key] = val
|
|
92
|
-
}
|
|
93
|
-
} else if (typeof element[key] === 'object' && typeof val === 'object' &&
|
|
94
|
-
!Array.isArray(element[key]) && !Array.isArray(val) &&
|
|
95
|
-
element[key] !== null && val !== null) {
|
|
96
|
-
// Deep merge objects — element (child) values override base values
|
|
97
|
-
// element[key] is already set from a previous extends; val is the newer/more-specific definition
|
|
98
|
-
// The newer definition (val) should override the older ancestor (element[key])
|
|
99
|
-
element[key] = deepMergeChild(element[key], val)
|
|
100
|
-
} else {
|
|
101
|
-
// Later extends override earlier for primitives/functions
|
|
102
|
-
element[key] = val
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Deep merge where override wins over base at every level
|
|
108
|
-
function deepMergeChild(base, override) {
|
|
109
|
-
const result = { ...base }
|
|
110
|
-
for (const key in override) {
|
|
111
|
-
const baseVal = result[key]
|
|
112
|
-
const overVal = override[key]
|
|
113
|
-
if (baseVal && overVal &&
|
|
114
|
-
typeof baseVal === 'object' && typeof overVal === 'object' &&
|
|
115
|
-
!Array.isArray(baseVal) && !Array.isArray(overVal) &&
|
|
116
|
-
typeof baseVal !== 'function' && typeof overVal !== 'function') {
|
|
117
|
-
result[key] = deepMergeChild(baseVal, overVal)
|
|
118
|
-
} else {
|
|
119
|
-
result[key] = overVal
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return result
|
|
123
|
-
}
|
package/src/index.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
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
|
-
const exec = this.__ref?.__exec
|
|
156
|
-
for (const key in this) {
|
|
157
|
-
if (key === 'parent' || key === 'node' || key === '__ref') continue
|
|
158
|
-
if (excludeSet?.has(key)) continue
|
|
159
|
-
// Reactive props (text/html/value/style/attr/css/custom-prop fns) are
|
|
160
|
-
// wrapped at create time; the live value is the evaluated result, but
|
|
161
|
-
// ref.__exec[key] holds the original function (or template). Restore it
|
|
162
|
-
// so reset() / parseDeep() reproduce the reactive definition rather
|
|
163
|
-
// than baking in a snapshot value.
|
|
164
|
-
if (exec && exec[key] !== undefined) {
|
|
165
|
-
result[key] = exec[key]
|
|
166
|
-
continue
|
|
167
|
-
}
|
|
168
|
-
if (typeof this[key] === 'function') continue
|
|
169
|
-
result[key] = this[key]
|
|
170
|
-
}
|
|
171
|
-
return result
|
|
172
|
-
},
|
|
173
|
-
|
|
174
|
-
parseDeep(exclude) {
|
|
175
|
-
const result = this.parse(exclude)
|
|
176
|
-
const ref = this.__ref
|
|
177
|
-
if (ref?.__children) {
|
|
178
|
-
for (const key of ref.__children) {
|
|
179
|
-
if (this[key]?.parseDeep) {
|
|
180
|
-
result[key] = this[key].parseDeep(exclude)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return result
|
|
185
|
-
},
|
|
186
|
-
|
|
187
|
-
keys() {
|
|
188
|
-
return Object.keys(this)
|
|
189
|
-
},
|
|
190
|
-
|
|
191
|
-
log(...keys) {
|
|
192
|
-
if (keys.length) {
|
|
193
|
-
const data = {}
|
|
194
|
-
for (const k of keys) data[k] = this[k]
|
|
195
|
-
console.log(data)
|
|
196
|
-
} else {
|
|
197
|
-
console.log(this)
|
|
198
|
-
}
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
setProps(props, options) {
|
|
202
|
-
return this.update(props, options)
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
setNodeStyles(styles) {
|
|
206
|
-
if (!this.node?.style) return
|
|
207
|
-
for (const prop in styles) {
|
|
208
|
-
const val = styles[prop]
|
|
209
|
-
if (val == null) continue
|
|
210
|
-
const cssProp = prop.replace(/[A-Z]/g, m => '-' + m.toLowerCase())
|
|
211
|
-
const str = String(val).replace(/\s*!important\s*$/, '')
|
|
212
|
-
this.node.style.setProperty(cssProp, str, 'important')
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
|
|
216
|
-
call(methodName, ...args) {
|
|
217
|
-
const ctx = this.context
|
|
218
|
-
const method =
|
|
219
|
-
ctx?.methods?.[methodName] ||
|
|
220
|
-
ctx?.functions?.[methodName] ||
|
|
221
|
-
ctx?.utils?.[methodName] ||
|
|
222
|
-
this[methodName]
|
|
223
|
-
if (typeof method === 'function') {
|
|
224
|
-
return method.call(this, ...args)
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
|
|
228
|
-
removeContent() {
|
|
229
|
-
const ref = this.__ref
|
|
230
|
-
if (!ref?.__children) return
|
|
231
|
-
const contentChild = this.content
|
|
232
|
-
if (contentChild?.remove) {
|
|
233
|
-
contentChild.remove()
|
|
234
|
-
delete this.content
|
|
235
|
-
const idx = ref.__children.indexOf('content')
|
|
236
|
-
if (idx !== -1) ref.__children.splice(idx, 1)
|
|
237
|
-
}
|
|
238
|
-
if (contentChild?.node?.parentNode) {
|
|
239
|
-
contentChild.node.parentNode.removeChild(contentChild.node)
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Cache per-context prototypes so we don't recreate them for every element
|
|
245
|
-
const contextProtoCache = new WeakMap()
|
|
246
|
-
|
|
247
|
-
function getContextProto(ctx) {
|
|
248
|
-
if (!ctx) return elementProto
|
|
249
|
-
let proto = contextProtoCache.get(ctx)
|
|
250
|
-
if (proto) return proto
|
|
251
|
-
|
|
252
|
-
// Create a context-specific prototype that chains from elementProto
|
|
253
|
-
proto = Object.create(elementProto)
|
|
254
|
-
const methods = ctx.methods
|
|
255
|
-
if (methods) {
|
|
256
|
-
for (const k in methods) {
|
|
257
|
-
// Functions become prototype methods; non-function values (e.g. an SDK
|
|
258
|
-
// instance) are exposed as shared properties so `el.sdk.x` keeps working.
|
|
259
|
-
proto[k] = methods[k]
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
contextProtoCache.set(ctx, proto)
|
|
263
|
-
return proto
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
export function addMethods(element) {
|
|
267
|
-
const proto = getContextProto(element.context)
|
|
268
|
-
Object.setPrototypeOf(element, proto)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export { elementProto }
|