glass-easel 0.2.0 → 0.3.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 (197) hide show
  1. package/.eslintignore +4 -0
  2. package/dist/glass_easel.all.js +2 -2
  3. package/dist/glass_easel.all.js.map +1 -1
  4. package/dist/glass_easel.d.ts +2546 -0
  5. package/dist/glass_easel.domlike.global.js +2 -2
  6. package/dist/glass_easel.domlike.global.js.map +1 -1
  7. package/guide/zh_CN/appendix/backend_protocol.md +164 -74
  8. package/jest.unit.config.js +7 -4
  9. package/package.json +7 -5
  10. package/rollup.config.ts +101 -0
  11. package/src/backend/backend_protocol.ts +26 -345
  12. package/src/backend/composed_backend_protocol.ts +10 -264
  13. package/src/backend/current_window_backend_context.ts +478 -0
  14. package/src/backend/domlike_backend_protocol.ts +9 -498
  15. package/src/backend/empty_backend.ts +265 -0
  16. package/src/backend/empty_composed_backend.ts +245 -0
  17. package/src/backend/index.ts +18 -0
  18. package/src/backend/suggested_backend_protocol.ts +62 -11
  19. package/src/behavior.ts +131 -442
  20. package/src/class_list.ts +328 -222
  21. package/src/component.ts +349 -184
  22. package/src/component_params.ts +54 -25
  23. package/src/component_space.ts +36 -15
  24. package/src/data_path.ts +22 -34
  25. package/src/data_proxy.ts +334 -27
  26. package/src/devtool.ts +89 -0
  27. package/src/element.ts +667 -285
  28. package/src/element_iterator.ts +9 -4
  29. package/src/event.ts +39 -20
  30. package/src/external_shadow_tree.ts +2 -2
  31. package/src/func_arr.ts +55 -101
  32. package/src/global_options.ts +18 -42
  33. package/src/index.ts +72 -73
  34. package/src/mutation_observer.ts +10 -7
  35. package/src/native_node.ts +79 -35
  36. package/src/node.ts +23 -30
  37. package/src/relation.ts +101 -12
  38. package/src/render.ts +5 -3
  39. package/src/selector.ts +9 -7
  40. package/src/shadow_root.ts +96 -240
  41. package/src/template_engine.ts +10 -7
  42. package/src/text_node.ts +43 -19
  43. package/src/tmpl/index.ts +38 -30
  44. package/src/tmpl/native_rendering.ts +14 -21
  45. package/src/tmpl/proc_gen_wrapper.ts +189 -151
  46. package/src/tmpl/proc_gen_wrapper_dom.ts +10 -8
  47. package/src/tmpl/range_list_diff.ts +30 -9
  48. package/src/trait_behaviors.ts +1 -1
  49. package/src/type_symbol.ts +36 -0
  50. package/src/virtual_node.ts +26 -13
  51. package/src/warning.ts +119 -0
  52. package/tests/base/composed_backend.ts +143 -142
  53. package/tests/base/env.ts +76 -3
  54. package/tests/base/match.ts +24 -22
  55. package/tests/base/shadow_backend.ts +917 -0
  56. package/tests/core/backend.test.ts +53 -1
  57. package/tests/core/behavior.test.ts +1 -1
  58. package/tests/core/data_update.test.ts +135 -0
  59. package/tests/core/misc.test.ts +91 -21
  60. package/tests/core/placeholder.test.ts +66 -25
  61. package/tests/core/slot.test.ts +161 -23
  62. package/tests/legacy/component.test.js +152 -78
  63. package/tests/legacy/data_path.test.js +19 -19
  64. package/tests/legacy/data_proxy.test.js +14 -7
  65. package/tests/legacy/external.test.js +8 -8
  66. package/tests/legacy/mutation_observer.test.js +7 -5
  67. package/tests/legacy/slot.test.js +2 -3
  68. package/tests/legacy/virtual.test.js +258 -25
  69. package/tests/tmpl/binding_map.test.ts +201 -195
  70. package/tests/tmpl/event.test.ts +138 -23
  71. package/tests/tmpl/expression.test.ts +15 -15
  72. package/tests/tmpl/lvalue.test.ts +101 -0
  73. package/tests/tmpl/structure.test.ts +486 -43
  74. package/tests/types/chaining.test.ts +27 -30
  75. package/tests/types/createElement.test.ts +5 -4
  76. package/tests/types/definition.test.ts +11 -14
  77. package/tsconfig.json +1 -3
  78. package/dist/glass_easel.all.d.ts +0 -1
  79. package/dist/glass_easel.domlike.global.d.ts +0 -1
  80. package/dist/index.d.ts +0 -1
  81. package/dist/index.js +0 -1
  82. package/dist/types/src/backend/backend_protocol.d.ts +0 -141
  83. package/dist/types/src/backend/backend_protocol.d.ts.map +0 -1
  84. package/dist/types/src/backend/composed_backend_protocol.d.ts +0 -102
  85. package/dist/types/src/backend/composed_backend_protocol.d.ts.map +0 -1
  86. package/dist/types/src/backend/domlike_backend_protocol.d.ts +0 -100
  87. package/dist/types/src/backend/domlike_backend_protocol.d.ts.map +0 -1
  88. package/dist/types/src/backend/mode.d.ts +0 -65
  89. package/dist/types/src/backend/mode.d.ts.map +0 -1
  90. package/dist/types/src/backend/suggested_backend_protocol.d.ts +0 -30
  91. package/dist/types/src/backend/suggested_backend_protocol.d.ts.map +0 -1
  92. package/dist/types/src/behavior.d.ts +0 -434
  93. package/dist/types/src/behavior.d.ts.map +0 -1
  94. package/dist/types/src/class_list.d.ts +0 -83
  95. package/dist/types/src/class_list.d.ts.map +0 -1
  96. package/dist/types/src/component.d.ts +0 -325
  97. package/dist/types/src/component.d.ts.map +0 -1
  98. package/dist/types/src/component_params.d.ts +0 -236
  99. package/dist/types/src/component_params.d.ts.map +0 -1
  100. package/dist/types/src/component_space.d.ts +0 -211
  101. package/dist/types/src/component_space.d.ts.map +0 -1
  102. package/dist/types/src/data_path.d.ts +0 -5
  103. package/dist/types/src/data_path.d.ts.map +0 -1
  104. package/dist/types/src/data_proxy.d.ts +0 -107
  105. package/dist/types/src/data_proxy.d.ts.map +0 -1
  106. package/dist/types/src/data_utils.d.ts +0 -3
  107. package/dist/types/src/data_utils.d.ts.map +0 -1
  108. package/dist/types/src/element.d.ts +0 -365
  109. package/dist/types/src/element.d.ts.map +0 -1
  110. package/dist/types/src/element_iterator.d.ts +0 -43
  111. package/dist/types/src/element_iterator.d.ts.map +0 -1
  112. package/dist/types/src/event.d.ts +0 -105
  113. package/dist/types/src/event.d.ts.map +0 -1
  114. package/dist/types/src/external_shadow_tree.d.ts +0 -20
  115. package/dist/types/src/external_shadow_tree.d.ts.map +0 -1
  116. package/dist/types/src/func_arr.d.ts +0 -39
  117. package/dist/types/src/func_arr.d.ts.map +0 -1
  118. package/dist/types/src/global_options.d.ts +0 -120
  119. package/dist/types/src/global_options.d.ts.map +0 -1
  120. package/dist/types/src/index.d.ts +0 -43
  121. package/dist/types/src/index.d.ts.map +0 -1
  122. package/dist/types/src/mutation_observer.d.ts +0 -79
  123. package/dist/types/src/mutation_observer.d.ts.map +0 -1
  124. package/dist/types/src/native_node.d.ts +0 -11
  125. package/dist/types/src/native_node.d.ts.map +0 -1
  126. package/dist/types/src/node.d.ts +0 -48
  127. package/dist/types/src/node.d.ts.map +0 -1
  128. package/dist/types/src/relation.d.ts +0 -46
  129. package/dist/types/src/relation.d.ts.map +0 -1
  130. package/dist/types/src/render.d.ts +0 -3
  131. package/dist/types/src/render.d.ts.map +0 -1
  132. package/dist/types/src/selector.d.ts +0 -32
  133. package/dist/types/src/selector.d.ts.map +0 -1
  134. package/dist/types/src/shadow_root.d.ts +0 -174
  135. package/dist/types/src/shadow_root.d.ts.map +0 -1
  136. package/dist/types/src/template_engine.d.ts +0 -56
  137. package/dist/types/src/template_engine.d.ts.map +0 -1
  138. package/dist/types/src/text_node.d.ts +0 -44
  139. package/dist/types/src/text_node.d.ts.map +0 -1
  140. package/dist/types/src/tmpl/index.d.ts +0 -19
  141. package/dist/types/src/tmpl/index.d.ts.map +0 -1
  142. package/dist/types/src/tmpl/native_rendering.d.ts +0 -45
  143. package/dist/types/src/tmpl/native_rendering.d.ts.map +0 -1
  144. package/dist/types/src/tmpl/proc_gen_wrapper.d.ts +0 -89
  145. package/dist/types/src/tmpl/proc_gen_wrapper.d.ts.map +0 -1
  146. package/dist/types/src/tmpl/proc_gen_wrapper_dom.d.ts +0 -50
  147. package/dist/types/src/tmpl/proc_gen_wrapper_dom.d.ts.map +0 -1
  148. package/dist/types/src/tmpl/range_list_diff.d.ts +0 -19
  149. package/dist/types/src/tmpl/range_list_diff.d.ts.map +0 -1
  150. package/dist/types/src/trait_behaviors.d.ts +0 -38
  151. package/dist/types/src/trait_behaviors.d.ts.map +0 -1
  152. package/dist/types/src/virtual_node.d.ts +0 -10
  153. package/dist/types/src/virtual_node.d.ts.map +0 -1
  154. package/dist/types/tests/backend/domlike.test.d.ts +0 -2
  155. package/dist/types/tests/backend/domlike.test.d.ts.map +0 -1
  156. package/dist/types/tests/base/composed_backend.d.ts +0 -123
  157. package/dist/types/tests/base/composed_backend.d.ts.map +0 -1
  158. package/dist/types/tests/base/env.d.ts +0 -30
  159. package/dist/types/tests/base/env.d.ts.map +0 -1
  160. package/dist/types/tests/base/match.d.ts +0 -9
  161. package/dist/types/tests/base/match.d.ts.map +0 -1
  162. package/dist/types/tests/core/backend.test.d.ts +0 -2
  163. package/dist/types/tests/core/backend.test.d.ts.map +0 -1
  164. package/dist/types/tests/core/behavior.test.d.ts +0 -2
  165. package/dist/types/tests/core/behavior.test.d.ts.map +0 -1
  166. package/dist/types/tests/core/component_space.test.d.ts +0 -2
  167. package/dist/types/tests/core/component_space.test.d.ts.map +0 -1
  168. package/dist/types/tests/core/data_update.test.d.ts +0 -2
  169. package/dist/types/tests/core/data_update.test.d.ts.map +0 -1
  170. package/dist/types/tests/core/misc.test.d.ts +0 -2
  171. package/dist/types/tests/core/misc.test.d.ts.map +0 -1
  172. package/dist/types/tests/core/placeholder.test.d.ts +0 -2
  173. package/dist/types/tests/core/placeholder.test.d.ts.map +0 -1
  174. package/dist/types/tests/core/slot.test.d.ts +0 -2
  175. package/dist/types/tests/core/slot.test.d.ts.map +0 -1
  176. package/dist/types/tests/core/trait_behaviors.test.d.ts +0 -2
  177. package/dist/types/tests/core/trait_behaviors.test.d.ts.map +0 -1
  178. package/dist/types/tests/tmpl/binding_map.test.d.ts +0 -2
  179. package/dist/types/tests/tmpl/binding_map.test.d.ts.map +0 -1
  180. package/dist/types/tests/tmpl/event.test.d.ts +0 -2
  181. package/dist/types/tests/tmpl/event.test.d.ts.map +0 -1
  182. package/dist/types/tests/tmpl/expression.test.d.ts +0 -2
  183. package/dist/types/tests/tmpl/expression.test.d.ts.map +0 -1
  184. package/dist/types/tests/tmpl/lvalue.test.d.ts +0 -2
  185. package/dist/types/tests/tmpl/lvalue.test.d.ts.map +0 -1
  186. package/dist/types/tests/tmpl/native_rendering.test.d.ts +0 -2
  187. package/dist/types/tests/tmpl/native_rendering.test.d.ts.map +0 -1
  188. package/dist/types/tests/tmpl/structure.test.d.ts +0 -2
  189. package/dist/types/tests/tmpl/structure.test.d.ts.map +0 -1
  190. package/dist/types/tests/types/chaining.test.d.ts +0 -2
  191. package/dist/types/tests/types/chaining.test.d.ts.map +0 -1
  192. package/dist/types/tests/types/createElement.test.d.ts +0 -2
  193. package/dist/types/tests/types/createElement.test.d.ts.map +0 -1
  194. package/dist/types/tests/types/definition.test.d.ts +0 -2
  195. package/dist/types/tests/types/definition.test.d.ts.map +0 -1
  196. package/webpack.config.js +0 -275
  197. /package/src/backend/{mode.ts → shared.ts} +0 -0
@@ -1,16 +1,5 @@
1
1
  import * as glassEasel from '../../src'
2
2
 
3
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
- const protocolMethod = (instance: any, propertyKey: string, descriptor: PropertyDescriptor) => {
5
- const originalFn = descriptor.value as unknown as (...args: any[]) => any
6
- descriptor.value = function (this: Context | Element, ...args: any[]) {
7
- if (this._$currentMethod === null) this._$currentMethod = propertyKey
8
- const ret: unknown = originalFn.apply(this, args)
9
- this._$currentMethod = null
10
- return ret
11
- }
12
- }
13
-
14
3
  const enum ThrowOption {
15
4
  StyleScopeNotFound,
16
5
  StyleSheetNotFound,
@@ -28,7 +17,7 @@ export class Context implements glassEasel.composedBackend.Context {
28
17
  private _$theme = 'light'
29
18
 
30
19
  private _$styleSheetIdInc = 1
31
- public _$styleScopes = new Set<number>()
20
+ public _$styleScopes = new Set<number>([0])
32
21
  private _$styleSheetContents = new Map<string, unknown>()
33
22
 
34
23
  public _$allElements: Node[] = []
@@ -46,82 +35,65 @@ export class Context implements glassEasel.composedBackend.Context {
46
35
  options: glassEasel.EventOptions,
47
36
  ) => glassEasel.EventBubbleStatus) = null
48
37
 
49
- public shouldThrows(when?: ThrowOption) {
50
- if (when === undefined) return true
38
+ public shouldThrows(when: ThrowOption) {
51
39
  if (this._$throwOn === true) return true
52
40
  if (typeof this._$throwOn !== 'boolean' && this._$throwOn[when]) return true
53
41
  return false
54
42
  }
55
- // eslint-disable-next-line class-methods-use-this
56
- private throws(message: string, when?: ThrowOption) {
57
- if (!this.shouldThrows(when)) return
58
- const method = this._$currentMethod || 'unknownMethod'
59
- this._$currentMethod = null
60
- throw new Error(`During ComposedBackend.Context#${method}: ${message}`)
61
- }
62
43
 
63
- @protocolMethod
64
44
  destroy(): void {
65
- if (this._$destroyed) this.throws('destroyed twice')
45
+ if (this._$destroyed) throw new Error('destroyed twice')
66
46
  this._$destroyed = true
67
47
  if (this._$allElements.length !== 0) {
68
- this.throws('destroyed with elements not yet destroyed')
48
+ throw new Error('destroyed with elements not yet destroyed')
69
49
  }
70
50
  }
71
51
 
72
- @protocolMethod
73
52
  getWindowWidth(): number {
74
53
  return this._$windowWidth
75
54
  }
76
55
 
77
- @protocolMethod
78
56
  getWindowHeight(): number {
79
57
  return this._$windowHeight
80
58
  }
81
59
 
82
- @protocolMethod
83
60
  getDevicePixelRatio(): number {
84
61
  return this._$devicePixelRatio
85
62
  }
86
63
 
87
- @protocolMethod
88
64
  getTheme(): string {
89
65
  return this._$theme
90
66
  }
91
67
 
92
- @protocolMethod
93
68
  registerStyleSheetContent(path: string, content: unknown): void {
94
69
  if (this._$styleSheetContents.has(path)) {
95
- this.throws(
96
- `Style sheet with path "${path}" registered twice`,
97
- ThrowOption.StyleSheetDoubleRegistration,
98
- )
70
+ if (this.shouldThrows(ThrowOption.StyleSheetDoubleRegistration)) {
71
+ throw new Error(`Style sheet with path "${path}" registered twice`)
72
+ }
99
73
  }
100
74
  this._$styleSheetContents.set(path, content)
101
75
  }
102
76
 
103
- @protocolMethod
104
77
  appendStyleSheetPath(path: string, styleScope?: number): number {
105
78
  const id = this._$styleSheetIdInc
106
79
  this._$styleSheetIdInc += 1
107
80
  this._$styleScopes.add(styleScope ?? 0)
108
81
  if (!this._$styleSheetContents.has(path)) {
109
- this.throws(
110
- `Style sheet added with path "${path}" not registered`,
111
- ThrowOption.StyleSheetNotRegistered,
112
- )
82
+ if (this.shouldThrows(ThrowOption.StyleSheetNotRegistered)) {
83
+ throw new Error(`Style sheet added with path "${path}" not registered`)
84
+ }
113
85
  }
114
86
  return id
115
87
  }
116
88
 
117
- @protocolMethod
118
89
  disableStyleSheet(index: number): void {
119
90
  if (index >= this._$styleSheetIdInc) {
120
- this.throws(`Style sheet with id "${index}" not exists`, ThrowOption.StyleSheetNotFound)
91
+ if (this.shouldThrows(ThrowOption.StyleSheetNotFound)) {
92
+ throw new Error(`Style sheet with id "${index}" not exists`)
93
+ }
121
94
  }
122
95
  }
123
96
 
124
- @protocolMethod
125
97
  render(cb: (err: Error | null) => void): void {
126
98
  if (this._$renderCallbacks) {
127
99
  this._$renderCallbacks.push(cb)
@@ -141,27 +113,22 @@ export class Context implements glassEasel.composedBackend.Context {
141
113
  this._$rootNode = rootNode
142
114
  }
143
115
 
144
- @protocolMethod
145
116
  getRootNode(): Node {
146
117
  return this._$rootNode
147
118
  }
148
119
 
149
- @protocolMethod
150
120
  createElement(tagName: string, stylingName: string): Node {
151
121
  return new Element(this, tagName, stylingName)
152
122
  }
153
123
 
154
- @protocolMethod
155
124
  createTextNode(textContent: string): Node {
156
125
  return new TextNode(this, textContent)
157
126
  }
158
127
 
159
- @protocolMethod
160
128
  createFragment(): Node {
161
129
  return new Fragment(this)
162
130
  }
163
131
 
164
- @protocolMethod
165
132
  onEvent(
166
133
  _listener: (
167
134
  target: glassEasel.Element,
@@ -173,7 +140,6 @@ export class Context implements glassEasel.composedBackend.Context {
173
140
  this._$eventEmitter = _listener
174
141
  }
175
142
 
176
- @protocolMethod
177
143
  // eslint-disable-next-line class-methods-use-this
178
144
  createMediaQueryObserver(
179
145
  _status: glassEasel.backend.MediaQueryStatus,
@@ -201,10 +167,10 @@ const stringifyType = (v: unknown) => {
201
167
  * https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
202
168
  */
203
169
  const enum NODE_TYPE {
204
- ELEMENT_NODE = 1,
205
- TEXT_NODE = 3,
206
- DOCUMENT_NODE = 9,
207
- DOCUMENT_FRAGMENT_NODE = 11,
170
+ ELEMENT_NODE,
171
+ TEXT_NODE,
172
+ DOCUMENT_NODE,
173
+ DOCUMENT_FRAGMENT_NODE,
208
174
  }
209
175
 
210
176
  /** An element for empty backend implementation */
@@ -227,8 +193,11 @@ abstract class Node implements glassEasel.composedBackend.Element {
227
193
 
228
194
  private _$style = ''
229
195
  private _$styleScope = 0
230
- private _$classes: Array<[string, number]> = []
231
- private _$attributes: Record<string, any> = {}
196
+ private _$extraStyleScope: number | undefined
197
+ private _$hostStyleScope: number | undefined
198
+ private _$classes: Array<[string, number | undefined]> | undefined
199
+ private _$attributes: Array<[string, unknown]> = []
200
+ private _$styleScopeManager: glassEasel.StyleScopeManager | undefined
232
201
 
233
202
  constructor(ownerContext: Context) {
234
203
  this._$ownerContext = ownerContext
@@ -236,126 +205,153 @@ abstract class Node implements glassEasel.composedBackend.Element {
236
205
  }
237
206
 
238
207
  // eslint-disable-next-line class-methods-use-this
239
- private throws(message: string, when?: ThrowOption) {
240
- if (!this._$ownerContext.shouldThrows(when)) return
241
- const method = this._$currentMethod || 'unknownMethod'
242
- this._$currentMethod = null
243
- throw new Error(`During ComposedBackend.Element#${method}: ${message}`)
244
- }
245
-
246
208
  private assertFragment(frag: Node) {
247
- if (!(frag instanceof Fragment)) this.throws(`expect a fragment but got ${stringifyType(frag)}`)
209
+ if (!(frag instanceof Fragment))
210
+ throw new Error(`expect a fragment but got ${stringifyType(frag)}`)
248
211
  if (frag.parentNode !== null) {
249
- this.throws('fragment should have no parent')
212
+ throw new Error('fragment should have no parent')
250
213
  }
251
214
  }
215
+ // eslint-disable-next-line class-methods-use-this
252
216
  private assertDetached(child: Node) {
253
- if (!(child instanceof Node)) this.throws(`expect an element but got ${stringifyType(child)}`)
254
- if (child.parentNode !== null) this.throws('newChild is not detached')
217
+ if (!(child instanceof Node))
218
+ throw new Error(`expect an element but got ${stringifyType(child)}`)
219
+ if (child.parentNode !== null) throw new Error('newChild is not detached')
255
220
  }
256
221
  private assertChild(child: Node) {
257
- if (!(child instanceof Node)) this.throws(`expect an element but got ${stringifyType(child)}`)
222
+ if (!(child instanceof Node))
223
+ throw new Error(`expect an element but got ${stringifyType(child)}`)
258
224
  const index = this.childNodes.indexOf(child)
259
- if (index === -1) this.throws('element is not a child of `this`')
260
- if (child.parentNode !== this) this.throws('element is not a child of `this`')
225
+ if (index === -1) throw new Error('element is not a child of `this`')
226
+ if (child.parentNode !== this) throw new Error('element is not a child of `this`')
261
227
  return index
262
228
  }
263
229
  private assertChildWithIndex(child: Node, index?: number) {
264
230
  const actualIndex = this.assertChild(child)
265
231
  const processedIndex = ensureNonNegativeInteger(index)
266
232
  if (processedIndex !== undefined && processedIndex !== actualIndex) {
267
- this.throws(`index mismatched, expected ${actualIndex} but got ${processedIndex}`)
233
+ throw new Error(`index mismatched, expected ${actualIndex} but got ${processedIndex}`)
268
234
  }
269
235
  return actualIndex
270
236
  }
271
237
  private assertIndex(index: number) {
272
238
  if (index >= this.childNodes.length) {
273
- this.throws(
239
+ throw new Error(
274
240
  `provided index is invalid, got ${index} but only ${this.childNodes.length} children exist`,
275
241
  )
276
242
  }
277
243
  }
278
244
  private assertDeleteCount(deleteCount: number, index: number) {
279
245
  if (deleteCount + index > this.childNodes.length) {
280
- this.throws(
246
+ throw new Error(
281
247
  `try to remove ${deleteCount} children starting from ${index}, but there are only ${this.childNodes.length} children.`,
282
248
  )
283
249
  }
284
250
  }
251
+ private assertStyleScope(styleScope: number | undefined) {
252
+ if (styleScope !== undefined && !this._$ownerContext._$styleScopes.has(styleScope)) {
253
+ if (this._$ownerContext.shouldThrows(ThrowOption.StyleScopeNotFound)) {
254
+ throw new Error(
255
+ `set style scope to ${styleScope}, but its owner context do not have a style sheet with that scope`,
256
+ )
257
+ }
258
+ }
259
+ }
285
260
 
286
- private logicalTagName() {
287
- if (this instanceof Root) return 'root'
288
- if (this instanceof Fragment) return 'fragment'
289
- if (this instanceof TextNode) return 'text'
290
- if (this instanceof Element) return this._$logicalTagName
291
- throw new Error('unreachable')
261
+ get tagName(): string {
262
+ if (this instanceof Element) {
263
+ return (
264
+ this._$logicalTagName === 'wx-x' ? this._$stylingTagName : this._$logicalTagName
265
+ ).toUpperCase()
266
+ }
267
+ return ''
292
268
  }
293
- // eslint-disable-next-line no-console
294
- dump(logFn: (msg: string) => void = console.info) {
295
- logFn(this.dumpToString())
269
+
270
+ get innerHTML(): string {
271
+ if (this instanceof Element) {
272
+ return this.childNodes.map((child) => child.outerHTML).join('')
273
+ }
274
+ return this.textContent || ''
296
275
  }
297
- dumpToString(indent = ''): string {
276
+
277
+ get outerHTML(): string {
298
278
  const ret: string[] = []
299
- const props: Record<string, string> = {}
300
- Object.entries(this._$attributes).forEach(([key, value]) => {
301
- props[key] = `${value as unknown as string}`
302
- })
303
- props.id = this.id
304
- props.style = this._$style
305
- props.class = this._$classes.map((c) => `${c[0]}$${c[1]}`).join(' ')
306
- const propsStr = Object.entries(props)
307
- .filter(([_, value]) => value.length > 0)
308
- .map(([key, value]) => `${key}="${value}"`)
309
- .join(' ')
310
- ret.push(indent)
311
279
  if (this instanceof Element) {
312
- ret.push(`<${this.logicalTagName()} ${this._$stylingTagName}$${this._$styleScope}`)
313
- } else {
314
- ret.push(`<${this.logicalTagName()} $${this._$styleScope}`)
315
- }
316
- if (propsStr.length > 0) ret.push(` ${propsStr}`)
317
- if (this.textContent === null && this.childNodes.length === 0) {
318
- ret.push(' />\n')
319
- } else {
320
- ret.push('>\n')
321
- if (this.textContent !== null) ret.push(`${indent} ${this.textContent}\n`)
322
- this.childNodes.forEach((child) => {
323
- ret.push(child.dumpToString(`${indent} `))
280
+ const props: Record<string, string> = {}
281
+ this._$attributes.forEach(([key]) => {
282
+ const value = this.getAttribute(key)
283
+ if (value !== null) {
284
+ props[key.toLowerCase()] = value
285
+ }
324
286
  })
325
- ret.push(`${indent}</${this.logicalTagName()}>\n`)
287
+ const tagName = this.tagName.toLowerCase()
288
+ ret.push(`<${tagName}`)
289
+ if (this.id) props.id = this.getAttribute('id')!
290
+ if (this._$style) props.style = this.getAttribute('style')!
291
+ if (this._$classes) props.class = this.getAttribute('class')!
292
+ const propsStr = Object.entries(props)
293
+ .map(([key, value]) => `${key}="${value}"`)
294
+ .join(' ')
295
+ if (propsStr.length > 0) ret.push(` ${propsStr}`)
296
+ ret.push(`>${this.innerHTML}</${tagName}>`)
297
+ } else {
298
+ ret.push(this.innerHTML)
326
299
  }
327
300
  return ret.join('')
328
301
  }
329
302
 
330
- @protocolMethod
303
+ getAttribute(name: string): string | null {
304
+ // eslint-disable-next-line no-param-reassign
305
+ name = name.toLowerCase()
306
+ if (name === 'id') return this.id
307
+ if (name === 'style') return this._$style
308
+ if (name === 'class')
309
+ return this._$classes
310
+ ? this._$classes
311
+ .map(([name, styleScope]) => {
312
+ let prefix = ''
313
+ if (styleScope !== undefined) {
314
+ prefix = this._$styleScopeManager!.queryName(styleScope) || ''
315
+ }
316
+ return `${prefix ? `${prefix}--` : ''}${name}`
317
+ })
318
+ .join(' ')
319
+ : null
320
+ const attr = this._$attributes.find((attr) => attr[0] === name)
321
+ if (!attr) return null
322
+ const value = attr[1]
323
+ if (value === false) return null
324
+ return value === true || value === undefined || value === null ? '' : String(value)
325
+ }
326
+
331
327
  release(): void {
332
- if (this._$released) this.throws('element released twice')
328
+ if (this._$released) throw new Error('element released twice')
333
329
  this._$released = true
334
330
  const index = this._$ownerContext._$allElements.indexOf(this)
335
- if (index === -1) this.throws('element not found in its owner Context')
331
+ if (index === -1) throw new Error('element not found in its owner Context')
336
332
  this._$ownerContext._$allElements.splice(index, 1)
337
333
  }
338
334
 
339
- @protocolMethod
340
335
  associateValue(v: glassEasel.Element): void {
341
336
  this.__wxElement = v
337
+ if (v.ownerShadowRoot) {
338
+ const ownerSpace = v.ownerShadowRoot.getHostNode()._$behavior.ownerSpace
339
+ this._$styleScopeManager = ownerSpace.styleScopeManager
340
+ }
342
341
  }
343
342
 
344
- @protocolMethod
345
343
  appendChild(child: Node): void {
346
344
  this.assertDetached(child)
347
345
  this.childNodes.push(child)
348
346
  child.parentNode = this
349
347
  }
350
348
 
351
- @protocolMethod
352
349
  removeChild(child: Node, index?: number): void {
353
350
  const actualIndex = this.assertChildWithIndex(child, index)
354
351
  this.childNodes.splice(actualIndex, 1)
355
352
  child.parentNode = null
356
353
  }
357
354
 
358
- @protocolMethod
359
355
  insertBefore(child: Node, before: Node, index?: number): void {
360
356
  this.assertDetached(child)
361
357
  const processedIndex = ensureNonNegativeInteger(index)
@@ -376,7 +372,6 @@ abstract class Node implements glassEasel.composedBackend.Element {
376
372
  }
377
373
  }
378
374
 
379
- @protocolMethod
380
375
  replaceChild(child: Node, oldChild: Node, index?: number): void {
381
376
  this.assertDetached(child)
382
377
  const processedIndex = ensureNonNegativeInteger(index)
@@ -401,7 +396,6 @@ abstract class Node implements glassEasel.composedBackend.Element {
401
396
  }
402
397
  }
403
398
 
404
- @protocolMethod
405
399
  spliceBefore(before: Node, deleteCount: number, list: Node): void {
406
400
  this.assertFragment(list)
407
401
  const index = this.assertChild(before)
@@ -416,7 +410,6 @@ abstract class Node implements glassEasel.composedBackend.Element {
416
410
  })
417
411
  }
418
412
 
419
- @protocolMethod
420
413
  spliceAppend(list: Node): void {
421
414
  this.assertFragment(list)
422
415
  this.childNodes.splice(this.childNodes.length, 0, ...list.childNodes)
@@ -426,7 +419,6 @@ abstract class Node implements glassEasel.composedBackend.Element {
426
419
  list.childNodes = []
427
420
  }
428
421
 
429
- @protocolMethod
430
422
  spliceRemove(before: Node, deleteCount: number): void {
431
423
  const index = this.assertChild(before)
432
424
  this.assertDeleteCount(deleteCount, index)
@@ -436,62 +428,76 @@ abstract class Node implements glassEasel.composedBackend.Element {
436
428
  })
437
429
  }
438
430
 
439
- @protocolMethod
440
431
  setId(id: string): void {
441
432
  this.id = id
442
433
  }
443
434
 
444
- @protocolMethod
445
- setStyleScope(styleScope: number): void {
446
- if (!this._$ownerContext._$styleScopes.has(styleScope)) {
447
- this.throws(
448
- `set style scope to ${styleScope}, but its owner context do not have a style sheet with that scope`,
449
- ThrowOption.StyleScopeNotFound,
450
- )
451
- }
435
+ setStyleScope(
436
+ styleScope: number,
437
+ extraStyleScope: number | undefined,
438
+ hostStyleScope: number | undefined,
439
+ ): void {
440
+ this.assertStyleScope(styleScope)
441
+ this.assertStyleScope(extraStyleScope)
442
+ this.assertStyleScope(hostStyleScope)
452
443
  this._$styleScope = styleScope
444
+ this._$extraStyleScope = extraStyleScope
445
+ this._$hostStyleScope = hostStyleScope
453
446
  }
454
447
 
455
- @protocolMethod
456
448
  setStyle(styleText: string): void {
457
449
  this._$style = styleText
458
450
  }
459
451
 
460
- @protocolMethod
461
452
  addClass(elementClass: string, styleScope?: number): void {
462
- const scope = ensureNonNegativeInteger(styleScope) || 0
453
+ const scope = ensureNonNegativeInteger(styleScope)
454
+ if (!this._$classes) this._$classes = []
463
455
  const index = this._$classes.findIndex((c) => c[0] === elementClass && c[1] === scope)
464
456
  if (index === -1) this._$classes.push([elementClass, scope])
465
457
  }
466
458
 
467
- @protocolMethod
468
459
  removeClass(elementClass: string, styleScope?: number): void {
469
- const scope = ensureNonNegativeInteger(styleScope) || 0
460
+ const scope = ensureNonNegativeInteger(styleScope)
461
+ if (!this._$classes) return
470
462
  const index = this._$classes.findIndex((c) => c[0] === elementClass && c[1] === scope)
471
463
  if (index !== -1) this._$classes.splice(index, 1)
472
464
  }
473
465
 
474
- @protocolMethod
475
466
  clearClasses(): void {
467
+ if (!this._$classes) return
476
468
  this._$classes = []
477
469
  }
478
470
 
479
- @protocolMethod
480
471
  setAttribute(name: string, value: unknown): void {
481
- this._$attributes[name] = value
472
+ // eslint-disable-next-line no-param-reassign
473
+ name = name.toLowerCase()
474
+ const index = this._$attributes.findIndex((attr) => attr[0] === name)
475
+ if (value === false) {
476
+ if (index >= 0) {
477
+ this._$attributes.splice(index, 1)
478
+ }
479
+ } else {
480
+ if (index >= 0) {
481
+ this._$attributes[index]![1] = value
482
+ } else {
483
+ this._$attributes.push([name, value])
484
+ }
485
+ }
482
486
  }
483
487
 
484
- @protocolMethod
485
488
  removeAttribute(name: string): void {
486
- delete this._$attributes[name]
489
+ // eslint-disable-next-line no-param-reassign
490
+ name = name.toLowerCase()
491
+ const index = this._$attributes.findIndex((attr) => attr[0] === name)
492
+ if (index >= 0) {
493
+ this._$attributes.splice(index, 1)
494
+ }
487
495
  }
488
496
 
489
- @protocolMethod
490
497
  setText(content: string): void {
491
498
  this.textContent = content
492
499
  }
493
500
 
494
- @protocolMethod
495
501
  // eslint-disable-next-line class-methods-use-this
496
502
  getBoundingClientRect(cb: (res: glassEasel.backend.BoundingClientRect) => void): void {
497
503
  setTimeout(() => {
@@ -504,7 +510,6 @@ abstract class Node implements glassEasel.composedBackend.Element {
504
510
  }, 0)
505
511
  }
506
512
 
507
- @protocolMethod
508
513
  // eslint-disable-next-line class-methods-use-this
509
514
  getScrollOffset(cb: (res: glassEasel.backend.ScrollOffset) => void): void {
510
515
  setTimeout(() => {
@@ -517,13 +522,11 @@ abstract class Node implements glassEasel.composedBackend.Element {
517
522
  }, 0)
518
523
  }
519
524
 
520
- @protocolMethod
521
525
  // eslint-disable-next-line class-methods-use-this
522
526
  setListenerStats(_type: string, _capture: boolean, _mutLevel: glassEasel.EventMutLevel): void {
523
527
  // empty
524
528
  }
525
529
 
526
- @protocolMethod
527
530
  // eslint-disable-next-line class-methods-use-this
528
531
  setModelBindingStat(
529
532
  _attributeName: string,
@@ -532,7 +535,6 @@ abstract class Node implements glassEasel.composedBackend.Element {
532
535
  // empty
533
536
  }
534
537
 
535
- @protocolMethod
536
538
  // eslint-disable-next-line class-methods-use-this
537
539
  createIntersectionObserver(
538
540
  _relativeElement: glassEasel.composedBackend.Element | null,
@@ -547,7 +549,6 @@ abstract class Node implements glassEasel.composedBackend.Element {
547
549
  }
548
550
  }
549
551
 
550
- @protocolMethod
551
552
  // eslint-disable-next-line class-methods-use-this
552
553
  getContext(cb: (res: unknown) => void): void {
553
554
  cb(null)
package/tests/base/env.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  import { TmplGroup } from 'glass-easel-template-compiler'
5
5
  import * as glassEasel from '../../src'
6
6
  import * as ComposedBackend from './composed_backend'
7
+ import * as ShadowBackend from './shadow_backend'
7
8
 
8
9
  glassEasel.globalOptions.throwGlobalError = true
9
10
  const warningThrow = (msg: string) => {
@@ -20,6 +21,20 @@ glassEasel.addGlobalWarningListener(warningThrow)
20
21
  return `[GlassEaselTextNode ${glassEasel.dumpSingleElementToString(this)}]`
21
22
  }
22
23
 
24
+ Object.defineProperty(window, 'matchMedia', {
25
+ writable: true,
26
+ value: jest.fn().mockImplementation((query) => ({
27
+ matches: false,
28
+ media: query as unknown,
29
+ onchange: null,
30
+ addListener: jest.fn(), // Deprecated
31
+ removeListener: jest.fn(), // Deprecated
32
+ addEventListener: jest.fn(),
33
+ removeEventListener: jest.fn(),
34
+ dispatchEvent: jest.fn(),
35
+ })),
36
+ })
37
+
23
38
  export const execWithWarn = <T>(expectCount: number, func: () => T): T => {
24
39
  let count = 0
25
40
  const warningListener = () => {
@@ -35,9 +50,30 @@ export const execWithWarn = <T>(expectCount: number, func: () => T): T => {
35
50
  return ret
36
51
  }
37
52
 
53
+ export const execWithError = <R>(func: () => R, ...errors: string[]): R | void => {
54
+ let count = 0
55
+ const errorListener = (err: unknown) => {
56
+ if (count >= errors.length) return true // will throw
57
+ expect(err).toBeInstanceOf(Error)
58
+ expect((err as Error).message).toBe(errors[count])
59
+ count += 1
60
+ return false
61
+ }
62
+ glassEasel.addGlobalErrorListener(errorListener)
63
+ try {
64
+ return func()
65
+ } catch (e) {
66
+ errorListener(e)
67
+ return undefined
68
+ } finally {
69
+ glassEasel.removeGlobalErrorListener(errorListener)
70
+ expect(count).toBe(errors.length)
71
+ }
72
+ }
73
+
38
74
  type TemplateOptions = {
39
75
  updateMode?: string
40
- disallowNativeNode?: boolean
76
+ fallbackListenerOnNativeNode?: boolean
41
77
  }
42
78
 
43
79
  export const tmpl = (src: string, options?: TemplateOptions) => {
@@ -78,6 +114,43 @@ export const multiTmpl = (src: { [path: string]: string }, options?: TemplateOpt
78
114
  }
79
115
  }
80
116
 
81
- export const domBackend = new glassEasel.domlikeBackend.CurrentWindowBackendContext()
82
- export const shadowBackend = new glassEasel.backend.EmptyBackendContext()
117
+ export const domBackend = new glassEasel.CurrentWindowBackendContext()
118
+ export const shadowBackend = new ShadowBackend.Context()
83
119
  export const composedBackend = new ComposedBackend.Context()
120
+
121
+ export const getCustomExternalTemplateEngine = (
122
+ createExternalShadowRoot: (comp: glassEasel.GeneralComponent) => glassEasel.ExternalShadowRoot,
123
+ ) => {
124
+ class CustomExternalTemplateEngine implements glassEasel.templateEngine.Template {
125
+ static create() {
126
+ return new CustomExternalTemplateEngine()
127
+ }
128
+
129
+ // eslint-disable-next-line class-methods-use-this
130
+ createInstance(comp: glassEasel.GeneralComponent): glassEasel.templateEngine.TemplateInstance {
131
+ return new EmptyTemplateInstance(comp, createExternalShadowRoot(comp))
132
+ }
133
+ }
134
+
135
+ class EmptyTemplateInstance implements glassEasel.templateEngine.TemplateInstance {
136
+ comp: glassEasel.GeneralComponent
137
+ shadowRoot: glassEasel.ExternalShadowRoot
138
+
139
+ constructor(comp: glassEasel.GeneralComponent, shadowRoot: glassEasel.ExternalShadowRoot) {
140
+ this.comp = comp
141
+ this.shadowRoot = shadowRoot
142
+ }
143
+
144
+ // eslint-disable-next-line class-methods-use-this
145
+ initValues() {
146
+ // empty
147
+ }
148
+
149
+ // eslint-disable-next-line class-methods-use-this
150
+ updateValues() {
151
+ // empty
152
+ }
153
+ }
154
+
155
+ return CustomExternalTemplateEngine
156
+ }