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
package/src/class_list.ts CHANGED
@@ -1,11 +1,14 @@
1
- import * as backend from './backend/backend_protocol'
2
- import * as composedBackend from './backend/composed_backend_protocol'
3
- import * as domlikeBackend from './backend/domlike_backend_protocol'
4
- import { BM, BackendMode } from './backend/mode'
5
- import { Element, GeneralComponent, GeneralBackendElement, StyleSegmentIndex } from '.'
6
- import { MutationObserverTarget } from './mutation_observer'
1
+ import {
2
+ BM,
3
+ BackendMode,
4
+ type GeneralBackendElement,
5
+ type backend,
6
+ type composedBackend,
7
+ type domlikeBackend,
8
+ } from './backend'
9
+ import { StyleSegmentIndex, type Element } from './element'
7
10
 
8
- const CLASS_NAME_REG_EXP = /(~|\^+)?-?[_0-9a-z][-_0-9a-z]*/gi
11
+ const CLASS_NAME_REG_EXP = /\s+/
9
12
 
10
13
  /**
11
14
  * The style scope identifier
@@ -49,7 +52,7 @@ export type AliasTarget = {
49
52
  */
50
53
  export class ClassList {
51
54
  /** @internal */
52
- private _$elem: Element
55
+ private _$element: Element
53
56
  /** @internal */
54
57
  private _$owner: ClassList | null
55
58
  /** @internal */
@@ -59,53 +62,42 @@ export class ClassList {
59
62
  /** @internal */
60
63
  private _$rootScope: StyleScopeId | undefined
61
64
  /** @internal */
62
- private _$alias: { [externalName: string]: string[] | null } | null
65
+ private _$externalNames: string[] | undefined = undefined
63
66
  /** @internal */
64
- private _$resolvedAlias: { [externalName: string]: AliasTarget[] | null } | null
67
+ private _$externalRawAlias: (string[] | undefined)[] | null = null
65
68
  /** @internal */
66
- private _$aliasDirty = false
69
+ private _$dirtyExternalNames: string[] | null = null
67
70
  /** @internal */
68
- private _$rawNames: string[] = []
71
+ private _$rawNames: string[][] = []
69
72
  /** @internal */
70
- private _$rawNamesSegmentSep: number[] = []
73
+ private _$backendNames: string[] = []
71
74
  /** @internal */
72
- private _$hasResolvedNames = false
75
+ private _$backendNameScopes: (StyleScopeId | undefined)[] = []
76
+ /** @internal */
77
+ private _$backendNamesCount: number[] = []
78
+ /** @internal */
79
+ private _$hasAliasNames = false
73
80
  /** @internal */
74
81
  private _$prefixManager: StyleScopeManager | undefined
75
82
 
76
83
  constructor(
77
- elem: Element,
78
- externalNameAlias: { [externalName: string]: string[] | null } | null,
84
+ element: Element,
85
+ externalNames: string[] | undefined,
86
+ owner: ClassList | null,
87
+ styleScope: number,
88
+ extraStyleScope: number | undefined,
89
+ styleScopeManager: StyleScopeManager | undefined,
79
90
  ) {
80
- this._$elem = elem
81
- const ownerComp = elem.ownerShadowRoot?.getHostNode()
82
- if (ownerComp) {
83
- const compOptions = ownerComp.getComponentOptions()
84
- this._$owner = ownerComp.classList
85
- this._$defaultScope = compOptions.styleScope
86
- this._$extraScope =
87
- compOptions.extraStyleScope === null ? undefined : compOptions.extraStyleScope
88
- this._$rootScope = ownerComp.classList!._$rootScope ?? this._$defaultScope
89
- } else {
90
- this._$owner = null
91
- this._$defaultScope = StyleScopeManager.globalScope()
92
- this._$extraScope = undefined
93
- this._$rootScope = undefined
94
- }
95
- this._$alias = externalNameAlias
96
- if (externalNameAlias) {
97
- const resolved = Object.create(null) as { [externalName: string]: null }
98
- Object.keys(externalNameAlias).forEach((k) => {
99
- resolved[k] = null
100
- })
101
- this._$resolvedAlias = resolved
102
- } else {
103
- this._$resolvedAlias = null
104
- }
105
- if (BM.DOMLIKE || (BM.DYNAMIC && elem.getBackendMode() === BackendMode.Domlike)) {
106
- this._$prefixManager = elem.ownerShadowRoot
107
- ?.getHostNode()
108
- .getRootBehavior().ownerSpace.styleScopeManager
91
+ this._$element = element
92
+ this._$owner = owner
93
+ this._$defaultScope = styleScope
94
+ this._$extraScope = extraStyleScope
95
+ // root owner got globalScope as it's styleScope, avoid the root owner
96
+ this._$rootScope = owner?._$owner ? owner._$rootScope : styleScope
97
+ this._$prefixManager = styleScopeManager
98
+ if (externalNames) {
99
+ this._$externalNames = externalNames
100
+ this._$externalRawAlias = []
109
101
  }
110
102
  }
111
103
 
@@ -114,26 +106,28 @@ export class ClassList {
114
106
  name: string,
115
107
  cb: (scopeId: StyleScopeId | undefined, className: string) => void,
116
108
  ) {
117
- const resolvedAlias = this._$owner?._$resolvedAlias
118
- if (resolvedAlias && resolvedAlias[name] !== undefined) {
119
- const targets = resolvedAlias[name]
120
- this._$hasResolvedNames = true
121
- if (targets) {
122
- for (let i = 0; i < targets.length; i += 1) {
123
- const target = targets[i]!
124
- cb(target.scopeId, target.className)
109
+ const owner = this._$owner
110
+ const externalNames = owner?._$externalNames
111
+ const externalIndex = externalNames ? externalNames.indexOf(name) : -1
112
+ if (owner && externalIndex !== -1) {
113
+ this._$hasAliasNames = true
114
+ const rawAlias = owner._$externalRawAlias![externalIndex]
115
+ if (rawAlias) {
116
+ for (let i = 0; i < rawAlias.length; i += 1) {
117
+ owner._$resolvePrefixes(rawAlias[i]!, cb)
125
118
  }
126
119
  }
127
120
  } else if (name[0] === '~') {
128
121
  cb(this._$rootScope, name.slice(1))
129
122
  } else if (name[0] === '^') {
130
123
  let n = name.slice(1)
131
- let owner = this._$owner
132
- while (owner && n[0] === '^') {
124
+ let owner: ClassList | null | undefined = this._$owner
125
+ while (n[0] === '^') {
133
126
  n = n.slice(1)
134
- owner = owner._$owner
127
+ owner = owner?._$owner
135
128
  }
136
- const scopeId = owner ? owner._$defaultScope : this._$rootScope
129
+ // root owner got globalScope as it's styleScope, avoid the root owner
130
+ const scopeId = owner?._$owner ? owner._$defaultScope : this._$rootScope
137
131
  cb(scopeId, n)
138
132
  } else {
139
133
  if (this._$extraScope !== undefined) {
@@ -144,194 +138,321 @@ export class ClassList {
144
138
  }
145
139
 
146
140
  /** @internal */
147
- private _$updateResolvedAliases(name: string): boolean {
148
- const slices = this._$alias![name]
149
- const resolved: AliasTarget[] = []
150
- if (slices) {
151
- slices.forEach((name) => {
152
- this._$resolvePrefixes(name, (scopeId, className) => {
153
- resolved.push({
154
- scopeId,
155
- className,
156
- })
157
- })
158
- })
141
+ _$hasAlias(name: string): boolean {
142
+ return !!this._$externalNames && this._$externalNames.includes(name)
143
+ }
144
+
145
+ /** @internal */
146
+ _$setAlias(name: string, target: string | string[]) {
147
+ if (!this._$externalNames) return
148
+ let slices: string[]
149
+ if (target === undefined || target === null) {
150
+ slices = []
151
+ } else if (Array.isArray(target)) {
152
+ slices = Array<string>(target.length)
153
+ for (let i = 0, l = target.length; i < l; i += 1) {
154
+ slices[i] = String(target[i])
155
+ }
156
+ } else {
157
+ slices = String(target)
158
+ .split(CLASS_NAME_REG_EXP)
159
+ .filter((s) => s !== '') // split result could be [ '' ]
159
160
  }
160
- let changed = false
161
- const prevResolved = this._$resolvedAlias![name]
162
- if (prevResolved) {
163
- if (prevResolved.length !== resolved.length) {
164
- changed = true
165
- } else {
166
- for (let i = 0; i < prevResolved.length; i += 1) {
167
- const a = prevResolved[i]!
168
- const b = resolved[i]!
169
- if (a.scopeId !== b.scopeId || a.className !== b.className) {
170
- changed = true
171
- break
161
+ const externalIndex = this._$externalNames.indexOf(name)
162
+ if (externalIndex === -1) return
163
+ this._$dirtyExternalNames = this._$dirtyExternalNames || []
164
+ this._$dirtyExternalNames.push(name)
165
+ this._$externalRawAlias![externalIndex] = slices
166
+ }
167
+
168
+ /** @internal */
169
+ _$getAlias(): string[] | undefined {
170
+ return this._$externalNames
171
+ }
172
+
173
+ /** @internal */
174
+ _$spreadExternalClassUpdate(): boolean {
175
+ const owner = this._$owner
176
+ if (!owner?._$dirtyExternalNames || !this._$hasAliasNames) return false
177
+ const dirtyExternalNames = owner._$dirtyExternalNames
178
+ const externalNames = this._$externalNames
179
+ if (externalNames) {
180
+ for (let externalIndex = 0; externalIndex < externalNames.length; externalIndex += 1) {
181
+ const externalRawAlias = this._$externalRawAlias![externalIndex] || []
182
+ for (let i = 0; i < dirtyExternalNames.length; i += 1) {
183
+ if (externalRawAlias.includes(dirtyExternalNames[i]!)) {
184
+ this._$dirtyExternalNames = this._$dirtyExternalNames || []
185
+ this._$dirtyExternalNames.push(externalNames[externalIndex]!)
172
186
  }
173
187
  }
174
188
  }
175
- } else if (resolved.length > 0) {
176
- changed = true
177
189
  }
178
- this._$resolvedAlias![name] = resolved
179
- return changed
190
+ const shouldUpdate = this._$rawNames.some((names) =>
191
+ names.some((name) => dirtyExternalNames.includes(name)),
192
+ )
193
+ if (shouldUpdate) {
194
+ this._$updateResolvedNames()
195
+ }
196
+ return !!this._$dirtyExternalNames || shouldUpdate
180
197
  }
181
198
 
182
199
  /** @internal */
183
- _$hasAlias(name: string): boolean {
184
- if (this._$alias && this._$alias[name] !== undefined) {
185
- return true
200
+ _$shouldUpdateExternalClass() {
201
+ if (!this._$dirtyExternalNames) return false
202
+ if (BM.SHADOW || (BM.DYNAMIC && this._$element.getBackendMode() === BackendMode.Shadow)) {
203
+ const dirtyExternalNames = this._$dirtyExternalNames
204
+ for (let i = 0; i < dirtyExternalNames.length; i += 1) {
205
+ const name = dirtyExternalNames[i]!
206
+ const externalIndex = this._$externalNames!.indexOf(name)
207
+ const targets = this._$externalRawAlias![externalIndex] || []
208
+ ;(this._$element._$backendElement as backend.Element).setClassAlias(name, targets)
209
+ }
210
+ this._$dirtyExternalNames = null
211
+ return false
186
212
  }
187
- return false
213
+ return true
188
214
  }
189
215
 
190
216
  /** @internal */
191
- _$setAlias(name: string, target: string) {
192
- if (this._$alias) {
193
- const slices = (String(target).match(CLASS_NAME_REG_EXP) as string[]) || null
194
- this._$alias[name] = slices
195
- const changed = this._$updateResolvedAliases(name)
196
- if (changed) this._$aliasDirty = true
197
- }
217
+ _$markExternalClassUpdated() {
218
+ this._$dirtyExternalNames = null
198
219
  }
199
220
 
200
221
  /** @internal */
201
- _$spreadAliasUpdate() {
202
- if (this._$aliasDirty) {
203
- this._$aliasDirty = false
204
- const callClassListUpdate = (elem: Element) => {
205
- const classList = elem.classList
206
- if (classList) {
207
- const alias = classList._$alias
208
- if (alias) {
209
- let changed = false
210
- Object.keys(alias).forEach((name) => {
211
- if (classList._$updateResolvedAliases(name)) {
212
- changed = true
213
- }
214
- })
215
- if (changed) {
216
- const comp = elem as GeneralComponent
217
- if (comp._$external === false) {
218
- callClassListUpdate(comp.shadowRoot as Element)
219
- }
222
+ private _$updateResolvedNames(): boolean {
223
+ const backendElement = this._$element._$backendElement
224
+ if (!backendElement) return false
225
+ const rawNames = this._$rawNames
226
+ const oldBackendNames = this._$backendNames
227
+ const oldBackendNameScopes = this._$backendNameScopes
228
+ const oldBackendNamesCount = this._$backendNamesCount
229
+
230
+ const newBackendNames: string[] = []
231
+ const newBackendNameScopes: (StyleScopeId | undefined)[] = []
232
+ const newBackendNamesCount: number[] = []
233
+
234
+ this._$hasAliasNames = false
235
+
236
+ if (BM.SHADOW || (BM.DYNAMIC && this._$element.getBackendMode() === BackendMode.Shadow)) {
237
+ for (let i = 0, l = rawNames.length; i < l; i += 1) {
238
+ const names = rawNames[i]
239
+ if (!names) continue
240
+ for (let j = 0, ll = names.length; j < ll; j += 1) {
241
+ const rawName = names[j]!
242
+ let found = false
243
+ for (let i = 0; i < newBackendNames.length; i += 1) {
244
+ if (rawName === newBackendNames[i]) {
245
+ newBackendNamesCount[i] += 1
246
+ found = true
247
+ break
220
248
  }
221
249
  }
222
- if (classList._$hasResolvedNames) {
223
- classList._$updateResolvedNames()
250
+ if (!found) {
251
+ newBackendNames.push(rawName)
252
+ newBackendNameScopes.push(undefined)
253
+ newBackendNamesCount.push(1)
224
254
  }
225
255
  }
226
- const children = elem.childNodes
227
- children.forEach((child) => {
228
- if (child instanceof Element) callClassListUpdate(child)
229
- })
230
256
  }
231
- const comp = this._$elem as GeneralComponent
232
- if (comp._$external === false) {
233
- callClassListUpdate(comp.shadowRoot as Element)
257
+ } else {
258
+ for (let i = 0, l = rawNames.length; i < l; i += 1) {
259
+ const names = rawNames[i]
260
+ if (!names) continue
261
+ for (let j = 0, ll = names.length; j < ll; j += 1) {
262
+ const rawName = names[j]!
263
+ this._$resolvePrefixes(rawName, (scopeId, className) => {
264
+ for (let i = 0; i < newBackendNames.length; i += 1) {
265
+ if (className === newBackendNames[i] && scopeId === newBackendNameScopes[i]) {
266
+ newBackendNamesCount[i] += 1
267
+ return
268
+ }
269
+ }
270
+ newBackendNames.push(className)
271
+ newBackendNameScopes.push(scopeId)
272
+ newBackendNamesCount.push(1)
273
+ })
274
+ }
234
275
  }
235
276
  }
236
- }
237
277
 
238
- /** @internal */
239
- private _$updateResolvedNames() {
240
- const backendElement = this._$elem.getBackendElement()
241
- if (!backendElement) return
242
- this._$hasResolvedNames = false
243
- if (BM.DOMLIKE || (BM.DYNAMIC && this._$elem.getBackendMode() === BackendMode.Domlike)) {
244
- ;(backendElement as domlikeBackend.Element).setAttribute('class', '')
245
- } else {
246
- ;(backendElement as backend.Element | composedBackend.Element).clearClasses()
278
+ let changed = false
279
+
280
+ for (let i = 0; i < newBackendNames.length; i += 1) {
281
+ let found = false
282
+ for (let j = 0; j < oldBackendNames.length; j += 1) {
283
+ if (
284
+ newBackendNames[i] === oldBackendNames[j] &&
285
+ newBackendNameScopes[i] === oldBackendNameScopes[j]
286
+ ) {
287
+ found = true
288
+ oldBackendNamesCount[j] = 0 // mark as exists
289
+ break
290
+ }
291
+ }
292
+ if (!found) {
293
+ changed = true
294
+ this._$addClassToBackend(newBackendNames[i]!, newBackendNameScopes[i], backendElement)
295
+ }
296
+ }
297
+ for (let j = 0; j < oldBackendNames.length; j += 1) {
298
+ // 0 means old exists
299
+ if (oldBackendNamesCount[j] !== 0) {
300
+ changed = true
301
+ this._$removeClassFromBackend(oldBackendNames[j]!, oldBackendNameScopes[j], backendElement)
302
+ }
247
303
  }
248
- this._$rawNames.forEach((name) => {
249
- this._$addClassToBackend(name, backendElement)
250
- })
304
+
305
+ this._$backendNames = newBackendNames
306
+ this._$backendNameScopes = newBackendNameScopes
307
+ this._$backendNamesCount = newBackendNamesCount
308
+
309
+ return changed
251
310
  }
252
311
 
253
312
  /** @internal */
254
- private _$classListAdd(name: string, scope: StyleScopeId | undefined, e: GeneralBackendElement) {
255
- if (BM.DOMLIKE || (BM.DYNAMIC && this._$elem.getBackendMode() === BackendMode.Domlike)) {
313
+ private _$addClassToBackend(
314
+ name: string,
315
+ scope: StyleScopeId | undefined,
316
+ e: GeneralBackendElement,
317
+ ) {
318
+ if (BM.DOMLIKE || (BM.DYNAMIC && this._$element.getBackendMode() === BackendMode.Domlike)) {
256
319
  const prefix = scope === undefined ? '' : this._$prefixManager?.queryName(scope)
257
320
  const val = prefix ? `${prefix}--${name}` : name
258
321
  ;(e as domlikeBackend.Element).classList.add(val)
322
+ } else if (
323
+ BM.COMPOSED ||
324
+ (BM.DYNAMIC && this._$element.getBackendMode() === BackendMode.Composed)
325
+ ) {
326
+ ;(e as composedBackend.Element).addClass(name, scope)
259
327
  } else {
260
- ;(e as backend.Element | composedBackend.Element).addClass(name, scope)
328
+ ;(e as backend.Element).addClass(name)
261
329
  }
262
330
  }
263
331
 
264
332
  /** @internal */
265
- private _$classListRemove(
333
+ private _$removeClassFromBackend(
266
334
  name: string,
267
335
  scope: StyleScopeId | undefined,
268
336
  e: GeneralBackendElement,
269
337
  ) {
270
- if (BM.DOMLIKE || (BM.DYNAMIC && this._$elem.getBackendMode() === BackendMode.Domlike)) {
338
+ if (BM.DOMLIKE || (BM.DYNAMIC && this._$element.getBackendMode() === BackendMode.Domlike)) {
271
339
  const prefix = scope && this._$prefixManager?.queryName(scope)
272
340
  const val = prefix ? `${prefix}--${name}` : name
273
341
  ;(e as domlikeBackend.Element).classList.remove(val)
342
+ } else if (
343
+ BM.COMPOSED ||
344
+ (BM.DYNAMIC && this._$element.getBackendMode() === BackendMode.Composed)
345
+ ) {
346
+ ;(e as composedBackend.Element).removeClass(name, scope)
274
347
  } else {
275
- ;(e as backend.Element | composedBackend.Element).removeClass(name, scope)
348
+ ;(e as backend.Element).removeClass(name)
276
349
  }
277
350
  }
278
351
 
279
352
  /** @internal */
280
- private _$addClassToBackend(name: string, backendElement: GeneralBackendElement) {
281
- this._$resolvePrefixes(name, (scopeId, className) => {
282
- this._$classListAdd(className, scopeId, backendElement)
283
- })
353
+ private _$addClass(
354
+ name: string,
355
+ scopeId: StyleScopeId | undefined,
356
+ backendElement: GeneralBackendElement,
357
+ ) {
358
+ const oldClassNames = this._$backendNames
359
+ const oldScopeIds = this._$backendNameScopes
360
+ const classNamesCount = this._$backendNamesCount
361
+ let found = false
362
+ for (let j = 0; j < oldClassNames.length; j += 1) {
363
+ if (name === oldClassNames[j] && scopeId === oldScopeIds[j]) {
364
+ found = true
365
+ classNamesCount[j] += 1
366
+ break
367
+ }
368
+ }
369
+ if (!found) {
370
+ oldClassNames.push(name)
371
+ oldScopeIds.push(scopeId)
372
+ classNamesCount.push(1)
373
+ this._$addClassToBackend(name, scopeId, backendElement)
374
+ }
284
375
  }
285
376
 
286
377
  /** @internal */
287
- private _$removeClassFromBackend(name: string, backendElement: GeneralBackendElement) {
288
- this._$resolvePrefixes(name, (scopeId, className) => {
289
- this._$classListRemove(className, scopeId, backendElement)
290
- })
291
- }
292
-
293
- toggle(name: string, force?: boolean) {
294
- const slices = (String(name).match(CLASS_NAME_REG_EXP) as string[]) || null
295
- if (slices) {
296
- const backendElement = this._$elem.getBackendElement()
297
- slices.forEach((slice) => {
298
- const index = this._$rawNames.indexOf(name)
299
- let isAdd: boolean
300
- if (force === undefined) {
301
- if (index < 0) isAdd = true
302
- else isAdd = false
378
+ private _$removeClass(
379
+ name: string,
380
+ scopeId: StyleScopeId | undefined,
381
+ backendElement: GeneralBackendElement,
382
+ ) {
383
+ const oldClassNames = this._$backendNames
384
+ const oldScopeIds = this._$backendNameScopes
385
+ const classNamesCount = this._$backendNamesCount
386
+ for (let j = 0; j < oldClassNames.length; j += 1) {
387
+ if (name === oldClassNames[j] && scopeId === oldScopeIds[j]) {
388
+ if (classNamesCount[j]! <= 1) {
389
+ oldClassNames.splice(j, 1)
390
+ oldScopeIds.splice(j, 1)
391
+ classNamesCount.splice(j, 1)
392
+ this._$removeClassFromBackend(name, scopeId, backendElement)
303
393
  } else {
304
- isAdd = !!force
394
+ classNamesCount[j] -= 1
305
395
  }
306
- let changed = false
307
- if (isAdd) {
308
- if (index < 0) {
309
- this._$rawNames.push(slice)
310
- if (backendElement) this._$addClassToBackend(slice, backendElement)
311
- changed = true
312
- }
313
- } else if (index >= 0) {
314
- this._$rawNames.splice(index, 1)
315
- if (backendElement) this._$removeClassFromBackend(slice, backendElement)
316
- changed = true
396
+ break
397
+ }
398
+ }
399
+ }
400
+
401
+ toggle(
402
+ name: string,
403
+ force?: boolean,
404
+ segmentIndex: StyleSegmentIndex = StyleSegmentIndex.MAIN,
405
+ ): boolean {
406
+ /* istanbul ignore if */
407
+ if (CLASS_NAME_REG_EXP.test(name)) throw new Error('Class name contains space characters.')
408
+
409
+ const backendElement = this._$element._$backendElement
410
+ const rawClassIndex = this._$rawNames[segmentIndex]
411
+ ? this._$rawNames[segmentIndex]!.indexOf(name)
412
+ : -1
413
+ const isAdd = force === undefined ? rawClassIndex === -1 : !!force
414
+
415
+ let changed = false
416
+ if (isAdd) {
417
+ if (rawClassIndex === -1) {
418
+ const rawNames = this._$rawNames
419
+ if (!rawNames[segmentIndex]) {
420
+ rawNames[segmentIndex] = []
317
421
  }
318
- if (changed) {
319
- const elem = this._$elem
320
- if (elem._$mutationObserverTarget) {
321
- MutationObserverTarget.callAttrObservers(elem, {
322
- type: 'properties',
323
- target: elem,
324
- attributeName: 'class',
422
+ const names = rawNames[segmentIndex]!
423
+ names.push(name)
424
+ if (backendElement) {
425
+ if (BM.SHADOW || (BM.DYNAMIC && this._$element.getBackendMode() === BackendMode.Shadow)) {
426
+ this._$addClass(name, undefined, backendElement)
427
+ } else {
428
+ this._$resolvePrefixes(name, (scopeId, className) => {
429
+ this._$addClass(className, scopeId, backendElement)
325
430
  })
326
431
  }
327
432
  }
328
- })
433
+ changed = true
434
+ }
435
+ } else if (rawClassIndex !== -1) {
436
+ const names = this._$rawNames[segmentIndex]
437
+ if (names) names.splice(rawClassIndex, 1)
438
+ if (backendElement) {
439
+ if (BM.SHADOW || (BM.DYNAMIC && this._$element.getBackendMode() === BackendMode.Shadow)) {
440
+ this._$removeClass(name, undefined, backendElement)
441
+ } else {
442
+ this._$resolvePrefixes(name, (scopeId, className) => {
443
+ this._$removeClass(className, scopeId, backendElement)
444
+ })
445
+ }
446
+ }
447
+ changed = true
329
448
  }
449
+ return changed
330
450
  }
331
451
 
332
- contains(name: string): boolean {
333
- for (let i = 0; i < this._$rawNames.length; i += 1) {
334
- const rn = this._$rawNames[i]!
452
+ contains(name: string, segmentIndex: StyleSegmentIndex = StyleSegmentIndex.MAIN): boolean {
453
+ const names = this._$rawNames[segmentIndex] || []
454
+ for (let i = 0; i < names.length; i += 1) {
455
+ const rn = names[i]!
335
456
  if (rn[0] === '~') {
336
457
  const n = rn.slice(1)
337
458
  if (n === name) return true
@@ -349,46 +470,31 @@ export class ClassList {
349
470
  }
350
471
 
351
472
  /** Set class string */
352
- setClassNames(names: string, index: StyleSegmentIndex = 0) {
353
- let n: string
354
- if (names === undefined || names === null) n = ''
355
- else n = String(names)
356
- const newRawNames = n.match(CLASS_NAME_REG_EXP) || []
357
- while (this._$rawNamesSegmentSep.length < index) {
358
- this._$rawNamesSegmentSep.push(
359
- this._$rawNamesSegmentSep.length > 0
360
- ? this._$rawNamesSegmentSep[this._$rawNamesSegmentSep.length - 1]!
361
- : this._$rawNames.length,
362
- )
363
- }
364
- if (this._$rawNamesSegmentSep.length === 0) {
365
- this._$rawNames = newRawNames
366
- } else {
367
- const segEnd =
368
- index < this._$rawNamesSegmentSep.length
369
- ? this._$rawNamesSegmentSep[index]!
370
- : this._$rawNames.length
371
- const segStart = index > 0 ? this._$rawNamesSegmentSep[index - 1]! : 0
372
- const segLen = segEnd - segStart
373
- const lenDiff = newRawNames.length - segLen
374
- for (let i = index; i < this._$rawNamesSegmentSep.length; i += 1) {
375
- this._$rawNamesSegmentSep[i] += lenDiff
473
+ setClassNames(
474
+ names: string | string[],
475
+ segmentIndex: StyleSegmentIndex = StyleSegmentIndex.MAIN,
476
+ ): boolean {
477
+ let n: string[]
478
+ if (names === undefined || names === null) {
479
+ n = []
480
+ } else if (Array.isArray(names)) {
481
+ n = Array<string>(names.length)
482
+ for (let i = 0, l = names.length; i < l; i += 1) {
483
+ n[i] = String(names[i])
376
484
  }
377
- this._$rawNames.splice(segStart, segLen, ...newRawNames)
378
- }
379
- this._$updateResolvedNames()
380
- const elem = this._$elem
381
- if (elem._$mutationObserverTarget) {
382
- MutationObserverTarget.callAttrObservers(elem, {
383
- type: 'properties',
384
- target: elem,
385
- attributeName: 'class',
386
- })
485
+ } else {
486
+ n = String(names)
487
+ .split(CLASS_NAME_REG_EXP)
488
+ .filter((s) => s !== '') // split result could be [ '' ]
387
489
  }
490
+ this._$rawNames[segmentIndex] = n
491
+
492
+ return this._$updateResolvedNames()
388
493
  }
389
494
 
390
495
  /** Returns space separated class string */
391
- getClassNames(): string {
392
- return this._$rawNames ? this._$rawNames.join(' ') : ''
496
+ getClassNames(segmentIndex: StyleSegmentIndex = StyleSegmentIndex.MAIN): string {
497
+ const names = this._$rawNames[segmentIndex] || []
498
+ return names ? names.join(' ') : ''
393
499
  }
394
500
  }