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.
Files changed (237) hide show
  1. package/README.md +40 -0
  2. package/dist/glass_easel.all.d.ts +1 -0
  3. package/dist/glass_easel.all.js +2 -0
  4. package/dist/glass_easel.all.js.map +1 -0
  5. package/dist/glass_easel.domlike.global.d.ts +1 -0
  6. package/dist/glass_easel.domlike.global.js +2 -0
  7. package/dist/glass_easel.domlike.global.js.map +1 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +1 -0
  10. package/dist/types/src/backend/backend_protocol.d.ts +119 -0
  11. package/dist/types/src/backend/backend_protocol.d.ts.map +1 -0
  12. package/dist/types/src/backend/composed_backend_protocol.d.ts +90 -0
  13. package/dist/types/src/backend/composed_backend_protocol.d.ts.map +1 -0
  14. package/dist/types/src/backend/domlike_backend_protocol.d.ts +76 -0
  15. package/dist/types/src/backend/domlike_backend_protocol.d.ts.map +1 -0
  16. package/dist/types/src/backend/mode.d.ts +46 -0
  17. package/dist/types/src/backend/mode.d.ts.map +1 -0
  18. package/dist/types/src/backend/suggested_backend_protocol.d.ts +30 -0
  19. package/dist/types/src/backend/suggested_backend_protocol.d.ts.map +1 -0
  20. package/dist/types/src/behavior.d.ts +428 -0
  21. package/dist/types/src/behavior.d.ts.map +1 -0
  22. package/dist/types/src/class_list.d.ts +79 -0
  23. package/dist/types/src/class_list.d.ts.map +1 -0
  24. package/dist/types/src/component.d.ts +291 -0
  25. package/dist/types/src/component.d.ts.map +1 -0
  26. package/dist/types/src/component_params.d.ts +239 -0
  27. package/dist/types/src/component_params.d.ts.map +1 -0
  28. package/dist/types/src/component_space.d.ts +164 -0
  29. package/dist/types/src/component_space.d.ts.map +1 -0
  30. package/dist/types/src/data_path.d.ts +5 -0
  31. package/dist/types/src/data_path.d.ts.map +1 -0
  32. package/dist/types/src/data_proxy.d.ts +107 -0
  33. package/dist/types/src/data_proxy.d.ts.map +1 -0
  34. package/dist/types/src/data_utils.d.ts +3 -0
  35. package/dist/types/src/data_utils.d.ts.map +1 -0
  36. package/dist/types/src/element.d.ts +275 -0
  37. package/dist/types/src/element.d.ts.map +1 -0
  38. package/dist/types/src/element_iterator.d.ts +43 -0
  39. package/dist/types/src/element_iterator.d.ts.map +1 -0
  40. package/dist/types/src/event.d.ts +104 -0
  41. package/dist/types/src/event.d.ts.map +1 -0
  42. package/dist/types/src/external_shadow_tree.d.ts +20 -0
  43. package/dist/types/src/external_shadow_tree.d.ts.map +1 -0
  44. package/dist/types/src/func_arr.d.ts +39 -0
  45. package/dist/types/src/func_arr.d.ts.map +1 -0
  46. package/dist/types/src/global_options.d.ts +111 -0
  47. package/dist/types/src/global_options.d.ts.map +1 -0
  48. package/dist/types/src/index.d.ts +43 -0
  49. package/dist/types/src/index.d.ts.map +1 -0
  50. package/dist/types/src/mutation_observer.d.ts +79 -0
  51. package/dist/types/src/mutation_observer.d.ts.map +1 -0
  52. package/dist/types/src/native_node.d.ts +8 -0
  53. package/dist/types/src/native_node.d.ts.map +1 -0
  54. package/dist/types/src/node.d.ts +49 -0
  55. package/dist/types/src/node.d.ts.map +1 -0
  56. package/dist/types/src/relation.d.ts +47 -0
  57. package/dist/types/src/relation.d.ts.map +1 -0
  58. package/dist/types/src/render.d.ts +3 -0
  59. package/dist/types/src/render.d.ts.map +1 -0
  60. package/dist/types/src/selector.d.ts +32 -0
  61. package/dist/types/src/selector.d.ts.map +1 -0
  62. package/dist/types/src/shadow_root.d.ts +136 -0
  63. package/dist/types/src/shadow_root.d.ts.map +1 -0
  64. package/dist/types/src/template_engine.d.ts +18 -0
  65. package/dist/types/src/template_engine.d.ts.map +1 -0
  66. package/dist/types/src/text_node.d.ts +32 -0
  67. package/dist/types/src/text_node.d.ts.map +1 -0
  68. package/dist/types/src/tmpl/index.d.ts +18 -0
  69. package/dist/types/src/tmpl/index.d.ts.map +1 -0
  70. package/dist/types/src/tmpl/native_rendering.d.ts +45 -0
  71. package/dist/types/src/tmpl/native_rendering.d.ts.map +1 -0
  72. package/dist/types/src/tmpl/proc_gen_wrapper.d.ts +80 -0
  73. package/dist/types/src/tmpl/proc_gen_wrapper.d.ts.map +1 -0
  74. package/dist/types/src/tmpl/proc_gen_wrapper_dom.d.ts +50 -0
  75. package/dist/types/src/tmpl/proc_gen_wrapper_dom.d.ts.map +1 -0
  76. package/dist/types/src/tmpl/range_list_diff.d.ts +19 -0
  77. package/dist/types/src/tmpl/range_list_diff.d.ts.map +1 -0
  78. package/dist/types/src/trait_behaviors.d.ts +38 -0
  79. package/dist/types/src/trait_behaviors.d.ts.map +1 -0
  80. package/dist/types/src/virtual_node.d.ts +10 -0
  81. package/dist/types/src/virtual_node.d.ts.map +1 -0
  82. package/dist/types/tests/backend/domlike.test.d.ts +2 -0
  83. package/dist/types/tests/backend/domlike.test.d.ts.map +1 -0
  84. package/dist/types/tests/base/env.d.ts +29 -0
  85. package/dist/types/tests/base/env.d.ts.map +1 -0
  86. package/dist/types/tests/base/match.d.ts +9 -0
  87. package/dist/types/tests/base/match.d.ts.map +1 -0
  88. package/dist/types/tests/core/backend.test.d.ts +2 -0
  89. package/dist/types/tests/core/backend.test.d.ts.map +1 -0
  90. package/dist/types/tests/core/behavior.test.d.ts +2 -0
  91. package/dist/types/tests/core/behavior.test.d.ts.map +1 -0
  92. package/dist/types/tests/core/component_space.test.d.ts +2 -0
  93. package/dist/types/tests/core/component_space.test.d.ts.map +1 -0
  94. package/dist/types/tests/core/data_update.test.d.ts +2 -0
  95. package/dist/types/tests/core/data_update.test.d.ts.map +1 -0
  96. package/dist/types/tests/core/misc.test.d.ts +2 -0
  97. package/dist/types/tests/core/misc.test.d.ts.map +1 -0
  98. package/dist/types/tests/core/placeholder.test.d.ts +2 -0
  99. package/dist/types/tests/core/placeholder.test.d.ts.map +1 -0
  100. package/dist/types/tests/core/slot.test.d.ts +2 -0
  101. package/dist/types/tests/core/slot.test.d.ts.map +1 -0
  102. package/dist/types/tests/core/trait_behaviors.test.d.ts +2 -0
  103. package/dist/types/tests/core/trait_behaviors.test.d.ts.map +1 -0
  104. package/dist/types/tests/tmpl/binding_map.test.d.ts +2 -0
  105. package/dist/types/tests/tmpl/binding_map.test.d.ts.map +1 -0
  106. package/dist/types/tests/tmpl/event.test.d.ts +2 -0
  107. package/dist/types/tests/tmpl/event.test.d.ts.map +1 -0
  108. package/dist/types/tests/tmpl/expression.test.d.ts +2 -0
  109. package/dist/types/tests/tmpl/expression.test.d.ts.map +1 -0
  110. package/dist/types/tests/tmpl/lvalue.test.d.ts +2 -0
  111. package/dist/types/tests/tmpl/lvalue.test.d.ts.map +1 -0
  112. package/dist/types/tests/tmpl/native_rendering.test.d.ts +2 -0
  113. package/dist/types/tests/tmpl/native_rendering.test.d.ts.map +1 -0
  114. package/dist/types/tests/tmpl/structure.test.d.ts +2 -0
  115. package/dist/types/tests/tmpl/structure.test.d.ts.map +1 -0
  116. package/dist/types/tests/types/chaining.test.d.ts +2 -0
  117. package/dist/types/tests/types/chaining.test.d.ts.map +1 -0
  118. package/dist/types/tests/types/createElement.test.d.ts +2 -0
  119. package/dist/types/tests/types/createElement.test.d.ts.map +1 -0
  120. package/dist/types/tests/types/definition.test.d.ts +2 -0
  121. package/dist/types/tests/types/definition.test.d.ts.map +1 -0
  122. package/guide/zh_CN/advanced/binding_map_update.md +32 -0
  123. package/guide/zh_CN/advanced/build_args.md +28 -0
  124. package/guide/zh_CN/advanced/component_filter.md +70 -0
  125. package/guide/zh_CN/advanced/component_space.md +124 -0
  126. package/guide/zh_CN/advanced/custom_backend.md +53 -0
  127. package/guide/zh_CN/advanced/error_listener.md +32 -0
  128. package/guide/zh_CN/advanced/external_component.md +73 -0
  129. package/guide/zh_CN/advanced/template_engine.md +61 -0
  130. package/guide/zh_CN/appendix/backend_protocol.md +501 -0
  131. package/guide/zh_CN/appendix/list_diff_algorithm.md +406 -0
  132. package/guide/zh_CN/basic/beginning.md +94 -0
  133. package/guide/zh_CN/basic/component.md +156 -0
  134. package/guide/zh_CN/basic/event.md +169 -0
  135. package/guide/zh_CN/basic/lifetime.md +66 -0
  136. package/guide/zh_CN/basic/method.md +62 -0
  137. package/guide/zh_CN/basic/template.md +135 -0
  138. package/guide/zh_CN/data_management/advanced_update.md +170 -0
  139. package/guide/zh_CN/data_management/data_deep_copy.md +157 -0
  140. package/guide/zh_CN/data_management/data_observer.md +154 -0
  141. package/guide/zh_CN/data_management/property_early_init.md +31 -0
  142. package/guide/zh_CN/data_management/pure_data_pattern.md +21 -0
  143. package/guide/zh_CN/index.md +93 -0
  144. package/guide/zh_CN/interaction/behavior.md +52 -0
  145. package/guide/zh_CN/interaction/component_path.md +37 -0
  146. package/guide/zh_CN/interaction/generic.md +73 -0
  147. package/guide/zh_CN/interaction/placeholder.md +40 -0
  148. package/guide/zh_CN/interaction/relation.md +151 -0
  149. package/guide/zh_CN/interaction/slot.md +137 -0
  150. package/guide/zh_CN/interaction/template_import.md +94 -0
  151. package/guide/zh_CN/interaction/trait_behavior.md +117 -0
  152. package/guide/zh_CN/styling/external_class.md +46 -0
  153. package/guide/zh_CN/styling/style_isolation.md +54 -0
  154. package/guide/zh_CN/styling/virtual_host.md +52 -0
  155. package/guide/zh_CN/tree/element_iterator.md +54 -0
  156. package/guide/zh_CN/tree/mutation_observer.md +52 -0
  157. package/guide/zh_CN/tree/node_tree.md +142 -0
  158. package/guide/zh_CN/tree/node_tree_modification.md +78 -0
  159. package/guide/zh_CN/tree/selector.md +66 -0
  160. package/jest.config.js +6 -0
  161. package/jest.dts.config.js +9 -0
  162. package/jest.unit.config.js +14 -0
  163. package/package.json +28 -0
  164. package/src/backend/backend_protocol.ts +313 -0
  165. package/src/backend/composed_backend_protocol.ts +252 -0
  166. package/src/backend/domlike_backend_protocol.ts +370 -0
  167. package/src/backend/mode.ts +51 -0
  168. package/src/backend/suggested_backend_protocol.ts +83 -0
  169. package/src/behavior.ts +1655 -0
  170. package/src/bootstrap_dom_dev.js +22 -0
  171. package/src/class_list.ts +376 -0
  172. package/src/component.ts +1309 -0
  173. package/src/component_params.ts +461 -0
  174. package/src/component_space.ts +547 -0
  175. package/src/data_path.ts +225 -0
  176. package/src/data_proxy.ts +670 -0
  177. package/src/data_utils.ts +50 -0
  178. package/src/element.ts +1966 -0
  179. package/src/element_iterator.ts +158 -0
  180. package/src/event.ts +401 -0
  181. package/src/external_shadow_tree.ts +27 -0
  182. package/src/func_arr.ts +198 -0
  183. package/src/global_options.ts +242 -0
  184. package/src/index.ts +187 -0
  185. package/src/mutation_observer.ts +252 -0
  186. package/src/native_node.ts +74 -0
  187. package/src/node.ts +174 -0
  188. package/src/relation.ts +380 -0
  189. package/src/render.ts +25 -0
  190. package/src/selector.ts +218 -0
  191. package/src/shadow_root.ts +766 -0
  192. package/src/template_engine.ts +45 -0
  193. package/src/text_node.ts +149 -0
  194. package/src/tmpl/index.ts +199 -0
  195. package/src/tmpl/native_rendering.ts +175 -0
  196. package/src/tmpl/proc_gen_wrapper.ts +954 -0
  197. package/src/tmpl/proc_gen_wrapper_dom.ts +230 -0
  198. package/src/tmpl/range_list_diff.ts +443 -0
  199. package/src/trait_behaviors.ts +51 -0
  200. package/src/virtual_node.ts +51 -0
  201. package/tests/backend/domlike.test.ts +254 -0
  202. package/tests/base/env.ts +78 -0
  203. package/tests/base/match.ts +185 -0
  204. package/tests/core/backend.test.ts +144 -0
  205. package/tests/core/behavior.test.ts +546 -0
  206. package/tests/core/component_space.test.ts +212 -0
  207. package/tests/core/data_update.test.ts +461 -0
  208. package/tests/core/misc.test.ts +339 -0
  209. package/tests/core/placeholder.test.ts +180 -0
  210. package/tests/core/slot.test.ts +1495 -0
  211. package/tests/core/trait_behaviors.test.ts +153 -0
  212. package/tests/legacy/README.md +3 -0
  213. package/tests/legacy/behavior.test.js +293 -0
  214. package/tests/legacy/component.test.js +1247 -0
  215. package/tests/legacy/data_path.test.js +149 -0
  216. package/tests/legacy/data_proxy.test.js +759 -0
  217. package/tests/legacy/element_iterator.test.js +148 -0
  218. package/tests/legacy/event.test.js +849 -0
  219. package/tests/legacy/external.test.js +510 -0
  220. package/tests/legacy/extra_info.test.js +109 -0
  221. package/tests/legacy/generics.test.js +176 -0
  222. package/tests/legacy/mutation_observer.test.js +210 -0
  223. package/tests/legacy/relation.test.js +517 -0
  224. package/tests/legacy/selector.test.js +263 -0
  225. package/tests/legacy/slot.test.js +915 -0
  226. package/tests/legacy/virtual.test.js +394 -0
  227. package/tests/tmpl/binding_map.test.ts +208 -0
  228. package/tests/tmpl/event.test.ts +206 -0
  229. package/tests/tmpl/expression.test.ts +429 -0
  230. package/tests/tmpl/lvalue.test.ts +160 -0
  231. package/tests/tmpl/native_rendering.test.ts +155 -0
  232. package/tests/tmpl/structure.test.ts +998 -0
  233. package/tests/types/chaining.test.ts +614 -0
  234. package/tests/types/createElement.test.ts +82 -0
  235. package/tests/types/definition.test.ts +442 -0
  236. package/tsconfig.json +11 -0
  237. package/webpack.config.js +270 -0
@@ -0,0 +1,670 @@
1
+ import {
2
+ safeCallback,
3
+ triggerWarning,
4
+ } from './func_arr'
5
+ import {
6
+ convertValueToType,
7
+ PropertyDefinition,
8
+ } from './behavior'
9
+ import {
10
+ deepCopy,
11
+ simpleDeepCopy,
12
+ } from './data_utils'
13
+ import {
14
+ DataPath,
15
+ MultiPaths,
16
+ } from './data_path'
17
+ import {
18
+ DeepCopyKind,
19
+ } from './global_options'
20
+ import {
21
+ MutationObserverTarget,
22
+ } from './mutation_observer'
23
+ import {
24
+ GeneralComponentInstance,
25
+ DataList,
26
+ PropertyList,
27
+ MethodList,
28
+ ComponentInstance,
29
+ DataWithPropertyValues,
30
+ } from './component_params'
31
+
32
+ // eslint-disable-next-line @typescript-eslint/unbound-method
33
+ const hasOwnProperty = Object.prototype.hasOwnProperty
34
+
35
+ export const enum DeepCopyStrategy {
36
+ None,
37
+ Simple,
38
+ SimpleWithRecursion,
39
+ }
40
+
41
+ export type DataValue = unknown
42
+
43
+ export type DataObserver = (...values: unknown[]) => void
44
+
45
+ export type DataChange = DataReplace | DataSplice
46
+ // for replace
47
+ export type DataReplace = [DataPath, DataValue, undefined, undefined]
48
+ // for splice, numbers are index, removal count
49
+ export type DataSplice = [DataPath, DataValue[], number, number]
50
+
51
+ export type PropertyChange = {
52
+ propName: string,
53
+ prop: PropertyDefinition,
54
+ oldValue: unknown,
55
+ newValue: unknown,
56
+ skipModelListener: boolean,
57
+ }
58
+
59
+ export type DataUpdateCallback = (
60
+ data: { [name: string]: DataValue },
61
+ combinedChanges: DataChange[],
62
+ ) => void
63
+
64
+ export type ModelBindingListener = (value: DataValue) => void
65
+
66
+ type ObserverNode = {
67
+ listener?: number[],
68
+ wildcard?: number[],
69
+ sub: { [name: string]: ObserverNode },
70
+ }
71
+
72
+ export class DataGroupObserverTree {
73
+ propFields: { [name: string]: PropertyDefinition }
74
+ observerTree: ObserverNode = { sub: {} }
75
+ observers: DataObserverWithPath[] = []
76
+
77
+ constructor(propFields: { [name: string]: PropertyDefinition }) {
78
+ this.propFields = propFields
79
+ }
80
+
81
+ cloneSub(): DataGroupObserverTree {
82
+ const ret = new DataGroupObserverTree(this.propFields)
83
+ if (this.observers.length > 0) {
84
+ ret.observerTree = simpleDeepCopy(this.observerTree)
85
+ ret.observers = this.observers.slice()
86
+ }
87
+ return ret
88
+ }
89
+
90
+ addObserver(func: DataObserver, dataPath: MultiPaths) {
91
+ const id = this.observers.length
92
+ this.observers.push({
93
+ path: dataPath,
94
+ f: func,
95
+ })
96
+ for (let i = 0; i < dataPath.length; i += 1) {
97
+ const singlePath = dataPath[i]!
98
+ let cur = this.observerTree
99
+ let wildcard = false
100
+ for (let j = 0; j < singlePath.length; j += 1) {
101
+ const pathSlice = singlePath[j]!
102
+ if (pathSlice === '**') {
103
+ wildcard = true
104
+ break
105
+ }
106
+ if (!cur.sub[pathSlice]) cur.sub[pathSlice] = { sub: {} }
107
+ cur = cur.sub[pathSlice]!
108
+ }
109
+ if (wildcard) {
110
+ if (!cur.wildcard) cur.wildcard = [id]
111
+ else cur.wildcard.push(id)
112
+ } else {
113
+ if (!cur.listener) cur.listener = [id]
114
+ else cur.listener.push(id)
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ const callObserver = <
121
+ TData extends DataList,
122
+ TProperty extends PropertyList,
123
+ TMethod extends MethodList,
124
+ >(
125
+ comp: ComponentInstance<TData, TProperty, TMethod>,
126
+ data: { [key: string]: DataValue },
127
+ path: MultiPaths,
128
+ f: DataObserver,
129
+ ) => {
130
+ const args: unknown[] = new Array(path.length)
131
+ for (let i = 0; i < path.length; i += 1) {
132
+ const singlePath = path[i]!
133
+ let cur: DataValue = data
134
+ for (let j = 0; j < singlePath.length; j += 1) {
135
+ const slice = singlePath[j]!
136
+ if (slice === '**') break
137
+ if (typeof cur === 'object' && cur !== null) {
138
+ cur = (cur as { [name: string]: DataValue })[slice]
139
+ } else {
140
+ cur = undefined
141
+ break
142
+ }
143
+ }
144
+ args[i] = cur
145
+ }
146
+ safeCallback(
147
+ 'Data Observer',
148
+ f,
149
+ comp.getMethodCaller() as any,
150
+ args,
151
+ comp as unknown as GeneralComponentInstance || undefined,
152
+ )
153
+ }
154
+
155
+ const dfsMarkTriggerBitOnPath = (
156
+ node: ObserverNode,
157
+ observerStatus: boolean[],
158
+ ) => {
159
+ if (node.listener) {
160
+ for (let i = 0; i < node.listener.length; i += 1) {
161
+ const observerId = node.listener[i]!
162
+ observerStatus[observerId] = true
163
+ }
164
+ }
165
+ if (node.wildcard) {
166
+ for (let i = 0; i < node.wildcard.length; i += 1) {
167
+ const observerId = node.wildcard[i]!
168
+ observerStatus[observerId] = true
169
+ }
170
+ }
171
+ const keys = Object.keys(node.sub)
172
+ for (let i = 0; i < keys.length; i += 1) {
173
+ const k = keys[i]!
174
+ dfsMarkTriggerBitOnPath(node.sub[k]!, observerStatus)
175
+ }
176
+ }
177
+
178
+ const markTriggerBitOnPath = (
179
+ root: ObserverNode,
180
+ observerStatus: boolean[],
181
+ singlePath: DataPath,
182
+ ) => {
183
+ let cur: ObserverNode = root
184
+ let found = true
185
+ for (let i = 0; i < singlePath.length; i += 1) {
186
+ const slice = singlePath[i]!
187
+ if (cur.wildcard) {
188
+ const arr = cur.wildcard
189
+ for (let i = 0; i < arr.length; i += 1) {
190
+ const observerId = arr[i]!
191
+ observerStatus[observerId] = true
192
+ }
193
+ }
194
+ if (!hasOwnProperty.call(cur.sub, slice)) {
195
+ found = false
196
+ break
197
+ }
198
+ cur = cur.sub[slice]!
199
+ }
200
+ if (found) dfsMarkTriggerBitOnPath(cur, observerStatus)
201
+ }
202
+
203
+ type DataObserverWithPath = { path: MultiPaths, f: DataObserver }
204
+
205
+ const triggerAndCleanTriggerBit = <
206
+ TData extends DataList,
207
+ TProperty extends PropertyList,
208
+ TMethod extends MethodList,
209
+ >(
210
+ observers: DataObserverWithPath[],
211
+ observerStatus: boolean[],
212
+ comp: ComponentInstance<TData, TProperty, TMethod> | null,
213
+ data: { [key: string]: DataValue },
214
+ ) => {
215
+ for (let i = 0; i < observers.length; i += 1) {
216
+ const { path, f } = observers[i]!
217
+ const status = observerStatus[i]
218
+ if (status) {
219
+ observerStatus[i] = false
220
+ if (comp) callObserver(comp, data, path, f)
221
+ }
222
+ }
223
+ }
224
+
225
+ export const getDeepCopyStrategy = (level: DeepCopyKind) => {
226
+ if (level === DeepCopyKind.Simple) return DeepCopyStrategy.Simple
227
+ if (level === DeepCopyKind.SimpleWithRecursion) return DeepCopyStrategy.SimpleWithRecursion
228
+ return DeepCopyStrategy.None
229
+ }
230
+
231
+ /** A data wrapper for data operations such as `setData` */
232
+ export class DataGroup<
233
+ TData extends DataList,
234
+ TProperty extends PropertyList,
235
+ TMethod extends MethodList,
236
+ > {
237
+ data: DataWithPropertyValues<TData, TProperty>
238
+ innerData: { [key: string]: DataValue } | null
239
+ private _$comp: ComponentInstance<TData, TProperty, TMethod> | null
240
+ private _$pureDataPattern: RegExp | null
241
+ private _$dataDeepCopy: DeepCopyStrategy
242
+ private _$propertyPassingDeepCopy: DeepCopyStrategy
243
+ private _$reflectToAttributes: boolean
244
+ private _$propFields: { [name: string]: PropertyDefinition }
245
+ private _$observerTree: ObserverNode
246
+ private _$observers: DataObserverWithPath[]
247
+ private _$observerStatus: boolean[]
248
+ private _$modelBindingListener: { [name: string]: ModelBindingListener } | null = null
249
+ private _$updateListener?: DataUpdateCallback
250
+ private _$pendingChanges: DataChange[] = []
251
+ private _$doingUpdates: {
252
+ prop: PropertyChange[],
253
+ combined: DataChange[],
254
+ count: number,
255
+ } | null = null
256
+
257
+ private _$generateInnerData(data: { [key: string]: DataValue }) {
258
+ const pureDataPattern = this._$pureDataPattern
259
+ const dataDeepCopy = this._$dataDeepCopy
260
+ if (pureDataPattern || dataDeepCopy !== DeepCopyStrategy.None) {
261
+ const innerData = {} as { [key: string]: DataValue }
262
+ const keys = Object.keys(data)
263
+ for (let i = 0; i < keys.length; i += 1) {
264
+ const k = keys[i]!
265
+ const v = data[k]!
266
+ if (pureDataPattern && pureDataPattern.test(k)) continue
267
+ if (dataDeepCopy === DeepCopyStrategy.None) {
268
+ innerData[k] = v
269
+ } else if (dataDeepCopy === DeepCopyStrategy.SimpleWithRecursion) {
270
+ innerData[k] = deepCopy(v, true)
271
+ } else {
272
+ innerData[k] = simpleDeepCopy(v)
273
+ }
274
+ }
275
+ return innerData
276
+ }
277
+ return null
278
+ }
279
+
280
+ constructor(
281
+ associatedComponent: ComponentInstance<TData, TProperty, TMethod>
282
+ | null,
283
+ data: DataWithPropertyValues<TData, TProperty>,
284
+ pureDataPattern: RegExp | null,
285
+ dataDeepCopy: DeepCopyStrategy,
286
+ propertyPassingDeepCopy: DeepCopyStrategy,
287
+ reflectToAttributes: boolean,
288
+ observerTree: DataGroupObserverTree,
289
+ ) {
290
+ this._$comp = associatedComponent
291
+ this.data = data
292
+ this._$pureDataPattern = pureDataPattern
293
+ this._$dataDeepCopy = dataDeepCopy
294
+ this._$propertyPassingDeepCopy = propertyPassingDeepCopy
295
+ this._$reflectToAttributes = reflectToAttributes && !!associatedComponent
296
+ this._$propFields = observerTree.propFields
297
+ this._$observerTree = observerTree.observerTree
298
+ this._$observers = observerTree.observers
299
+ this._$observerStatus = new Array(observerTree.observers.length) as boolean[]
300
+ this.innerData = this._$generateInnerData(data)
301
+ }
302
+
303
+ /** Create a simple data group */
304
+ static create(data: { [key: string]: DataValue }) {
305
+ return new DataGroup(
306
+ null,
307
+ data as any,
308
+ null,
309
+ DeepCopyStrategy.None,
310
+ DeepCopyStrategy.None,
311
+ false,
312
+ new DataGroupObserverTree({}),
313
+ )
314
+ }
315
+
316
+ /** Set a callback for every grouped update */
317
+ setUpdateListener(updateListener: DataUpdateCallback) {
318
+ this._$updateListener = updateListener
319
+ }
320
+
321
+ /** Replace the underlying data */
322
+ replaceWholeData(data: DataWithPropertyValues<TData, TProperty>) {
323
+ this.data = data
324
+ this.innerData = this._$generateInnerData(data)
325
+ }
326
+
327
+ /** Add a new common data change to queue */
328
+ replaceDataOnPath(path: DataPath, newData: DataValue) {
329
+ this._$pendingChanges.push([path, newData, undefined, undefined])
330
+ }
331
+
332
+ /** Add a new array splice operation to queue */
333
+ spliceArrayDataOnPath(
334
+ path: DataPath,
335
+ index: number | undefined,
336
+ del: number | undefined,
337
+ inserts: DataValue[],
338
+ ) {
339
+ if (!Array.isArray(inserts)) {
340
+ triggerWarning(`The splice insertion must be a string (on path "${path.join('.')}"). The change is ignored.`)
341
+ return
342
+ }
343
+ this._$pendingChanges.push([path, inserts, index ?? -1, del || 0])
344
+ }
345
+
346
+ /**
347
+ * Add a new property change to queue
348
+ *
349
+ * (Generally designed for template engines.)
350
+ * If the `propName` is a property,
351
+ * the `newData` will be deep-copied according to the `propertyPassingDeepCopy` configuration.
352
+ * Otherwise, it returns false.
353
+ */
354
+ replaceProperty(propName: string, newData: DataValue): boolean {
355
+ let data = newData
356
+ if (!this._$propFields[propName]) return false
357
+ if (this._$propertyPassingDeepCopy !== DeepCopyStrategy.None) {
358
+ if (this._$propertyPassingDeepCopy === DeepCopyStrategy.SimpleWithRecursion) {
359
+ data = deepCopy(newData, true)
360
+ } else {
361
+ data = simpleDeepCopy(newData)
362
+ }
363
+ }
364
+ this._$pendingChanges.push([[propName], data, undefined, undefined])
365
+ return true
366
+ }
367
+
368
+ /** Discard changes in queue and generate a new queue with specified changes */
369
+ setChanges(changes: DataChange[]) {
370
+ this._$pendingChanges = changes
371
+ }
372
+
373
+ /** Get the data change queue */
374
+ getChanges(): DataChange[] {
375
+ return this._$pendingChanges
376
+ }
377
+
378
+ /**
379
+ * Set a callback when a specified property changes
380
+ *
381
+ * (Generally designed for template engines.)
382
+ */
383
+ setModelBindingListener(propName: string, listener: ModelBindingListener) {
384
+ if (this._$modelBindingListener) {
385
+ this._$modelBindingListener[propName] = listener
386
+ } else {
387
+ const map = this._$modelBindingListener = Object.create(null) as
388
+ { [name: string]: ModelBindingListener }
389
+ map[propName] = listener
390
+ }
391
+ }
392
+
393
+ /** Apply all changes in queue */
394
+ applyDataUpdates(skipModelListener = false) {
395
+ const propFields = this._$propFields
396
+ const comp = this._$comp
397
+ const pureDataPattern = this._$pureDataPattern
398
+ const dataDeepCopy = this._$dataDeepCopy
399
+
400
+ // handling chained updates if observers are used
401
+ const isChainedUpdates = !!this._$doingUpdates
402
+ let combinedChanges: DataChange[]
403
+ let propChanges: PropertyChange[]
404
+ if (this._$observers.length > 0) {
405
+ if (this._$doingUpdates) {
406
+ combinedChanges = this._$doingUpdates.combined
407
+ propChanges = this._$doingUpdates.prop
408
+ } else {
409
+ this._$doingUpdates = {
410
+ prop: [] as PropertyChange[],
411
+ combined: [] as DataChange[],
412
+ count: 0,
413
+ }
414
+ combinedChanges = this._$doingUpdates.combined
415
+ propChanges = this._$doingUpdates.prop
416
+ }
417
+ } else {
418
+ combinedChanges = []
419
+ propChanges = []
420
+ }
421
+
422
+ // find all changes
423
+ const changes = this._$pendingChanges
424
+ this._$pendingChanges = []
425
+
426
+ // apply changes and collect changing information
427
+ for (let i = 0; i < changes.length; i += 1) {
428
+ const change = changes[i]!
429
+ const [path, newData, spliceIndex, spliceDel] = change
430
+ const isSplice = spliceDel !== undefined
431
+ const propName = String(path[0])
432
+ const excluded = pureDataPattern ? pureDataPattern.test(propName) : false
433
+ const prop: PropertyDefinition | undefined = propFields[propName]
434
+ if (prop && path.length === 1) {
435
+ // do update for 1-level fields
436
+ const oldData: unknown = this.data[propName]
437
+ let normalizedSpliceIndex: number | undefined
438
+ let filteredData: DataValue
439
+ if (isSplice) {
440
+ if (Array.isArray(oldData)) {
441
+ const c = change as DataSplice
442
+ normalizedSpliceIndex = spliceIndex! >= 0 && spliceIndex! < oldData.length
443
+ ? spliceIndex!
444
+ : oldData.length
445
+ c[2] = normalizedSpliceIndex
446
+ oldData.splice(normalizedSpliceIndex, spliceDel, ...(newData as typeof oldData))
447
+ } else {
448
+ triggerWarning(`An array splice change cannot be applied to a non-array value (on path "${path.join('.')}"). The change is ignored.`)
449
+ }
450
+ filteredData = oldData
451
+ } else {
452
+ filteredData = convertValueToType(newData, propName, prop)
453
+ }
454
+ if (!excluded) {
455
+ if (this.innerData) {
456
+ let innerNewData: unknown
457
+ if (dataDeepCopy === DeepCopyStrategy.None) {
458
+ change[1] = innerNewData = filteredData
459
+ } else if (normalizedSpliceIndex !== undefined) {
460
+ innerNewData = this.innerData[propName] as DataValue[]
461
+ let inserts: DataValue[]
462
+ if (dataDeepCopy === DeepCopyStrategy.SimpleWithRecursion) {
463
+ change[1] = inserts = deepCopy(newData as unknown[], true)
464
+ } else {
465
+ change[1] = inserts = simpleDeepCopy(newData as unknown[])
466
+ }
467
+ (innerNewData as DataValue[]).splice(normalizedSpliceIndex, spliceDel!, ...inserts)
468
+ } else if (dataDeepCopy === DeepCopyStrategy.SimpleWithRecursion) {
469
+ change[1] = innerNewData = deepCopy(filteredData, true)
470
+ } else {
471
+ change[1] = innerNewData = simpleDeepCopy(filteredData)
472
+ }
473
+ this.innerData[propName] = innerNewData
474
+ }
475
+ }
476
+ (this.data as DataList)[propName] = filteredData
477
+ if (this._$reflectToAttributes) {
478
+ const be = comp!.getBackendElement()
479
+ if (be) {
480
+ let attrValue = filteredData
481
+ if (prop.reflectIdPrefix) {
482
+ const owner = comp!.ownerShadowRoot
483
+ if (owner) {
484
+ const idPrefix = owner.getHostNode()._$idPrefix
485
+ if (idPrefix) {
486
+ attrValue = `${idPrefix}--${filteredData as string}`
487
+ }
488
+ }
489
+ }
490
+ const attrName = propName.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)
491
+ const type = typeof attrValue
492
+ if (type === 'boolean') {
493
+ if (filteredData) be.setAttribute(attrName, '')
494
+ else be.removeAttribute(attrName)
495
+ } else if (type === 'object') {
496
+ be.setAttribute(attrName, JSON.stringify(attrValue))
497
+ } else {
498
+ be.setAttribute(attrName, attrValue)
499
+ }
500
+ }
501
+ }
502
+ if (!excluded && oldData !== filteredData) {
503
+ propChanges.push({
504
+ propName,
505
+ prop,
506
+ oldValue: oldData,
507
+ newValue: filteredData,
508
+ skipModelListener,
509
+ })
510
+ }
511
+ } else {
512
+ // do update for multi-level fields
513
+ let curData: { [key: string]: unknown } | unknown[] = this.data
514
+ let curSlice: string | number = propName
515
+ for (let i = 1; i < path.length; i += 1) {
516
+ const nextSlice = path[i]!
517
+ if (Number.isFinite(nextSlice)) {
518
+ if (
519
+ !hasOwnProperty.call(curData, curSlice)
520
+ || !Array.isArray((curData as { [key: string]: unknown })[curSlice as string])
521
+ ) {
522
+ (curData as { [key: string]: unknown })[curSlice as string] = []
523
+ }
524
+ } else {
525
+ if (
526
+ !hasOwnProperty.call(curData, curSlice)
527
+ || (curData as { [key: string]: unknown })[curSlice as string] === null
528
+ || typeof (curData as { [key: string]: unknown })[curSlice as string] !== 'object'
529
+ || Array.isArray((curData as { [key: string]: unknown })[curSlice as string])
530
+ ) {
531
+ (curData as { [key: string]: unknown })[curSlice as string] = {}
532
+ }
533
+ }
534
+ curData = (curData as { [key: string]: unknown })[curSlice as string] as
535
+ { [key: string]: unknown } | unknown[]
536
+ curSlice = nextSlice
537
+ }
538
+ let normalizedSpliceIndex: number | undefined
539
+ if (isSplice) {
540
+ const oldData = (curData as DataList)[curSlice as string]
541
+ if (Array.isArray(oldData)) {
542
+ const c = change as DataSplice
543
+ normalizedSpliceIndex = spliceIndex! >= 0 && spliceIndex! < oldData.length
544
+ ? spliceIndex!
545
+ : oldData.length
546
+ c[2] = normalizedSpliceIndex
547
+ oldData.splice(normalizedSpliceIndex, spliceDel, ...(newData as typeof oldData))
548
+ } else {
549
+ triggerWarning(`An array splice change cannot be applied to a non-array value (on path "${path.join('.')}"). The change is ignored.`)
550
+ }
551
+ } else {
552
+ (curData as DataList)[curSlice as string] = newData
553
+ }
554
+ if (!excluded && this.innerData) {
555
+ curData = this.innerData
556
+ curSlice = propName
557
+ for (let i = 1; i < path.length; i += 1) {
558
+ const nextSlice = path[i]!
559
+ if (Number.isFinite(nextSlice)) {
560
+ if (
561
+ !hasOwnProperty.call(curData, curSlice)
562
+ || !Array.isArray((curData as { [key: string]: unknown })[curSlice as string])
563
+ ) {
564
+ (curData as { [key: string]: unknown })[curSlice as string] = []
565
+ }
566
+ } else {
567
+ if (
568
+ !hasOwnProperty.call(curData, curSlice)
569
+ || (curData as { [key: string]: unknown })[curSlice as string] === null
570
+ || typeof (curData as { [key: string]: unknown })[curSlice as string] !== 'object'
571
+ || Array.isArray((curData as { [key: string]: unknown })[curSlice as string])
572
+ ) {
573
+ (curData as { [key: string]: unknown })[curSlice as string] = {}
574
+ }
575
+ }
576
+ curData = (curData as { [key: string]: unknown })[curSlice as string] as
577
+ { [key: string]: unknown } | unknown[]
578
+ curSlice = nextSlice
579
+ }
580
+ let innerNewData: unknown
581
+ if (dataDeepCopy === DeepCopyStrategy.None) {
582
+ change[1] = innerNewData = newData
583
+ } else if (normalizedSpliceIndex !== undefined) {
584
+ innerNewData = (curData as { [key: string]: unknown })[curSlice as string] as
585
+ DataValue[]
586
+ let inserts: DataValue[]
587
+ if (dataDeepCopy === DeepCopyStrategy.SimpleWithRecursion) {
588
+ change[1] = inserts = deepCopy(newData as unknown[], true)
589
+ } else {
590
+ change[1] = inserts = simpleDeepCopy(newData as unknown[])
591
+ }
592
+ (innerNewData as DataValue[]).splice(normalizedSpliceIndex, spliceDel!, ...inserts)
593
+ } else if (dataDeepCopy === DeepCopyStrategy.SimpleWithRecursion) {
594
+ change[1] = innerNewData = deepCopy(newData, true)
595
+ } else {
596
+ change[1] = innerNewData = simpleDeepCopy(newData)
597
+ }
598
+ (curData as { [key: string]: unknown })[curSlice as string] = innerNewData
599
+ }
600
+ if (!excluded && prop) {
601
+ // NOTE for prop observers, oldVal will be undefined when doing a sub-path update
602
+ propChanges.push({
603
+ propName,
604
+ prop,
605
+ oldValue: undefined,
606
+ newValue: newData,
607
+ skipModelListener: skipModelListener || false,
608
+ })
609
+ }
610
+ }
611
+ markTriggerBitOnPath(this._$observerTree, this._$observerStatus, path)
612
+ if (!excluded) {
613
+ combinedChanges.push(change)
614
+ }
615
+ if (this._$doingUpdates) {
616
+ this._$doingUpdates.count += 1
617
+ }
618
+ }
619
+
620
+ // trigger data observers
621
+ if (isChainedUpdates) return
622
+ if (this._$doingUpdates) {
623
+ let changesCount: number
624
+ do {
625
+ changesCount = this._$doingUpdates.count
626
+ triggerAndCleanTriggerBit(this._$observers, this._$observerStatus, comp, this.data)
627
+ } while (changesCount !== this._$doingUpdates.count)
628
+ this._$doingUpdates = null
629
+ }
630
+
631
+ // tell template engine what changed
632
+ this._$updateListener?.(
633
+ this.innerData || this.data,
634
+ combinedChanges,
635
+ )
636
+
637
+ // trigger prop observers (to simulating legacy behaviors)
638
+ if (comp) {
639
+ for (let i = 0; i < propChanges.length; i += 1) {
640
+ const {
641
+ propName,
642
+ prop,
643
+ oldValue,
644
+ newValue,
645
+ skipModelListener,
646
+ } = propChanges[i]!
647
+ if (!skipModelListener && this._$modelBindingListener) {
648
+ const listener = this._$modelBindingListener[propName]
649
+ if (listener) listener(newValue)
650
+ }
651
+ if (prop.observer) {
652
+ safeCallback('Property Observer', prop.observer, comp.getMethodCaller() as any, [newValue, oldValue], comp as unknown as GeneralComponentInstance)
653
+ }
654
+ if (comp._$mutationObserverTarget) {
655
+ MutationObserverTarget.callAttrObservers(comp, {
656
+ type: 'properties',
657
+ target: comp,
658
+ propertyName: propName,
659
+ })
660
+ }
661
+ }
662
+ }
663
+ }
664
+ }
665
+
666
+ export type GeneralDataGroup = DataGroup<
667
+ DataList,
668
+ PropertyList,
669
+ MethodList
670
+ >