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,230 @@
1
+ /* eslint-disable class-methods-use-this */
2
+ /* global document */
3
+
4
+ import {
5
+ DataValue,
6
+ Node,
7
+ ShadowedEvent,
8
+ GeneralBackendElement,
9
+ } from '..'
10
+ import {
11
+ DataPath,
12
+ } from '../data_path'
13
+ import { RangeListManager } from './range_list_diff'
14
+ import {
15
+ ProcGen,
16
+ BindingMapGen,
17
+ UpdatePathTreeRoot,
18
+ dataValueToString,
19
+ ProcGenWrapper,
20
+ } from './proc_gen_wrapper'
21
+ import type {
22
+ GlassEaselTemplateDOMInstance,
23
+ } from './native_rendering'
24
+
25
+ type TmplArgs = {
26
+ key?: number | string,
27
+ keyList?: RangeListManager,
28
+ scopeData?: unknown, // for wx:scope-data
29
+ dynEvListeners?: {
30
+ [name: string]: (ev: ShadowedEvent<unknown>) => boolean | undefined,
31
+ },
32
+ index?: number,
33
+ slotProps?: Record<string, [DataValue, DataPath | null, boolean]>,
34
+ slotPropsUpdatePathTree?: Record<string, UpdatePathTreeRoot>,
35
+ }
36
+ export type TmplNode = Node & { _$wxTmplArgs?: TmplArgs }
37
+
38
+ const noop = () => { /* empty */ }
39
+
40
+ export type DefineChildren = (
41
+ isCreation: boolean,
42
+ defineTextNode: DefineTextNode,
43
+ defineElement: DefineElement,
44
+ defineIfGroup: typeof noop,
45
+ defineForLoop: typeof noop,
46
+ defineSlot: DefineSlot,
47
+ definePureVirtualNode: DefinePureVirtualNode,
48
+ ) => void
49
+
50
+ type DefineTextNode = (
51
+ text: string | undefined,
52
+ textInit?: (elem: Text) => void,
53
+ ) => void
54
+
55
+ type DefineElement = (
56
+ tag: string,
57
+ genericImpls: { [key: string]: string },
58
+ propertyInit: (elem: HTMLElement, isCreation: boolean) => void,
59
+ children: DefineChildren,
60
+ slot: string | undefined,
61
+ ) => void
62
+
63
+ type DefineSlot = () => void
64
+
65
+ type DefinePureVirtualNode = (
66
+ children: DefineChildren,
67
+ slot: string | undefined,
68
+ ) => void
69
+
70
+ export class ProcGenWrapperDom {
71
+ shadowRoot: GlassEaselTemplateDOMInstance
72
+ procGen: ProcGen
73
+
74
+ constructor(
75
+ shadowRoot: GlassEaselTemplateDOMInstance,
76
+ procGen: ProcGen,
77
+ ) {
78
+ this.shadowRoot = shadowRoot
79
+ this.procGen = procGen
80
+ }
81
+
82
+ create(data: DataValue): { [field: string]: BindingMapGen[] } | undefined {
83
+ const { shadowRoot, procGen } = this
84
+ const children = procGen(this as unknown as ProcGenWrapper, true, data, undefined)
85
+ this.handleChildrenCreation(
86
+ children.C as unknown as DefineChildren,
87
+ shadowRoot.shadowRootElement as HTMLElement,
88
+ )
89
+ return children.B
90
+ }
91
+
92
+ bindingMapUpdate(
93
+ field: string,
94
+ data: DataValue,
95
+ bindingMapGenList: { [field: string]: BindingMapGen[] },
96
+ ): void {
97
+ const updaters = bindingMapGenList[field]
98
+ if (updaters === undefined) return
99
+ for (let i = 0; i < updaters.length; i += 1) {
100
+ const bindingMapGen = updaters[i]!
101
+ bindingMapGen(
102
+ data,
103
+ () => { /* empty */ },
104
+ (elem: unknown, v: string) => {
105
+ (elem as Text).textContent = v
106
+ },
107
+ )
108
+ }
109
+ }
110
+
111
+ handleChildrenCreation(children: DefineChildren, parentNode: HTMLElement): void {
112
+ let slotMergeable = true
113
+ let slotMerged = false
114
+ const appendSlot = () => {
115
+ slotMergeable = false
116
+ slotMerged = false
117
+ const elem = document.createElement('virtual')
118
+ parentNode.appendChild(elem)
119
+ this.shadowRoot.slot = elem as unknown as GeneralBackendElement
120
+ }
121
+ children(
122
+ true,
123
+ // text node
124
+ (textContent: string | undefined, textInit?: (elem: Text) => void) => {
125
+ if (slotMerged) appendSlot()
126
+ else slotMergeable = false
127
+ const elem = document.createTextNode(textContent || '')
128
+ if (textInit) textInit(elem)
129
+ parentNode.appendChild(elem)
130
+ },
131
+ // component or native node
132
+ (
133
+ tagName: string,
134
+ genericImpls: { [key: string]: string },
135
+ propertyInit: (elem: HTMLElement, isCreation: boolean) => void,
136
+ children: DefineChildren,
137
+ ) => {
138
+ if (slotMerged) appendSlot()
139
+ else slotMergeable = false
140
+ const elem = document.createElement(tagName)
141
+ propertyInit(elem, true)
142
+ this.handleChildrenCreation(children, elem)
143
+ parentNode.appendChild(elem)
144
+ },
145
+ // wx:if node or template-is node
146
+ noop,
147
+ // wx:for node
148
+ noop,
149
+ // slot node
150
+ () => {
151
+ if (slotMergeable) {
152
+ slotMerged = true
153
+ this.shadowRoot.slot = parentNode as unknown as GeneralBackendElement
154
+ } else {
155
+ appendSlot()
156
+ }
157
+ },
158
+ // other virtual node
159
+ (
160
+ children: DefineChildren,
161
+ ) => {
162
+ if (slotMerged) appendSlot()
163
+ else slotMergeable = false
164
+ const elem = document.createElement('virtual')
165
+ this.handleChildrenCreation(children, elem)
166
+ parentNode.appendChild(elem)
167
+ },
168
+ )
169
+ }
170
+
171
+ // set slot
172
+ s() {
173
+ noop()
174
+ }
175
+
176
+ // set id
177
+ i(elem: HTMLElement, v: string) {
178
+ this.shadowRoot.idMap[v] = elem
179
+ }
180
+
181
+ // set class or external classes named `class`
182
+ c(elem: HTMLElement, v: string) {
183
+ elem.setAttribute('class', v)
184
+ }
185
+
186
+ // set style or property named `style`
187
+ y(elem: HTMLElement, v: string) {
188
+ elem.setAttribute('style', v)
189
+ }
190
+
191
+ // set dataset
192
+ d(elem: HTMLElement, name: string, v: unknown) {
193
+ elem.dataset[name] = dataValueToString(v)
194
+ }
195
+
196
+ // set mark
197
+ m() {
198
+ noop()
199
+ }
200
+
201
+ // set event handler
202
+ v(
203
+ elem: HTMLElement,
204
+ evName: string,
205
+ v: string,
206
+ final: boolean,
207
+ ) {
208
+ this.shadowRoot.setListener(elem as unknown as GeneralBackendElement, evName, (ev) => {
209
+ const handler = this.shadowRoot.template.methods[v]
210
+ const ret = handler?.(ev) as unknown
211
+ if (final) return false
212
+ return ret
213
+ })
214
+ }
215
+
216
+ // set scope-data
217
+ sd() {
218
+ noop()
219
+ }
220
+
221
+ // update a property or external class of a component, or an attribute of a native node
222
+ r(elem: HTMLElement, name: string, v: unknown) {
223
+ if (typeof v === 'boolean') {
224
+ if (v) elem.setAttribute(name, '')
225
+ else elem.removeAttribute(name)
226
+ } else {
227
+ elem.setAttribute(name, dataValueToString(v))
228
+ }
229
+ }
230
+ }
@@ -0,0 +1,443 @@
1
+ import {
2
+ Element,
3
+ VirtualNode,
4
+ } from '..'
5
+ import {
6
+ DataValue,
7
+ } from '../data_proxy'
8
+ import { UpdatePathTreeNode, UpdatePathTreeRoot } from './proc_gen_wrapper'
9
+ import { triggerWarning } from '../func_arr'
10
+
11
+ export class RangeListManager {
12
+ keyName: string | null
13
+ rawKeys!: string[]
14
+ keyMap!: { [key: string]: number }
15
+ sharedKeyMap!: { [key: string]: number[] } | undefined
16
+ items!: DataValue[]
17
+ indexes!: (string | number)[] | null
18
+
19
+ constructor(
20
+ keyName: string | null,
21
+ dataList: DataValue,
22
+ elem: Element,
23
+ newListItem: (
24
+ item: DataValue,
25
+ index: number | string,
26
+ ) => VirtualNode,
27
+ ) {
28
+ this.keyName = keyName
29
+ this.updateKeys(dataList)
30
+ const items = this.items
31
+ const indexes = this.indexes
32
+ const children: VirtualNode[] = []
33
+ for (let i = 0; i < items.length; i += 1) {
34
+ const item = items[i]!
35
+ const index = indexes === null ? i : indexes[i]!
36
+ children.push(newListItem(item, index))
37
+ }
38
+ elem.insertChildren(children, -1)
39
+ }
40
+
41
+ updateKeys(dataList: DataValue) {
42
+ // collect the list depending on the the type of the data list
43
+ let items: DataValue[]
44
+ let indexes: (string | number)[] | null
45
+ if (Array.isArray(dataList)) {
46
+ items = dataList
47
+ indexes = null
48
+ } else if (typeof dataList === 'object' && dataList !== null) {
49
+ const k = Object.keys(dataList)
50
+ items = new Array<DataValue>(k.length)
51
+ indexes = new Array<string>(k.length)
52
+ for (let i = 0; i < k.length; i += 1) {
53
+ const key = k[i]!
54
+ const item = (dataList as { [key: string]: unknown })[key]
55
+ items[i] = item
56
+ indexes[i] = key
57
+ }
58
+ } else if (typeof dataList === 'string') {
59
+ triggerWarning('Use string as for-list is generally for testing. Each character is treated as an item.')
60
+ items = new Array<string>(dataList.length)
61
+ indexes = null
62
+ for (let i = 0; i < dataList.length; i += 1) {
63
+ items[i] = dataList[i]!
64
+ }
65
+ } else if (typeof dataList === 'number') {
66
+ triggerWarning('Use number as for-list is generally for testing. The number is used as the repeated times of the item.')
67
+ items = new Array<string>(dataList)
68
+ indexes = null
69
+ for (let i = 0; i < dataList; i += 1) {
70
+ items[i] = i
71
+ }
72
+ } else {
73
+ triggerWarning('The for-list data is invalid. Use empty array instead.')
74
+ items = []
75
+ indexes = null
76
+ }
77
+ this.items = items
78
+ this.indexes = indexes
79
+
80
+ // generate keys and key indexes map for the list
81
+ const keyName = this.keyName
82
+ const rawKeys = new Array<string>(items.length)
83
+ const keyMap = Object.create(null) as { [key: string]: number }
84
+ let sharedKeyMap: { [key: string]: number[] } | undefined
85
+ if (keyName !== null) {
86
+ // firstly, find all unique keys and shared keys
87
+ for (let i = 0; i < items.length; i += 1) {
88
+ const item = items[i]! as { [k: string]: unknown } | undefined
89
+ const rawKeyField = keyName === '*this' ? item : item?.[keyName]
90
+ const rawKey = rawKeyField !== undefined && rawKeyField !== null ? String(rawKeyField) : ''
91
+ rawKeys[i] = rawKey
92
+ if (keyMap[rawKey] !== undefined) {
93
+ if (!sharedKeyMap) {
94
+ sharedKeyMap = Object.create(null) as { [key: string]: number[] }
95
+ }
96
+ sharedKeyMap[rawKey] = [keyMap[rawKey]!, i]
97
+ delete keyMap[rawKey]
98
+ } else {
99
+ keyMap[rawKey] = i
100
+ }
101
+ }
102
+ // convert shared keys to unique keys
103
+ if (sharedKeyMap) {
104
+ const keys = Object.keys(sharedKeyMap)
105
+ triggerWarning(`Some keys are not unique while list updates: "${keys.join('", "')}".`)
106
+ for (let i = 0; i < keys.length; i += 1) {
107
+ const key = keys[i]!
108
+ const items = sharedKeyMap[key]!
109
+ let inc = 0
110
+ for (let j = 0; j < items.length; j += 1) {
111
+ const index = items[j]!
112
+ while (keyMap[`${key}--${inc}`] !== undefined) inc += 1
113
+ const k = `${key}--${inc}`
114
+ keyMap[k] = index
115
+ rawKeys[index] = k
116
+ }
117
+ }
118
+ }
119
+ }
120
+ this.rawKeys = rawKeys
121
+ this.keyMap = keyMap
122
+ this.sharedKeyMap = sharedKeyMap
123
+ }
124
+
125
+ diff(
126
+ dataList: DataValue[],
127
+ oriUpdatePathTree: UpdatePathTreeRoot,
128
+ elem: Element,
129
+ newListItem: (
130
+ item: DataValue,
131
+ index: string | number,
132
+ ) => VirtualNode,
133
+ updateListItem: (
134
+ item: DataValue,
135
+ index: string | number,
136
+ updatePathTree: UpdatePathTreeRoot,
137
+ indexChanged: boolean,
138
+ node: VirtualNode,
139
+ ) => void,
140
+ ) {
141
+ // generate new list for comparison
142
+ const oldRawKeys = this.rawKeys
143
+ const oldKeyMap = this.keyMap
144
+ const oldSharedKeyMap = this.sharedKeyMap
145
+ const oldIndexes = this.indexes
146
+ this.updateKeys(dataList)
147
+ const newRawKeys = this.rawKeys
148
+ const newSharedKeyMap = this.sharedKeyMap
149
+ const items = this.items
150
+ const indexes = this.indexes
151
+ const keyName = this.keyName
152
+
153
+ // transform the update path tree if needed:
154
+ // if the list has been splice-updated, or a key is updated,
155
+ // all items with old-list shared keys or new-list shared keys should be marked;
156
+ // and items with key updates should be marked
157
+ let allowFastComparison: boolean
158
+ let updatePathTree: UpdatePathTreeRoot
159
+ if (oriUpdatePathTree === true) {
160
+ updatePathTree = true
161
+ allowFastComparison = keyName === null
162
+ } else if (oriUpdatePathTree === undefined) {
163
+ updatePathTree = oriUpdatePathTree
164
+ allowFastComparison = true
165
+ } else if (keyName === null) {
166
+ updatePathTree = true
167
+ allowFastComparison = true
168
+ } else {
169
+ let needUpdate = false
170
+ if (Array.isArray(oriUpdatePathTree)) {
171
+ needUpdate = true
172
+ } else {
173
+ const keys = Object.keys(oriUpdatePathTree)
174
+ for (let i = 0; i < keys.length; i += 1) {
175
+ const k = keys[i]!
176
+ const subTree = (oriUpdatePathTree as { [s: string]: UpdatePathTreeNode })[k]! as
177
+ { [s: string]: UpdatePathTreeNode } | undefined | true
178
+ if (subTree === true || (keyName === '*this' ? subTree : subTree?.[keyName])) {
179
+ needUpdate = true
180
+ break
181
+ }
182
+ }
183
+ }
184
+ if (needUpdate) {
185
+ updatePathTree = new Array(newRawKeys.length)
186
+ for (let i = 0; i < newRawKeys.length; i += 1) {
187
+ const k = newRawKeys[i]!
188
+ if (oldSharedKeyMap?.[k] !== undefined || newSharedKeyMap?.[k] !== undefined) {
189
+ updatePathTree[i] = true
190
+ } else {
191
+ const subTree = (oriUpdatePathTree as { [s: string]: UpdatePathTreeNode })[i] as
192
+ { [s: string]: UpdatePathTreeNode } | undefined | true
193
+ if (subTree === undefined) {
194
+ // empty
195
+ } else if (subTree === true || (keyName === '*this' ? subTree : subTree?.[keyName])) {
196
+ updatePathTree[i] = true
197
+ } else {
198
+ updatePathTree[i] = subTree
199
+ }
200
+ }
201
+ }
202
+ allowFastComparison = false
203
+ } else {
204
+ updatePathTree = oriUpdatePathTree
205
+ allowFastComparison = false
206
+ }
207
+ }
208
+
209
+ // if there is no key, or nothing in the old list range is updated
210
+ if (allowFastComparison) {
211
+ // transform the update path tree if needed:
212
+ // if the list has been splice-updated, then mark the whole list
213
+ let updatePathTree: UpdatePathTreeRoot
214
+ if (Array.isArray(oriUpdatePathTree)) updatePathTree = true
215
+ else updatePathTree = oriUpdatePathTree
216
+
217
+ // simply match them one-by-one
218
+ let i = 0
219
+ while (i < oldRawKeys.length && i < newRawKeys.length) {
220
+ const item = items[i]!
221
+ const index = indexes === null ? i : indexes[i]!
222
+ const oldIndex = oldIndexes === null ? i : oldIndexes[i]!
223
+ const u = updatePathTree === true || updatePathTree === undefined
224
+ ? updatePathTree
225
+ : updatePathTree[i]
226
+ updateListItem(item, index, u, index !== oldIndex, elem.childNodes[i]! as VirtualNode)
227
+ i += 1
228
+ }
229
+
230
+ // if the old list has extra items, remove them;
231
+ // if the new list has extra items, append them
232
+ if (i < oldRawKeys.length) {
233
+ elem.removeChildren(i, oldRawKeys.length - i)
234
+ } else if (i < newRawKeys.length) {
235
+ const children: VirtualNode[] = []
236
+ for (; i < newRawKeys.length; i += 1) {
237
+ const item = items[i]!
238
+ const index = indexes === null ? i : indexes[i]!
239
+ children.push(newListItem(item, index))
240
+ }
241
+ elem.insertChildren(children, -1)
242
+ }
243
+
244
+ return
245
+ }
246
+
247
+ // in order to find out which nodes are stable (a.k.a. not needed to be moved),
248
+ // here uses a classic LCS (longest-common-subsequence) algorithm for list with distinct items,
249
+ // which is O(N*logN) at worst
250
+ // (the `minIndexByLen[i]` is the minimum old-list index when LCS length is `i` )
251
+ const minIndexByLen: number[] = []
252
+ const minIndexByLenIndexes: number[] = []
253
+ const minIndexPrev = new Array<number>(newRawKeys.length)
254
+ const oldPosList = new Array<number>(newRawKeys.length)
255
+ let prevOldIndex = -1
256
+ let prevMinIndexByLenIndex = -1
257
+ for (let i = 0; i < newRawKeys.length; i += 1) {
258
+ const rawKey = newRawKeys[i]!
259
+
260
+ // the new-list current item is likely to be the same as the one in old-list;
261
+ // if that, simply use it -
262
+ // this will help to decrease the overhead in common cases (O(N) in best case)
263
+ if (oldRawKeys[prevOldIndex + 1] === rawKey) {
264
+ prevOldIndex += 1
265
+ prevMinIndexByLenIndex += 1
266
+ minIndexByLen[prevMinIndexByLenIndex] = prevOldIndex
267
+ minIndexByLenIndexes[prevMinIndexByLenIndex] = i
268
+ minIndexPrev[i] = prevMinIndexByLenIndex > 0
269
+ ? minIndexByLenIndexes[prevMinIndexByLenIndex - 1]!
270
+ : -1
271
+ oldPosList[i] = prevOldIndex
272
+ continue
273
+ }
274
+
275
+ // Skip new items
276
+ const oldIndex = oldKeyMap[rawKey]
277
+ if (oldIndex === undefined) {
278
+ oldPosList[i] = -1
279
+ continue
280
+ }
281
+
282
+ // find the old-list index for the key of the current item
283
+ let bottom = 0
284
+ let top = minIndexByLen.length
285
+ while (bottom < top) {
286
+ const mid = Math.floor((bottom + top) / 2)
287
+ if (oldIndex < minIndexByLen[mid]!) top = mid
288
+ else bottom = mid + 1
289
+ }
290
+ minIndexByLen[top] = oldIndex
291
+ minIndexByLenIndexes[top] = i
292
+ minIndexPrev[i] = top > 0 ? minIndexByLenIndexes[top - 1]! : -1
293
+ oldPosList[i] = oldIndex
294
+ prevOldIndex = oldIndex
295
+ prevMinIndexByLenIndex = top
296
+ }
297
+ const lcsLen = minIndexByLenIndexes.length
298
+
299
+ // if LCS is the whole list, then the list is not changed -
300
+ // simply match each item one-by-one
301
+ if (lcsLen === newRawKeys.length && lcsLen === oldRawKeys.length) {
302
+ let i = 0
303
+ while (i < oldRawKeys.length && i < newRawKeys.length) {
304
+ const item = items[i]!
305
+ const index = indexes === null ? i : indexes[i]!
306
+ const oldIndex = oldIndexes === null ? i : oldIndexes[i]!
307
+ const u: UpdatePathTreeRoot = updatePathTree === true || updatePathTree === undefined
308
+ ? updatePathTree
309
+ : (updatePathTree as UpdatePathTreeNode[])[i]
310
+ updateListItem(item, index, u, index !== oldIndex, elem.childNodes[i]! as VirtualNode)
311
+ i += 1
312
+ }
313
+ return
314
+ }
315
+
316
+ // search the unused `minIndexPrev` items to get the LCS item list
317
+ // ( `minIndexByLen` is reused as the LCS array, since they must have the same length)
318
+ let prevLcsIndex = lcsLen > 0 ? minIndexByLenIndexes[lcsLen - 1]! : -1
319
+ let curLcsArrIndex = lcsLen
320
+ while (prevLcsIndex !== -1) {
321
+ curLcsArrIndex -= 1
322
+ minIndexByLen[curLcsArrIndex] = prevLcsIndex
323
+ prevLcsIndex = minIndexPrev[prevLcsIndex]!
324
+ }
325
+ const lcsArr = minIndexByLen
326
+
327
+ // decide what operation to be used with each item
328
+ const enum OpKind {
329
+ Stable = 0,
330
+ ForwardMove,
331
+ BackwardMove,
332
+ }
333
+ const oldListOp = new Array(oldRawKeys.length) as (OpKind | undefined)[]
334
+ const changedItems = new Array(newRawKeys.length) as (VirtualNode | undefined)[]
335
+ let prevLcsOldPos = lcsArr[curLcsArrIndex]!
336
+ for (let i = 0; i < oldPosList.length; i += 1) {
337
+ const oldPos = oldPosList[i]!
338
+ if (i === lcsArr[curLcsArrIndex]) {
339
+ prevLcsOldPos = oldPos
340
+ curLcsArrIndex += 1
341
+ oldListOp[oldPos] = OpKind.Stable
342
+ continue
343
+ }
344
+ if (oldPos === -1) {
345
+ const item = items[i]!
346
+ const index = indexes === null ? i : indexes[i]!
347
+ changedItems[i] = newListItem(item, index)
348
+ continue
349
+ }
350
+ if (oldPos > prevLcsOldPos) {
351
+ oldListOp[oldPos] = OpKind.BackwardMove
352
+ } else {
353
+ oldListOp[oldPos] = OpKind.ForwardMove
354
+ }
355
+ changedItems[i] = elem.childNodes[oldPos] as VirtualNode
356
+ }
357
+
358
+ // visit the op list again and do the operations one-by-one
359
+ let realListDiff = 0
360
+ let opOldPos = 0
361
+ let opIndex = 0
362
+ curLcsArrIndex = 0
363
+ do {
364
+ const nextStable = curLcsArrIndex < lcsArr.length
365
+ ? lcsArr[curLcsArrIndex]!
366
+ : changedItems.length
367
+ const nextStableOldPos = curLcsArrIndex < lcsArr.length
368
+ ? oldPosList[nextStable]!
369
+ : oldListOp.length
370
+
371
+ // remove items between two LCS items
372
+ while (opOldPos < nextStableOldPos) {
373
+ if (oldListOp[opOldPos] === undefined) {
374
+ const start = opOldPos
375
+ opOldPos += 1
376
+ let count = 1
377
+ while (opOldPos < nextStableOldPos && oldListOp[opOldPos] === undefined) {
378
+ opOldPos += 1
379
+ count += 1
380
+ }
381
+ elem.removeChildren(start + realListDiff, count)
382
+ realListDiff -= count
383
+ } else {
384
+ if (oldListOp[opOldPos] === OpKind.BackwardMove) {
385
+ realListDiff -= 1
386
+ }
387
+ opOldPos += 1
388
+ }
389
+ }
390
+
391
+ // insert or move items between two LCS items
392
+ while (opIndex < nextStable) {
393
+ const newItem = changedItems[opIndex]!
394
+ const oldPos = oldPosList[opIndex]!
395
+ if (oldPos === -1) {
396
+ const start = opIndex
397
+ opIndex += 1
398
+ let count = 1
399
+ while (opIndex < nextStable && oldPosList[opIndex] === -1) {
400
+ opIndex += 1
401
+ count += 1
402
+ }
403
+ elem.insertChildren(
404
+ changedItems.slice(start, start + count) as VirtualNode[],
405
+ nextStableOldPos + realListDiff,
406
+ )
407
+ realListDiff += count
408
+ } else {
409
+ elem.insertChildAt(newItem, nextStableOldPos + realListDiff)
410
+ const item = items[opIndex]!
411
+ const index = indexes === null ? opIndex : indexes[opIndex]!
412
+ const oldIndex = oldIndexes === null ? oldPos : oldIndexes[oldPos]!
413
+ const u: UpdatePathTreeRoot = updatePathTree === true || updatePathTree === undefined
414
+ ? updatePathTree
415
+ : (updatePathTree as UpdatePathTreeNode[])[opIndex]
416
+ updateListItem(item, index, u, index !== oldIndex, newItem)
417
+ if (oldListOp[oldPos] === OpKind.BackwardMove) {
418
+ realListDiff += 1
419
+ }
420
+ opIndex += 1
421
+ }
422
+ }
423
+
424
+ // stable items might be marked and indexes in stable positions might change;
425
+ // if that, do an update
426
+ // IDEA do not update if {{index}} is not used in template
427
+ if (curLcsArrIndex < lcsArr.length) {
428
+ const item = items[nextStable]!
429
+ const index = indexes === null ? nextStable : indexes[nextStable]!
430
+ const oldIndex = oldIndexes === null ? nextStableOldPos : oldIndexes[nextStableOldPos]!
431
+ const u: UpdatePathTreeRoot = updatePathTree === true || updatePathTree === undefined
432
+ ? updatePathTree
433
+ : (updatePathTree as UpdatePathTreeNode[])[nextStable]
434
+ const node = elem.childNodes[nextStableOldPos + realListDiff]! as VirtualNode
435
+ updateListItem(item, index, u, index !== oldIndex, node)
436
+ }
437
+
438
+ opOldPos = nextStableOldPos + 1
439
+ opIndex = nextStable + 1
440
+ curLcsArrIndex += 1
441
+ } while (curLcsArrIndex <= lcsArr.length)
442
+ }
443
+ }