glass-easel 0.1.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.
- package/README.md +40 -0
- package/dist/glass_easel.all.d.ts +1 -0
- package/dist/glass_easel.all.js +2 -0
- package/dist/glass_easel.all.js.map +1 -0
- package/dist/glass_easel.domlike.global.d.ts +1 -0
- package/dist/glass_easel.domlike.global.js +2 -0
- package/dist/glass_easel.domlike.global.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types/src/backend/backend_protocol.d.ts +119 -0
- package/dist/types/src/backend/backend_protocol.d.ts.map +1 -0
- package/dist/types/src/backend/composed_backend_protocol.d.ts +90 -0
- package/dist/types/src/backend/composed_backend_protocol.d.ts.map +1 -0
- package/dist/types/src/backend/domlike_backend_protocol.d.ts +76 -0
- package/dist/types/src/backend/domlike_backend_protocol.d.ts.map +1 -0
- package/dist/types/src/backend/mode.d.ts +46 -0
- package/dist/types/src/backend/mode.d.ts.map +1 -0
- package/dist/types/src/backend/suggested_backend_protocol.d.ts +30 -0
- package/dist/types/src/backend/suggested_backend_protocol.d.ts.map +1 -0
- package/dist/types/src/behavior.d.ts +428 -0
- package/dist/types/src/behavior.d.ts.map +1 -0
- package/dist/types/src/class_list.d.ts +79 -0
- package/dist/types/src/class_list.d.ts.map +1 -0
- package/dist/types/src/component.d.ts +291 -0
- package/dist/types/src/component.d.ts.map +1 -0
- package/dist/types/src/component_params.d.ts +239 -0
- package/dist/types/src/component_params.d.ts.map +1 -0
- package/dist/types/src/component_space.d.ts +164 -0
- package/dist/types/src/component_space.d.ts.map +1 -0
- package/dist/types/src/data_path.d.ts +5 -0
- package/dist/types/src/data_path.d.ts.map +1 -0
- package/dist/types/src/data_proxy.d.ts +107 -0
- package/dist/types/src/data_proxy.d.ts.map +1 -0
- package/dist/types/src/data_utils.d.ts +3 -0
- package/dist/types/src/data_utils.d.ts.map +1 -0
- package/dist/types/src/element.d.ts +275 -0
- package/dist/types/src/element.d.ts.map +1 -0
- package/dist/types/src/element_iterator.d.ts +43 -0
- package/dist/types/src/element_iterator.d.ts.map +1 -0
- package/dist/types/src/event.d.ts +104 -0
- package/dist/types/src/event.d.ts.map +1 -0
- package/dist/types/src/external_shadow_tree.d.ts +20 -0
- package/dist/types/src/external_shadow_tree.d.ts.map +1 -0
- package/dist/types/src/func_arr.d.ts +39 -0
- package/dist/types/src/func_arr.d.ts.map +1 -0
- package/dist/types/src/global_options.d.ts +111 -0
- package/dist/types/src/global_options.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +43 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/mutation_observer.d.ts +79 -0
- package/dist/types/src/mutation_observer.d.ts.map +1 -0
- package/dist/types/src/native_node.d.ts +8 -0
- package/dist/types/src/native_node.d.ts.map +1 -0
- package/dist/types/src/node.d.ts +49 -0
- package/dist/types/src/node.d.ts.map +1 -0
- package/dist/types/src/relation.d.ts +47 -0
- package/dist/types/src/relation.d.ts.map +1 -0
- package/dist/types/src/render.d.ts +3 -0
- package/dist/types/src/render.d.ts.map +1 -0
- package/dist/types/src/selector.d.ts +32 -0
- package/dist/types/src/selector.d.ts.map +1 -0
- package/dist/types/src/shadow_root.d.ts +136 -0
- package/dist/types/src/shadow_root.d.ts.map +1 -0
- package/dist/types/src/template_engine.d.ts +18 -0
- package/dist/types/src/template_engine.d.ts.map +1 -0
- package/dist/types/src/text_node.d.ts +32 -0
- package/dist/types/src/text_node.d.ts.map +1 -0
- package/dist/types/src/tmpl/index.d.ts +18 -0
- package/dist/types/src/tmpl/index.d.ts.map +1 -0
- package/dist/types/src/tmpl/native_rendering.d.ts +45 -0
- package/dist/types/src/tmpl/native_rendering.d.ts.map +1 -0
- package/dist/types/src/tmpl/proc_gen_wrapper.d.ts +80 -0
- package/dist/types/src/tmpl/proc_gen_wrapper.d.ts.map +1 -0
- package/dist/types/src/tmpl/proc_gen_wrapper_dom.d.ts +50 -0
- package/dist/types/src/tmpl/proc_gen_wrapper_dom.d.ts.map +1 -0
- package/dist/types/src/tmpl/range_list_diff.d.ts +19 -0
- package/dist/types/src/tmpl/range_list_diff.d.ts.map +1 -0
- package/dist/types/src/trait_behaviors.d.ts +38 -0
- package/dist/types/src/trait_behaviors.d.ts.map +1 -0
- package/dist/types/src/virtual_node.d.ts +10 -0
- package/dist/types/src/virtual_node.d.ts.map +1 -0
- package/dist/types/tests/backend/domlike.test.d.ts +2 -0
- package/dist/types/tests/backend/domlike.test.d.ts.map +1 -0
- package/dist/types/tests/base/env.d.ts +29 -0
- package/dist/types/tests/base/env.d.ts.map +1 -0
- package/dist/types/tests/base/match.d.ts +9 -0
- package/dist/types/tests/base/match.d.ts.map +1 -0
- package/dist/types/tests/core/backend.test.d.ts +2 -0
- package/dist/types/tests/core/backend.test.d.ts.map +1 -0
- package/dist/types/tests/core/behavior.test.d.ts +2 -0
- package/dist/types/tests/core/behavior.test.d.ts.map +1 -0
- package/dist/types/tests/core/component_space.test.d.ts +2 -0
- package/dist/types/tests/core/component_space.test.d.ts.map +1 -0
- package/dist/types/tests/core/data_update.test.d.ts +2 -0
- package/dist/types/tests/core/data_update.test.d.ts.map +1 -0
- package/dist/types/tests/core/misc.test.d.ts +2 -0
- package/dist/types/tests/core/misc.test.d.ts.map +1 -0
- package/dist/types/tests/core/placeholder.test.d.ts +2 -0
- package/dist/types/tests/core/placeholder.test.d.ts.map +1 -0
- package/dist/types/tests/core/slot.test.d.ts +2 -0
- package/dist/types/tests/core/slot.test.d.ts.map +1 -0
- package/dist/types/tests/core/trait_behaviors.test.d.ts +2 -0
- package/dist/types/tests/core/trait_behaviors.test.d.ts.map +1 -0
- package/dist/types/tests/tmpl/binding_map.test.d.ts +2 -0
- package/dist/types/tests/tmpl/binding_map.test.d.ts.map +1 -0
- package/dist/types/tests/tmpl/event.test.d.ts +2 -0
- package/dist/types/tests/tmpl/event.test.d.ts.map +1 -0
- package/dist/types/tests/tmpl/expression.test.d.ts +2 -0
- package/dist/types/tests/tmpl/expression.test.d.ts.map +1 -0
- package/dist/types/tests/tmpl/lvalue.test.d.ts +2 -0
- package/dist/types/tests/tmpl/lvalue.test.d.ts.map +1 -0
- package/dist/types/tests/tmpl/native_rendering.test.d.ts +2 -0
- package/dist/types/tests/tmpl/native_rendering.test.d.ts.map +1 -0
- package/dist/types/tests/tmpl/structure.test.d.ts +2 -0
- package/dist/types/tests/tmpl/structure.test.d.ts.map +1 -0
- package/dist/types/tests/types/chaining.test.d.ts +2 -0
- package/dist/types/tests/types/chaining.test.d.ts.map +1 -0
- package/dist/types/tests/types/createElement.test.d.ts +2 -0
- package/dist/types/tests/types/createElement.test.d.ts.map +1 -0
- package/dist/types/tests/types/definition.test.d.ts +2 -0
- package/dist/types/tests/types/definition.test.d.ts.map +1 -0
- package/guide/zh_CN/advanced/binding_map_update.md +32 -0
- package/guide/zh_CN/advanced/build_args.md +28 -0
- package/guide/zh_CN/advanced/component_filter.md +70 -0
- package/guide/zh_CN/advanced/component_space.md +124 -0
- package/guide/zh_CN/advanced/custom_backend.md +53 -0
- package/guide/zh_CN/advanced/error_listener.md +32 -0
- package/guide/zh_CN/advanced/external_component.md +73 -0
- package/guide/zh_CN/advanced/template_engine.md +61 -0
- package/guide/zh_CN/appendix/backend_protocol.md +501 -0
- package/guide/zh_CN/appendix/list_diff_algorithm.md +406 -0
- package/guide/zh_CN/basic/beginning.md +94 -0
- package/guide/zh_CN/basic/component.md +156 -0
- package/guide/zh_CN/basic/event.md +169 -0
- package/guide/zh_CN/basic/lifetime.md +66 -0
- package/guide/zh_CN/basic/method.md +62 -0
- package/guide/zh_CN/basic/template.md +135 -0
- package/guide/zh_CN/data_management/advanced_update.md +170 -0
- package/guide/zh_CN/data_management/data_deep_copy.md +157 -0
- package/guide/zh_CN/data_management/data_observer.md +154 -0
- package/guide/zh_CN/data_management/property_early_init.md +31 -0
- package/guide/zh_CN/data_management/pure_data_pattern.md +21 -0
- package/guide/zh_CN/index.md +93 -0
- package/guide/zh_CN/interaction/behavior.md +52 -0
- package/guide/zh_CN/interaction/component_path.md +37 -0
- package/guide/zh_CN/interaction/generic.md +73 -0
- package/guide/zh_CN/interaction/placeholder.md +40 -0
- package/guide/zh_CN/interaction/relation.md +151 -0
- package/guide/zh_CN/interaction/slot.md +137 -0
- package/guide/zh_CN/interaction/template_import.md +94 -0
- package/guide/zh_CN/interaction/trait_behavior.md +117 -0
- package/guide/zh_CN/styling/external_class.md +46 -0
- package/guide/zh_CN/styling/style_isolation.md +54 -0
- package/guide/zh_CN/styling/virtual_host.md +52 -0
- package/guide/zh_CN/tree/element_iterator.md +54 -0
- package/guide/zh_CN/tree/mutation_observer.md +52 -0
- package/guide/zh_CN/tree/node_tree.md +142 -0
- package/guide/zh_CN/tree/node_tree_modification.md +78 -0
- package/guide/zh_CN/tree/selector.md +66 -0
- package/jest.config.js +6 -0
- package/jest.dts.config.js +9 -0
- package/jest.unit.config.js +14 -0
- package/package.json +28 -0
- package/src/backend/backend_protocol.ts +313 -0
- package/src/backend/composed_backend_protocol.ts +252 -0
- package/src/backend/domlike_backend_protocol.ts +370 -0
- package/src/backend/mode.ts +51 -0
- package/src/backend/suggested_backend_protocol.ts +83 -0
- package/src/behavior.ts +1655 -0
- package/src/bootstrap_dom_dev.js +22 -0
- package/src/class_list.ts +376 -0
- package/src/component.ts +1309 -0
- package/src/component_params.ts +461 -0
- package/src/component_space.ts +547 -0
- package/src/data_path.ts +225 -0
- package/src/data_proxy.ts +670 -0
- package/src/data_utils.ts +50 -0
- package/src/element.ts +1966 -0
- package/src/element_iterator.ts +158 -0
- package/src/event.ts +401 -0
- package/src/external_shadow_tree.ts +27 -0
- package/src/func_arr.ts +198 -0
- package/src/global_options.ts +242 -0
- package/src/index.ts +187 -0
- package/src/mutation_observer.ts +252 -0
- package/src/native_node.ts +74 -0
- package/src/node.ts +174 -0
- package/src/relation.ts +380 -0
- package/src/render.ts +25 -0
- package/src/selector.ts +218 -0
- package/src/shadow_root.ts +766 -0
- package/src/template_engine.ts +45 -0
- package/src/text_node.ts +149 -0
- package/src/tmpl/index.ts +199 -0
- package/src/tmpl/native_rendering.ts +175 -0
- package/src/tmpl/proc_gen_wrapper.ts +954 -0
- package/src/tmpl/proc_gen_wrapper_dom.ts +230 -0
- package/src/tmpl/range_list_diff.ts +443 -0
- package/src/trait_behaviors.ts +51 -0
- package/src/virtual_node.ts +51 -0
- package/tests/backend/domlike.test.ts +254 -0
- package/tests/base/env.ts +78 -0
- package/tests/base/match.ts +185 -0
- package/tests/core/backend.test.ts +144 -0
- package/tests/core/behavior.test.ts +546 -0
- package/tests/core/component_space.test.ts +212 -0
- package/tests/core/data_update.test.ts +461 -0
- package/tests/core/misc.test.ts +339 -0
- package/tests/core/placeholder.test.ts +180 -0
- package/tests/core/slot.test.ts +1495 -0
- package/tests/core/trait_behaviors.test.ts +153 -0
- package/tests/legacy/README.md +3 -0
- package/tests/legacy/behavior.test.js +293 -0
- package/tests/legacy/component.test.js +1247 -0
- package/tests/legacy/data_path.test.js +149 -0
- package/tests/legacy/data_proxy.test.js +759 -0
- package/tests/legacy/element_iterator.test.js +148 -0
- package/tests/legacy/event.test.js +849 -0
- package/tests/legacy/external.test.js +510 -0
- package/tests/legacy/extra_info.test.js +109 -0
- package/tests/legacy/generics.test.js +176 -0
- package/tests/legacy/mutation_observer.test.js +210 -0
- package/tests/legacy/relation.test.js +517 -0
- package/tests/legacy/selector.test.js +263 -0
- package/tests/legacy/slot.test.js +915 -0
- package/tests/legacy/virtual.test.js +394 -0
- package/tests/tmpl/binding_map.test.ts +208 -0
- package/tests/tmpl/event.test.ts +206 -0
- package/tests/tmpl/expression.test.ts +429 -0
- package/tests/tmpl/lvalue.test.ts +160 -0
- package/tests/tmpl/native_rendering.test.ts +155 -0
- package/tests/tmpl/structure.test.ts +998 -0
- package/tests/types/chaining.test.ts +614 -0
- package/tests/types/createElement.test.ts +82 -0
- package/tests/types/definition.test.ts +442 -0
- package/tsconfig.json +11 -0
- package/webpack.config.js +270 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Element,
|
|
3
|
+
} from './element'
|
|
4
|
+
import {
|
|
5
|
+
TextNode,
|
|
6
|
+
} from './text_node'
|
|
7
|
+
import {
|
|
8
|
+
Component,
|
|
9
|
+
} from './component'
|
|
10
|
+
import {
|
|
11
|
+
Node,
|
|
12
|
+
} from './node'
|
|
13
|
+
|
|
14
|
+
/** The iterator direction and order */
|
|
15
|
+
export const enum ElementIteratorType {
|
|
16
|
+
/** Iterate all ancestors in shadow tree */
|
|
17
|
+
ShadowAncestors = 'shadow-ancestors',
|
|
18
|
+
/** Iterate all ancestors in composed tree */
|
|
19
|
+
ComposedAncestors = 'composed-ancestors',
|
|
20
|
+
/** Iterate all descendants in shadow tree, returning parents before their children */
|
|
21
|
+
ShadowDescendantsRootFirst = 'shadow-descendants-root-first',
|
|
22
|
+
/** Iterate all descendants in shadow tree, returning parents after their children */
|
|
23
|
+
ShadowDescendantsRootLast = 'shadow-descendants-root-last',
|
|
24
|
+
/** Iterate all descendants in composed tree, returning parents before their children */
|
|
25
|
+
ComposedDescendantsRootFirst = 'composed-descendants-root-first',
|
|
26
|
+
/** Iterate all descendants in composed tree, returning parents after their children */
|
|
27
|
+
ComposedDescendantsRootLast = 'composed-descendants-root-last',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* An iterator for node tree traversal
|
|
32
|
+
*
|
|
33
|
+
* This iterator is convinient but seems a little slower.
|
|
34
|
+
*/
|
|
35
|
+
export class ElementIterator {
|
|
36
|
+
private _$node: Node
|
|
37
|
+
private _$nodeTypeLimit: unknown
|
|
38
|
+
private _$composed: boolean
|
|
39
|
+
private _$isAncestor: boolean
|
|
40
|
+
private _$rootFirst: boolean
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create an iterator with type specified
|
|
44
|
+
*
|
|
45
|
+
* The `nodeTypeLimit` is used to limit which kind of nodes will be returned.
|
|
46
|
+
* It limits the returned result by an `instanceof` call.
|
|
47
|
+
* The default value is `Element` ,
|
|
48
|
+
* which means only elements will be returned (text nodes will not).
|
|
49
|
+
* Consider specifying `Node` if text nodes need to be returned as well as elements.
|
|
50
|
+
* Specify `Component` will only return components.
|
|
51
|
+
*/
|
|
52
|
+
constructor(node: Node, type: ElementIteratorType, nodeTypeLimit: unknown = Element) {
|
|
53
|
+
if (!(node instanceof Element) && !(node instanceof TextNode)) {
|
|
54
|
+
throw new Error('Element iterators can only be used in elements or text nodes')
|
|
55
|
+
}
|
|
56
|
+
this._$node = node
|
|
57
|
+
this._$nodeTypeLimit = nodeTypeLimit || Element
|
|
58
|
+
if (
|
|
59
|
+
type === ElementIteratorType.ShadowAncestors
|
|
60
|
+
|| type === ElementIteratorType.ShadowDescendantsRootFirst
|
|
61
|
+
|| type === ElementIteratorType.ShadowDescendantsRootLast
|
|
62
|
+
) {
|
|
63
|
+
this._$composed = false
|
|
64
|
+
} else if (
|
|
65
|
+
type === ElementIteratorType.ComposedAncestors
|
|
66
|
+
|| type === ElementIteratorType.ComposedDescendantsRootFirst
|
|
67
|
+
|| type === ElementIteratorType.ComposedDescendantsRootLast
|
|
68
|
+
) {
|
|
69
|
+
this._$composed = true
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error(`Unrecognized iterator type "${String(type)}"`)
|
|
72
|
+
}
|
|
73
|
+
if (
|
|
74
|
+
type === ElementIteratorType.ShadowAncestors
|
|
75
|
+
|| type === ElementIteratorType.ComposedAncestors
|
|
76
|
+
) {
|
|
77
|
+
this._$isAncestor = true
|
|
78
|
+
} else {
|
|
79
|
+
this._$isAncestor = false
|
|
80
|
+
}
|
|
81
|
+
if (
|
|
82
|
+
type === ElementIteratorType.ShadowDescendantsRootFirst
|
|
83
|
+
|| type === ElementIteratorType.ComposedDescendantsRootFirst
|
|
84
|
+
) {
|
|
85
|
+
this._$rootFirst = true
|
|
86
|
+
} else {
|
|
87
|
+
this._$rootFirst = false
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Same as constructor (for backward compatibility) */
|
|
92
|
+
static create(node: Node, type: ElementIteratorType, nodeTypeLimit?: unknown): ElementIterator {
|
|
93
|
+
return new ElementIterator(node, type, nodeTypeLimit)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
forEach(f: (node: Node) => boolean) {
|
|
97
|
+
const nodeTypeLimit: any = this._$nodeTypeLimit
|
|
98
|
+
const composed = this._$composed
|
|
99
|
+
if (this._$isAncestor) {
|
|
100
|
+
const rec = (node: Node): boolean => {
|
|
101
|
+
let prev: Node | null = null
|
|
102
|
+
let cur = node
|
|
103
|
+
for (;;) {
|
|
104
|
+
if (composed && prev) {
|
|
105
|
+
if (cur instanceof Component && !cur._$external) {
|
|
106
|
+
const slot = cur.getShadowRoot()!.getContainingSlot(prev)
|
|
107
|
+
if (slot) {
|
|
108
|
+
if (rec(slot) === false) break
|
|
109
|
+
} else {
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (cur instanceof nodeTypeLimit) {
|
|
115
|
+
if (f(cur) === false) return false
|
|
116
|
+
}
|
|
117
|
+
if (!cur.parentNode) break
|
|
118
|
+
prev = cur
|
|
119
|
+
cur = cur.parentNode
|
|
120
|
+
}
|
|
121
|
+
return true
|
|
122
|
+
}
|
|
123
|
+
rec(this._$node)
|
|
124
|
+
} else {
|
|
125
|
+
const rootFirst = this._$rootFirst
|
|
126
|
+
const rec = (node: Node): boolean => {
|
|
127
|
+
if (rootFirst) {
|
|
128
|
+
if (node instanceof nodeTypeLimit) {
|
|
129
|
+
if (f(node) === false) return false
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (node instanceof Element) {
|
|
133
|
+
let interrupted = false
|
|
134
|
+
const childFn = (child: Node) => {
|
|
135
|
+
if (rec(child) === false) {
|
|
136
|
+
interrupted = true
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
if (composed) {
|
|
142
|
+
node.forEachComposedChild(childFn)
|
|
143
|
+
} else {
|
|
144
|
+
node.childNodes.forEach(childFn)
|
|
145
|
+
}
|
|
146
|
+
if (interrupted) return false
|
|
147
|
+
}
|
|
148
|
+
if (!rootFirst) {
|
|
149
|
+
if (node instanceof nodeTypeLimit) {
|
|
150
|
+
if (f(node) === false) return false
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return true
|
|
154
|
+
}
|
|
155
|
+
rec(this._$node)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
package/src/event.ts
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FuncArrWithMeta,
|
|
3
|
+
} from './func_arr'
|
|
4
|
+
import {
|
|
5
|
+
Element,
|
|
6
|
+
Component,
|
|
7
|
+
GeneralComponent,
|
|
8
|
+
ShadowRoot,
|
|
9
|
+
GeneralBackendElement,
|
|
10
|
+
ExternalShadowRoot,
|
|
11
|
+
} from '.'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Options for an event
|
|
15
|
+
*/
|
|
16
|
+
export type EventOptions = {
|
|
17
|
+
originalEvent?: unknown
|
|
18
|
+
bubbles?: boolean
|
|
19
|
+
composed?: boolean
|
|
20
|
+
capturePhase?: boolean
|
|
21
|
+
extraFields?: { [key: string]: unknown }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Options for an event listener
|
|
26
|
+
*/
|
|
27
|
+
export type EventListenerOptions = {
|
|
28
|
+
/** Always stop bubbling after this listener */
|
|
29
|
+
final?: boolean
|
|
30
|
+
/** Mark mutated after this listener (ignored if `final` is true) */
|
|
31
|
+
mutated?: boolean
|
|
32
|
+
/** Listen in the capture phase */
|
|
33
|
+
capture?: boolean
|
|
34
|
+
/** The same as `capture` for compatibility */
|
|
35
|
+
useCapture?: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Event bubbling control
|
|
40
|
+
*/
|
|
41
|
+
export const enum EventBubbleStatus {
|
|
42
|
+
Normal = 0,
|
|
43
|
+
NoDefault = 1,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type EventListener<TDetail> = (ev: ShadowedEvent<TDetail>) => boolean | void
|
|
47
|
+
|
|
48
|
+
let relativeTimeStamp = Date.now()
|
|
49
|
+
let prevTimeStamp = relativeTimeStamp
|
|
50
|
+
|
|
51
|
+
const getCurrentTimeStamp = () => {
|
|
52
|
+
const ts = Date.now()
|
|
53
|
+
if (ts < prevTimeStamp) {
|
|
54
|
+
relativeTimeStamp += ts - prevTimeStamp
|
|
55
|
+
}
|
|
56
|
+
prevTimeStamp = ts
|
|
57
|
+
return ts - relativeTimeStamp
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const enum MutLevel {
|
|
61
|
+
None = 0,
|
|
62
|
+
Mut = 1,
|
|
63
|
+
Final = 2,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type EventFuncArr<TDetail> = {
|
|
67
|
+
mutCount: number,
|
|
68
|
+
finalCount: number,
|
|
69
|
+
funcArr: FuncArrWithMeta<EventListener<TDetail>, MutLevel>,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const enum FinalChanged {
|
|
73
|
+
NotChanged = 0,
|
|
74
|
+
Failed,
|
|
75
|
+
Init,
|
|
76
|
+
Added,
|
|
77
|
+
Removed,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The target of an event */
|
|
81
|
+
export class EventTarget<TEvents extends { [type: string]: unknown }> {
|
|
82
|
+
listeners = Object.create(null) as {
|
|
83
|
+
[T in keyof TEvents]: EventFuncArr<TEvents[T]>
|
|
84
|
+
}
|
|
85
|
+
captureListeners: {
|
|
86
|
+
[T in keyof TEvents]: EventFuncArr<TEvents[T]>
|
|
87
|
+
} | null = null
|
|
88
|
+
|
|
89
|
+
addListener<T extends string>(
|
|
90
|
+
name: T,
|
|
91
|
+
func: EventListener<TEvents[T]>,
|
|
92
|
+
options: EventListenerOptions = {},
|
|
93
|
+
): FinalChanged {
|
|
94
|
+
// eslint-disable-next-line no-nested-ternary
|
|
95
|
+
const mutLevel = options.final
|
|
96
|
+
? MutLevel.Final
|
|
97
|
+
: (options.mutated ? MutLevel.Mut : MutLevel.None)
|
|
98
|
+
let listeners: { [T in keyof TEvents]: EventFuncArr<TEvents[T]> }
|
|
99
|
+
if (options.capture || options.useCapture) {
|
|
100
|
+
if (this.captureListeners) {
|
|
101
|
+
listeners = this.captureListeners
|
|
102
|
+
} else {
|
|
103
|
+
listeners = Object.create(null) as {
|
|
104
|
+
[T in keyof TEvents]: EventFuncArr<TEvents[T]>
|
|
105
|
+
}
|
|
106
|
+
this.captureListeners = listeners
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
listeners = this.listeners
|
|
110
|
+
}
|
|
111
|
+
let efa: EventFuncArr<TEvents[T]>
|
|
112
|
+
let initialized: boolean
|
|
113
|
+
if (listeners[name]) {
|
|
114
|
+
initialized = true
|
|
115
|
+
efa = listeners[name]!
|
|
116
|
+
} else {
|
|
117
|
+
initialized = false
|
|
118
|
+
efa = listeners[name] = {
|
|
119
|
+
mutCount: 0,
|
|
120
|
+
finalCount: 0,
|
|
121
|
+
funcArr: new FuncArrWithMeta(),
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
efa.funcArr.add(func, mutLevel)
|
|
125
|
+
if (mutLevel === MutLevel.Final) efa.finalCount += 1
|
|
126
|
+
else if (mutLevel === MutLevel.Mut) efa.mutCount += 1
|
|
127
|
+
if (initialized) {
|
|
128
|
+
return mutLevel === MutLevel.Final && efa.finalCount === 1
|
|
129
|
+
? FinalChanged.Added
|
|
130
|
+
: FinalChanged.NotChanged
|
|
131
|
+
}
|
|
132
|
+
return MutLevel.Final ? FinalChanged.Added : FinalChanged.Init
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
removeListener<T extends string>(
|
|
136
|
+
name: T,
|
|
137
|
+
func: EventListener<TEvents[T]>,
|
|
138
|
+
options: EventListenerOptions = {},
|
|
139
|
+
): FinalChanged {
|
|
140
|
+
const listeners = (options.capture || options.useCapture)
|
|
141
|
+
? this.captureListeners
|
|
142
|
+
: this.listeners
|
|
143
|
+
if (!listeners) return FinalChanged.Failed
|
|
144
|
+
const efa = listeners[name]
|
|
145
|
+
if (!efa) return FinalChanged.Failed
|
|
146
|
+
const mutLevel = efa.funcArr.remove(func)
|
|
147
|
+
if (mutLevel === null) return FinalChanged.Failed
|
|
148
|
+
if (mutLevel === MutLevel.Final) efa.finalCount -= 1
|
|
149
|
+
else if (mutLevel === MutLevel.Mut) efa.mutCount -= 1
|
|
150
|
+
return mutLevel === MutLevel.Final && efa.finalCount === 0
|
|
151
|
+
? FinalChanged.Removed
|
|
152
|
+
: FinalChanged.NotChanged
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export type ShadowedEvent<TDetail> = Required<Event<TDetail>> & {
|
|
157
|
+
target: Element
|
|
158
|
+
mark: { [name: string]: unknown } | null
|
|
159
|
+
currentTarget: Element
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export class Event<TDetail> {
|
|
163
|
+
type: string
|
|
164
|
+
timeStamp: number
|
|
165
|
+
detail: TDetail
|
|
166
|
+
bubbles: boolean
|
|
167
|
+
composed: boolean
|
|
168
|
+
/** @internal */
|
|
169
|
+
private _$capturePhase: boolean
|
|
170
|
+
/** @internal */
|
|
171
|
+
private _$originalEvent: unknown
|
|
172
|
+
/** @internal */
|
|
173
|
+
private _$dispatched: boolean
|
|
174
|
+
/** @internal */
|
|
175
|
+
private _$eventBubblingControl: {
|
|
176
|
+
stopped: boolean
|
|
177
|
+
mutated: boolean
|
|
178
|
+
noDefault: boolean
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
constructor(name: string, detail: TDetail, options: EventOptions = {}) {
|
|
182
|
+
const ts = getCurrentTimeStamp()
|
|
183
|
+
this.type = name
|
|
184
|
+
this.timeStamp = ts
|
|
185
|
+
this.detail = detail
|
|
186
|
+
this.bubbles = options.bubbles || false
|
|
187
|
+
this.composed = options.composed || false
|
|
188
|
+
this._$capturePhase = options.capturePhase || false
|
|
189
|
+
this._$eventBubblingControl = {
|
|
190
|
+
stopped: false,
|
|
191
|
+
mutated: false,
|
|
192
|
+
noDefault: false,
|
|
193
|
+
}
|
|
194
|
+
this._$originalEvent = options.originalEvent
|
|
195
|
+
this._$dispatched = false
|
|
196
|
+
if (options.extraFields) {
|
|
197
|
+
Object.assign(this, options.extraFields)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
getEventBubbleStatus(): EventBubbleStatus {
|
|
202
|
+
if (this._$eventBubblingControl.noDefault) return EventBubbleStatus.NoDefault
|
|
203
|
+
return EventBubbleStatus.Normal
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
wrapShadowedEvent(
|
|
207
|
+
targetCaller: Element,
|
|
208
|
+
mark: { [name: string]: unknown } | null,
|
|
209
|
+
currentTargetCaller: Element,
|
|
210
|
+
): ShadowedEvent<TDetail> {
|
|
211
|
+
const ret = Object.create(this) as ShadowedEvent<TDetail>
|
|
212
|
+
ret.target = targetCaller
|
|
213
|
+
ret.mark = mark
|
|
214
|
+
ret.currentTarget = currentTargetCaller
|
|
215
|
+
return ret
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getOriginalEvent(): unknown {
|
|
219
|
+
return this._$originalEvent
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
preventDefault() {
|
|
223
|
+
this._$eventBubblingControl.noDefault = true
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
defaultPrevented() {
|
|
227
|
+
return this._$eventBubblingControl.noDefault
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
stopPropagation() {
|
|
231
|
+
this._$eventBubblingControl.stopped = true
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
propagationStopped() {
|
|
235
|
+
return this._$eventBubblingControl.stopped
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
markMutated() {
|
|
239
|
+
this._$eventBubblingControl.mutated = true
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
mutatedMarked() {
|
|
243
|
+
return this._$eventBubblingControl.mutated
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
dispatch(target: Element, externalTarget?: GeneralBackendElement) {
|
|
247
|
+
if (this._$dispatched) {
|
|
248
|
+
throw new Error('Event cannot be dispatched twice')
|
|
249
|
+
}
|
|
250
|
+
this._$dispatched = true
|
|
251
|
+
const evName = this.type
|
|
252
|
+
const crossShadow = this.composed
|
|
253
|
+
const bubbles = this.bubbles
|
|
254
|
+
const inExternalOnly = externalTarget && !crossShadow
|
|
255
|
+
const eventBubblingControl = this._$eventBubblingControl
|
|
256
|
+
|
|
257
|
+
// calls listeners on a single element
|
|
258
|
+
const callEventFuncArr = (
|
|
259
|
+
targetCaller: Element,
|
|
260
|
+
mark: Record<string, unknown> | null,
|
|
261
|
+
cur: Element,
|
|
262
|
+
isCapture: boolean,
|
|
263
|
+
) => {
|
|
264
|
+
const eventTarget = cur._$eventTarget
|
|
265
|
+
if (!eventTarget) return
|
|
266
|
+
const listeners = isCapture ? eventTarget.captureListeners : eventTarget.listeners
|
|
267
|
+
if (!listeners) return
|
|
268
|
+
const efa = listeners[evName]
|
|
269
|
+
if (!efa) return
|
|
270
|
+
const skipMut = this.mutatedMarked()
|
|
271
|
+
const isComp = cur instanceof Component
|
|
272
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
273
|
+
const curCaller = isComp ? cur.getMethodCaller() : cur
|
|
274
|
+
const ev = this.wrapShadowedEvent(targetCaller, mark, curCaller)
|
|
275
|
+
const ret = efa.funcArr.call(
|
|
276
|
+
curCaller,
|
|
277
|
+
[ev],
|
|
278
|
+
(mulLevel) => !skipMut || mulLevel !== MutLevel.Mut,
|
|
279
|
+
isComp ? cur as GeneralComponent : undefined,
|
|
280
|
+
)
|
|
281
|
+
if (ret === false || efa.finalCount > 0) {
|
|
282
|
+
ev.stopPropagation()
|
|
283
|
+
ev.preventDefault()
|
|
284
|
+
} else if (efa.mutCount > 0) {
|
|
285
|
+
ev.markMutated()
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const forEachBubblePath = (
|
|
290
|
+
target: Element,
|
|
291
|
+
f: (target: Element, targetCaller: Element, mark: Record<string, unknown>) => boolean | void,
|
|
292
|
+
) => {
|
|
293
|
+
const recShadow = (target: Element): Element | null => {
|
|
294
|
+
let cur = target
|
|
295
|
+
const targetCaller = target instanceof Component
|
|
296
|
+
? target.getMethodCaller() as Element
|
|
297
|
+
: target
|
|
298
|
+
const mark = target.collectMarks()
|
|
299
|
+
for (;;) {
|
|
300
|
+
if (f(cur, targetCaller, mark) === false) return null
|
|
301
|
+
let next
|
|
302
|
+
if (crossShadow) {
|
|
303
|
+
if (cur instanceof ShadowRoot) return cur.getHostNode()
|
|
304
|
+
next = cur.parentNode
|
|
305
|
+
while (next?._$inheritSlots) {
|
|
306
|
+
next = next.parentNode
|
|
307
|
+
}
|
|
308
|
+
if (next instanceof Component && !next._$external) {
|
|
309
|
+
const slot = (next.shadowRoot as ShadowRoot).getContainingSlot(cur)
|
|
310
|
+
if (!slot) return null
|
|
311
|
+
next = recShadow(slot)
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
next = cur.parentNode
|
|
315
|
+
}
|
|
316
|
+
if (!next) return null
|
|
317
|
+
cur = next
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
let cur = target
|
|
321
|
+
for (;;) {
|
|
322
|
+
const next = recShadow(cur)
|
|
323
|
+
if (!next) break
|
|
324
|
+
cur = next
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// capture phase
|
|
329
|
+
if (this._$capturePhase && !eventBubblingControl.stopped && !inExternalOnly) {
|
|
330
|
+
const bubblingPath: [Element, Element, Record<string, unknown>][] = []
|
|
331
|
+
forEachBubblePath(target, (target, targetCaller, mark) => {
|
|
332
|
+
bubblingPath.push([target, targetCaller, mark])
|
|
333
|
+
})
|
|
334
|
+
for (let i = bubblingPath.length - 1; i >= 0; i -= 1) {
|
|
335
|
+
const [cur, targetCaller, mark] = bubblingPath[i]!
|
|
336
|
+
if (cur._$eventTarget) {
|
|
337
|
+
callEventFuncArr(targetCaller, mark, cur, true)
|
|
338
|
+
if (eventBubblingControl.stopped) break
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// bubble phase in external component
|
|
344
|
+
if (!eventBubblingControl.stopped && externalTarget) {
|
|
345
|
+
if (target instanceof Component && target._$external) {
|
|
346
|
+
(target.shadowRoot as ExternalShadowRoot).handleEvent(externalTarget, this)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// bubble phase
|
|
351
|
+
if (!eventBubblingControl.stopped && !inExternalOnly) {
|
|
352
|
+
let atTarget = true
|
|
353
|
+
forEachBubblePath(target, (target, targetCaller, mark) => {
|
|
354
|
+
if (!atTarget && target instanceof Component && target._$external) {
|
|
355
|
+
const sr = target.shadowRoot as ExternalShadowRoot
|
|
356
|
+
sr.handleEvent(sr.slot, this)
|
|
357
|
+
}
|
|
358
|
+
atTarget = false
|
|
359
|
+
if (target._$eventTarget) {
|
|
360
|
+
callEventFuncArr(targetCaller, mark, target, false)
|
|
361
|
+
}
|
|
362
|
+
return bubbles && !eventBubblingControl.stopped
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
static dispatchEvent<TDetail>(target: Element, event: Event<TDetail>) {
|
|
368
|
+
return event.dispatch(target)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
static dispatchExternalEvent<TDetail>(
|
|
372
|
+
component: GeneralComponent,
|
|
373
|
+
target: GeneralBackendElement,
|
|
374
|
+
event: Event<TDetail>,
|
|
375
|
+
) {
|
|
376
|
+
return event.dispatch(component, target)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
static triggerEvent<TDetail>(
|
|
380
|
+
this: void,
|
|
381
|
+
target: Element,
|
|
382
|
+
name: string,
|
|
383
|
+
detail: TDetail,
|
|
384
|
+
options?: EventOptions,
|
|
385
|
+
) {
|
|
386
|
+
const ev = new Event(name, detail, options)
|
|
387
|
+
Event.dispatchEvent(target, ev)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
static triggerExternalEvent<TDetail>(
|
|
391
|
+
this: void,
|
|
392
|
+
component: GeneralComponent,
|
|
393
|
+
target: GeneralBackendElement,
|
|
394
|
+
name: string,
|
|
395
|
+
detail: TDetail,
|
|
396
|
+
options?: EventOptions,
|
|
397
|
+
) {
|
|
398
|
+
const ev = new Event(name, detail, options)
|
|
399
|
+
Event.dispatchExternalEvent(component, target, ev)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Event,
|
|
3
|
+
ShadowedEvent,
|
|
4
|
+
} from './event'
|
|
5
|
+
import {
|
|
6
|
+
GeneralBackendElement,
|
|
7
|
+
} from './node'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* An external shadow root
|
|
11
|
+
*
|
|
12
|
+
* It can be used to build an external component.
|
|
13
|
+
* External component is a customizable subtree that can be composed with normal components.
|
|
14
|
+
* It allows third-party frameworks to render a subtree and then compose it together.
|
|
15
|
+
* However, the subtree must be created in the same backend context.
|
|
16
|
+
*/
|
|
17
|
+
export interface ExternalShadowRoot {
|
|
18
|
+
root: GeneralBackendElement
|
|
19
|
+
slot: GeneralBackendElement
|
|
20
|
+
getIdMap(): { [id: string]: GeneralBackendElement }
|
|
21
|
+
handleEvent<T>(target: GeneralBackendElement, event: Event<T>): void
|
|
22
|
+
setListener<T>(
|
|
23
|
+
elem: GeneralBackendElement,
|
|
24
|
+
ev: string,
|
|
25
|
+
listener: (event: ShadowedEvent<T>) => unknown,
|
|
26
|
+
): void
|
|
27
|
+
}
|