web-toolkit-x 0.0.1 → 0.71.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 (46) hide show
  1. package/AUTHORS +3 -0
  2. package/LICENSE +1 -1
  3. package/NOTICE +7 -0
  4. package/README.md +169 -6
  5. package/dist/release/global.d.ts +10 -0
  6. package/dist/release/index.d.ts +650 -0
  7. package/dist/release/index.d.ts.map +1 -0
  8. package/dist/release/index.js +2837 -0
  9. package/dist/release/index.js.map +1 -0
  10. package/dist/release/toolkit-0.71.0.js +2840 -0
  11. package/dist/release/toolkit-0.71.0.js.map +1 -0
  12. package/package.json +75 -18
  13. package/src/core/bragi.ts +122 -0
  14. package/src/core/browser.ts +555 -0
  15. package/src/core/child-nodes.ts +77 -0
  16. package/src/core/comment.ts +24 -0
  17. package/src/core/component.ts +190 -0
  18. package/src/core/created-roots.ts +38 -0
  19. package/src/core/custom-element.ts +191 -0
  20. package/src/core/description.ts +241 -0
  21. package/src/core/diff.ts +590 -0
  22. package/src/core/dispatcher.ts +221 -0
  23. package/src/core/dom.ts +124 -0
  24. package/src/core/draft.ts +609 -0
  25. package/src/core/lifecycle.ts +301 -0
  26. package/src/core/nodes.ts +13 -0
  27. package/src/core/patch.ts +412 -0
  28. package/src/core/plugins.ts +231 -0
  29. package/src/core/reconciler.ts +185 -0
  30. package/src/core/renderer.ts +75 -0
  31. package/src/core/runtime.ts +36 -0
  32. package/src/core/sandbox.ts +128 -0
  33. package/src/core/service.ts +39 -0
  34. package/src/core/template.ts +472 -0
  35. package/src/core/text.ts +24 -0
  36. package/src/core/toolkit.ts +220 -0
  37. package/src/core/utils.ts +148 -0
  38. package/src/core/virtual-dom.ts +118 -0
  39. package/src/core/virtual-element.ts +72 -0
  40. package/src/core/virtual-node.ts +87 -0
  41. package/src/core/web-component.ts +285 -0
  42. package/src/global.d.ts +10 -0
  43. package/src/index.ts +62 -0
  44. package/src/plugins/logger.ts +38 -0
  45. package/src/release.ts +3 -0
  46. package/index.js +0 -1
@@ -0,0 +1,148 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ export type AnyFunction = (...args: any[]) => unknown
3
+
4
+ /*
5
+ * Checks a condition the toolkit relies on internally. Unlike
6
+ * toolkit.assert, which reports invalid usage, it throws in all modes.
7
+ */
8
+ export function invariant(
9
+ condition: unknown,
10
+ message: string,
11
+ ): asserts condition {
12
+ if (!condition) {
13
+ throw new Error(message)
14
+ }
15
+ }
16
+
17
+ const throttle = <T extends AnyFunction>(
18
+ fn: T,
19
+ wait = 200,
20
+ delayFirstEvent = false,
21
+ ) => {
22
+ let lastTimestamp = 0
23
+ let taskId: ReturnType<typeof setTimeout> | null = null
24
+
25
+ let context: unknown
26
+ let params: Parameters<T>
27
+
28
+ return function throttled(this: unknown, ...args: Parameters<T>) {
29
+ if (!taskId) {
30
+ const timestamp = Date.now()
31
+ const elapsed = timestamp - lastTimestamp
32
+ const scheduleTask = (delay: number) => {
33
+ taskId = setTimeout(() => {
34
+ taskId = null
35
+ lastTimestamp = Date.now()
36
+ return fn.call(context, ...params)
37
+ }, delay)
38
+ }
39
+ if (elapsed >= wait) {
40
+ lastTimestamp = timestamp
41
+ if (!delayFirstEvent) {
42
+ return fn.call(this, ...args)
43
+ }
44
+ scheduleTask(wait)
45
+ } else {
46
+ scheduleTask(wait - elapsed)
47
+ }
48
+ }
49
+ // eslint-disable-next-line @typescript-eslint/no-this-alias -- kept for the delayed call
50
+ context = this
51
+ params = args
52
+ }
53
+ }
54
+
55
+ const debounce = <T extends AnyFunction>(
56
+ fn: T,
57
+ wait = 200,
58
+ leading = false,
59
+ ) => {
60
+ let taskId: ReturnType<typeof setTimeout> | null = null
61
+ // whether the delayed call is needed, not when only the leading call was made
62
+ let isTrailing = false
63
+
64
+ let context: unknown
65
+ let params: Parameters<T>
66
+
67
+ return function debounced(this: unknown, ...args: Parameters<T>) {
68
+ const isFirstInvocation = !taskId
69
+ if (taskId) {
70
+ clearTimeout(taskId)
71
+ }
72
+ isTrailing = !(isFirstInvocation && leading)
73
+ taskId = setTimeout(() => {
74
+ taskId = null
75
+ if (isTrailing) {
76
+ fn.call(context, ...params)
77
+ }
78
+ }, wait)
79
+
80
+ // eslint-disable-next-line @typescript-eslint/no-this-alias -- kept for the delayed call
81
+ context = this
82
+ params = args
83
+
84
+ if (!isTrailing) {
85
+ return fn.call(context, ...params)
86
+ }
87
+ }
88
+ }
89
+
90
+ const createUUID = () => {
91
+ const s4 = () =>
92
+ Math.floor((1 + Math.random()) * 0x10000)
93
+ .toString(16)
94
+ .substring(1)
95
+ return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`
96
+ }
97
+
98
+ export const lowerDash = (name: string) =>
99
+ name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
100
+
101
+ export const getAttributeName = (key: string) => {
102
+ if (key === 'acceptCharset' || key === 'httpEquiv') {
103
+ return lowerDash(key)
104
+ } else if (key.startsWith('aria')) {
105
+ return `aria-${key.slice(4).toLowerCase()}`
106
+ }
107
+ return key.toLowerCase()
108
+ }
109
+
110
+ export const getEventName = (key: string) =>
111
+ key === 'onDoubleClick' ? 'dblclick' : key.slice(2).toLowerCase()
112
+
113
+ const postRender = (fn: FrameRequestCallback) => {
114
+ // since Chromium 64 there are some problems with animations not being
115
+ // triggered correctly, this hack solves the problem across all OS-es
116
+
117
+ /* eslint-disable prefer-arrow-callback */
118
+ requestAnimationFrame(function () {
119
+ requestAnimationFrame(fn)
120
+ })
121
+ /* eslint-enable prefer-arrow-callback */
122
+ }
123
+
124
+ /* Checks if the arrays have the same items, in the same order. */
125
+ export const isSameList = (a: readonly unknown[], b: readonly unknown[]) =>
126
+ a.length === b.length && a.every((item, index) => item === b[index])
127
+
128
+ export const deepFreeze = <T>(obj: T): T => {
129
+ if (obj === null || typeof obj !== 'object' || Object.isFrozen(obj)) {
130
+ // functions are intentionally not frozen
131
+ return obj
132
+ }
133
+ Object.freeze(obj)
134
+ for (const property of Object.getOwnPropertyNames(obj)) {
135
+ deepFreeze((obj as Record<string, unknown>)[property])
136
+ }
137
+ return obj
138
+ }
139
+
140
+ /* The helpers of the public API, the others are used internally. */
141
+ const Utils = {
142
+ throttle,
143
+ debounce,
144
+ createUUID,
145
+ postRender,
146
+ }
147
+
148
+ export default Utils
@@ -0,0 +1,118 @@
1
+ import type { ComponentDescription, NodeDescription } from './description.js'
2
+ import {
3
+ Comment,
4
+ type Component,
5
+ type ParentVirtualNode,
6
+ Text,
7
+ VirtualElement,
8
+ type VirtualNode,
9
+ WebComponent,
10
+ } from './nodes.js'
11
+
12
+ const VirtualDOM = {
13
+ /**
14
+ * Creates a new Virtual DOM structure from given description.
15
+ */
16
+ createFromDescription(
17
+ description: NodeDescription | null,
18
+ parent?: VirtualNode | null,
19
+ context?: WebComponent | null,
20
+ ): VirtualNode | null {
21
+ if (!description) {
22
+ return null
23
+ }
24
+ switch (description.type) {
25
+ case 'component':
26
+ return this.createComponent(description, parent, context)
27
+ case 'element': {
28
+ const element = new VirtualElement(description, parent, context)
29
+ this.createChildNodes(element)
30
+ return element
31
+ }
32
+ case 'comment':
33
+ return new Comment(description, parent!)
34
+ case 'text':
35
+ return new Text(description, parent!)
36
+ default:
37
+ throw new Error(
38
+ `Unsupported node type: ${(description as NodeDescription).type}`,
39
+ )
40
+ }
41
+ },
42
+
43
+ /**
44
+ * Creates the child nodes of an element or a Web Component.
45
+ */
46
+ createChildNodes(parent: ParentVirtualNode) {
47
+ const { children } = parent.description
48
+ if (children) {
49
+ parent.setChildNodes(
50
+ children.map(description =>
51
+ this.createFromDescription(description, parent, parent.context)!,
52
+ ),
53
+ )
54
+ }
55
+ },
56
+
57
+ /**
58
+ * Creates a new component instance from given description.
59
+ */
60
+ createComponent(
61
+ description: ComponentDescription,
62
+ parent?: VirtualNode | null,
63
+ context?: WebComponent | null,
64
+ ): Component {
65
+ const ComponentClass = description.component
66
+ if (ComponentClass.prototype instanceof WebComponent) {
67
+ return this.createWebComponent(
68
+ description,
69
+ parent && parent.rootNode,
70
+ context,
71
+ /*= requireCustomElement */ true,
72
+ )
73
+ }
74
+ const component = new ComponentClass(description, parent, context)
75
+ const nodeDescription = component.renderDescription(
76
+ description.props,
77
+ description.childrenAsTemplates,
78
+ )
79
+ component.content = this.createFromDescription(
80
+ nodeDescription,
81
+ component,
82
+ context,
83
+ )
84
+ return component
85
+ },
86
+
87
+ /**
88
+ * Creates a new Web Component instance from given description.
89
+ */
90
+ createWebComponent(
91
+ description: ComponentDescription,
92
+ parent?: VirtualNode | null,
93
+ context?: WebComponent | null,
94
+ requireCustomElement = false,
95
+ ): WebComponent {
96
+ try {
97
+ const ComponentClass = description.component as typeof WebComponent
98
+ if (requireCustomElement && !ComponentClass.elementName) {
99
+ throw new Error(
100
+ `Root component "${
101
+ ComponentClass.displayName
102
+ }" does not define custom element name!`,
103
+ )
104
+ }
105
+ const root = new ComponentClass(description, parent, context)
106
+ if (ComponentClass.elementName) {
107
+ // rendered in the light DOM of the custom element
108
+ this.createChildNodes(root)
109
+ }
110
+ return root
111
+ } catch (error) {
112
+ console.error('Error rendering root component:', description)
113
+ throw error
114
+ }
115
+ },
116
+ }
117
+
118
+ export default VirtualDOM
@@ -0,0 +1,72 @@
1
+ import ChildNodes from './child-nodes.js'
2
+ import type { ElementDescription } from './description.js'
3
+ import DOM from './dom.js'
4
+ import VirtualNode from './virtual-node.js'
5
+ import type WebComponent from './web-component.js'
6
+
7
+ class VirtualElement extends VirtualNode {
8
+ static NodeType = 'element'
9
+
10
+ declare description: ElementDescription
11
+ declare ref: HTMLElement
12
+ declare children?: VirtualNode[]
13
+
14
+ constructor(
15
+ description: ElementDescription,
16
+ parent?: VirtualNode | null,
17
+ context?: WebComponent | null,
18
+ ) {
19
+ super(description, parent, context)
20
+ this.attachDOM()
21
+ }
22
+
23
+ /* The child nodes, as shared with Web Components. */
24
+ get childNodes(): VirtualNode[] | undefined {
25
+ return this.children
26
+ }
27
+
28
+ set childNodes(nodes: VirtualNode[] | undefined) {
29
+ if (nodes) {
30
+ this.children = nodes
31
+ } else {
32
+ delete this.children
33
+ }
34
+ }
35
+
36
+ get nodeType(): string {
37
+ return VirtualElement.NodeType
38
+ }
39
+
40
+ isCompatible(node: VirtualNode | null | undefined) {
41
+ return (
42
+ super.isCompatible(node) &&
43
+ this.description.name === (node as VirtualElement).description.name
44
+ )
45
+ }
46
+
47
+ setChildNodes(nodes: VirtualNode[]) {
48
+ ChildNodes.set(this, nodes)
49
+ }
50
+
51
+ insertChild(child: VirtualNode, index?: number) {
52
+ ChildNodes.insert(this, child, index)
53
+ }
54
+
55
+ replaceChild(child: VirtualNode, node: VirtualNode) {
56
+ ChildNodes.replace(this, child, node)
57
+ }
58
+
59
+ moveChild(child: VirtualNode, from: number, to: number) {
60
+ ChildNodes.move(this, child, from, to)
61
+ }
62
+
63
+ removeChild(child: VirtualNode) {
64
+ ChildNodes.remove(this, child)
65
+ }
66
+
67
+ attachDOM() {
68
+ this.ref = DOM.createElement(this.description)
69
+ }
70
+ }
71
+
72
+ export default VirtualElement
@@ -0,0 +1,87 @@
1
+ import type Comment from './comment.js'
2
+ import type Component from './component.js'
3
+ import type { NodeDescription } from './description.js'
4
+ import type Text from './text.js'
5
+ import type VirtualElement from './virtual-element.js'
6
+ import type WebComponent from './web-component.js'
7
+
8
+ /* The DOM node rendered for a virtual node. */
9
+ export type NodeRef = Element | CharacterData
10
+
11
+ /*
12
+ * An abstract parent node.
13
+ */
14
+ abstract class VirtualNode {
15
+ declare description: NodeDescription
16
+ declare key?: string
17
+ declare parentNode: VirtualNode | null
18
+ declare context: WebComponent | null
19
+
20
+ abstract ref: NodeRef
21
+ abstract get nodeType(): string
22
+ abstract attachDOM(): void
23
+
24
+ constructor(
25
+ description: NodeDescription,
26
+ parentNode: VirtualNode | null = null,
27
+ context: WebComponent | null = null,
28
+ ) {
29
+ this.description = description
30
+ this.key = description.key
31
+ this.parentNode = parentNode
32
+ this.context = context
33
+ }
34
+
35
+ get parentElement(): VirtualNode | null {
36
+ if (this.parentNode) {
37
+ return this.parentNode.isElement()
38
+ ? this.parentNode
39
+ : this.parentNode.parentElement
40
+ }
41
+ return null
42
+ }
43
+
44
+ get container(): Element | VirtualNode | undefined {
45
+ if (this.parentNode) {
46
+ return this.parentNode.container
47
+ }
48
+ return this
49
+ }
50
+
51
+ get rootNode(): WebComponent {
52
+ if (this.isRoot()) {
53
+ return this
54
+ }
55
+ if (this.parentNode) {
56
+ return this.parentNode.rootNode
57
+ }
58
+ throw new Error('Inconsistent virtual DOM tree detected!')
59
+ }
60
+
61
+ isRoot(): this is WebComponent {
62
+ return this.nodeType === 'root'
63
+ }
64
+
65
+ isComponent(): this is Component {
66
+ const { nodeType } = this
67
+ return nodeType === 'component' || nodeType === 'root'
68
+ }
69
+
70
+ isElement(): this is VirtualElement {
71
+ return this.nodeType === 'element'
72
+ }
73
+
74
+ isComment(): this is Comment {
75
+ return this.nodeType === 'comment'
76
+ }
77
+
78
+ isText(): this is Text {
79
+ return this.nodeType === 'text'
80
+ }
81
+
82
+ isCompatible(node: VirtualNode | null | undefined) {
83
+ return node && this.nodeType === node.nodeType && this.key === node.key
84
+ }
85
+ }
86
+
87
+ export default VirtualNode
@@ -0,0 +1,285 @@
1
+ import ChildNodes from './child-nodes.js'
2
+ import Comment from './comment.js'
3
+ import Component from './component.js'
4
+ import CreatedRoots from './created-roots.js'
5
+ import { createComponentElement } from './custom-element.js'
6
+ import { CommentDescription, type ComponentDescription } from './description.js'
7
+ import Dispatcher, {
8
+ type BoundCommands,
9
+ type Commands,
10
+ type CommandsAPI,
11
+ type State,
12
+ } from './dispatcher.js'
13
+ import Plugins from './plugins.js'
14
+ import { runtime } from './runtime.js'
15
+ import Template from './template.js'
16
+ import type VirtualNode from './virtual-node.js'
17
+ import type { NodeRef } from './virtual-node.js'
18
+
19
+ const CONTAINER = Symbol('container')
20
+ const CUSTOM_ELEMENT = Symbol('custom-element')
21
+ const DISPATCHER = Symbol('dispatcher')
22
+
23
+ /*
24
+ * A root component with its own state, commands and plugins, rendered
25
+ * in a custom element when elementName is defined.
26
+ *
27
+ * P: props received from the parent,
28
+ * S: state, available as `this.props` when rendering,
29
+ * C: commands API set as the static commands, changing the state.
30
+ */
31
+ class WebComponent<
32
+ P extends object = object,
33
+ S extends object = P,
34
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- no commands besides the core ones
35
+ C extends CommandsAPI<S> = {},
36
+ > extends Component<S> {
37
+ static NodeType = 'root'
38
+
39
+ static styles: string[] = []
40
+
41
+ /*
42
+ * The child nodes rendered in the light DOM of the custom element,
43
+ * separate from the `children` templates available when rendering.
44
+ */
45
+ declare childNodes?: VirtualNode[]
46
+ declare subroots: Set<WebComponent>
47
+ declare ready: Promise<void>
48
+ declare markAsReady: () => void
49
+ declare markAsFailed: (error: Error) => void
50
+ declare plugins: Plugins | null
51
+ declare shadow: ShadowRoot | null
52
+ declare state?: S
53
+ declare pendingDescription?: ComponentDescription
54
+ declare stopTracking?: () => void
55
+ declare elementClass?: CustomElementConstructor;
56
+ declare [CONTAINER]?: Element;
57
+ declare [CUSTOM_ELEMENT]?: HTMLElement | null;
58
+ declare [DISPATCHER]: Dispatcher
59
+
60
+ constructor(
61
+ description: ComponentDescription,
62
+ parent: VirtualNode | null = null,
63
+ context: WebComponent | null = null,
64
+ ) {
65
+ super(description, parent, context, /*= attachDOM */ false)
66
+ // before being tracked, to be destroyed when rendering it fails
67
+ CreatedRoots.collectRoot(this)
68
+ this.subroots = new Set()
69
+ this.dispatcher = new Dispatcher(this)
70
+ this.ready = new Promise((resolve, reject) => {
71
+ this.markAsReady = resolve
72
+ this.markAsFailed = reject
73
+ })
74
+ this.plugins = this.createPlugins()
75
+ // from the start, for the plugins to be replaced when reconfigured
76
+ runtime().track(this)
77
+ this.content = this.createPlaceholder()
78
+ this.shadow = null
79
+ this.attachDOM()
80
+ }
81
+
82
+ attachDOM() {
83
+ if ((this.constructor as typeof WebComponent).elementName) {
84
+ this.ref = createComponentElement(this)
85
+ this.plugins!.installAll()
86
+ } else {
87
+ this.plugins!.installAll()
88
+ super.attachDOM()
89
+ }
90
+ }
91
+
92
+ createPlaceholder(): VirtualNode {
93
+ return new Comment(
94
+ new CommentDescription(
95
+ (this.constructor as typeof WebComponent).displayName,
96
+ ),
97
+ )
98
+ }
99
+
100
+ /**
101
+ * Triggers the initial rendering of the component in given container.
102
+ */
103
+ async init() {
104
+ // props are passed from templates, which are not checked against P
105
+ const state = await this.getInitialState.call(
106
+ this.sandbox,
107
+ (this.description.props || {}) as P,
108
+ )
109
+ this.setState(state)
110
+
111
+ if (this.pendingDescription) {
112
+ const description = this.pendingDescription
113
+ delete this.pendingDescription
114
+ setTimeout(() => this.update(description))
115
+ }
116
+ this.isInitialized = true
117
+ this.markAsReady()
118
+ }
119
+
120
+ setState(state: S) {
121
+ if (state.constructor !== Object) {
122
+ throw new Error('Web Component state must be a plain object!')
123
+ }
124
+ this.dispatcher.setState(
125
+ Template.normalizeComponentProps(
126
+ state as State,
127
+ this.constructor as typeof WebComponent,
128
+ ),
129
+ )
130
+ }
131
+
132
+ /**
133
+ * Triggers the component update.
134
+ */
135
+ update(description: ComponentDescription) {
136
+ if (!this.isInitialized) {
137
+ this.pendingDescription = description
138
+ return
139
+ }
140
+ const state = this.getUpdatedState(
141
+ (description.props || {}) as P,
142
+ this.state || ({} as S),
143
+ )
144
+ this.setState(state)
145
+ }
146
+
147
+ /**
148
+ * The default implementation delegating the calculation of initial state
149
+ * to the state manager.
150
+ */
151
+ getInitialState(props: P): S | Promise<S> {
152
+ // the default state is a copy of the props
153
+ return { ...props } as unknown as S
154
+ }
155
+
156
+ /**
157
+ * The default implementation delegating the calculation of updated state
158
+ * to the state manager.
159
+ */
160
+ getUpdatedState(props: P, state: S): S {
161
+ return {
162
+ ...state,
163
+ ...props,
164
+ }
165
+ }
166
+
167
+ get dispatcher(): Dispatcher {
168
+ return this[DISPATCHER]
169
+ }
170
+
171
+ set dispatcher(dispatcher: Dispatcher) {
172
+ this[DISPATCHER] = dispatcher
173
+ }
174
+
175
+ get commands(): Commands<S> & BoundCommands<C> {
176
+ return this.dispatcher.commands as Commands<S> & BoundCommands<C>
177
+ }
178
+
179
+ createPlugins(): Plugins {
180
+ const plugins = new Plugins(this)
181
+ const inherited = this.parentNode
182
+ ? (this.parentNode as WebComponent).plugins
183
+ : runtime().plugins
184
+ for (const plugin of inherited!) {
185
+ plugins.register(plugin)
186
+ }
187
+ return plugins
188
+ }
189
+
190
+ async mount(container: Element): Promise<this> {
191
+ if ((this.constructor as typeof WebComponent).elementName) {
192
+ // triggers this.init() from element's connected callback
193
+ container.appendChild(this.ref)
194
+ await this.ready
195
+ } else {
196
+ this.container = container
197
+ await this.init()
198
+ }
199
+ return this
200
+ }
201
+
202
+ getStylesheets(): string[] {
203
+ const stylesheets: string[] = []
204
+ const stylesheetProviders = [...this.plugins!].filter(plugin =>
205
+ plugin.isStylesheetProvider(),
206
+ )
207
+ for (const plugin of stylesheetProviders) {
208
+ if (typeof plugin.getStylesheets !== 'function') {
209
+ throw new Error(
210
+ `Plugin '${plugin.name}' must provide the getStylesheets() method!`,
211
+ )
212
+ }
213
+ stylesheets.push(...plugin.getStylesheets())
214
+ }
215
+ const RootClass = this.constructor as typeof WebComponent
216
+ if (Array.isArray(RootClass.styles)) {
217
+ stylesheets.push(...RootClass.styles)
218
+ }
219
+ return stylesheets
220
+ }
221
+
222
+ get ref(): NodeRef {
223
+ return this[CUSTOM_ELEMENT] || super.ref
224
+ }
225
+
226
+ set ref(ref: HTMLElement | null) {
227
+ this[CUSTOM_ELEMENT] = ref
228
+ }
229
+
230
+ set container(container: Element | undefined) {
231
+ this[CONTAINER] = container
232
+ }
233
+
234
+ get container(): Element | undefined {
235
+ return this[CONTAINER]
236
+ }
237
+
238
+ get tracked(): WebComponent[] {
239
+ const tracked: WebComponent[] = []
240
+ for (const root of this.subroots) {
241
+ tracked.push(root, ...root.tracked)
242
+ }
243
+ return tracked
244
+ }
245
+
246
+ destroy() {
247
+ if (!this.plugins) {
248
+ // already destroyed
249
+ return
250
+ }
251
+ super.destroy()
252
+ // not tracked when destroyed before being initialized
253
+ this.stopTracking?.()
254
+ this.dispatcher.ignoreIncoming()
255
+ this.plugins.destroy()
256
+ this.plugins = null
257
+ this.parentNode = null
258
+ }
259
+
260
+ setChildNodes(nodes: VirtualNode[]) {
261
+ ChildNodes.set(this, nodes)
262
+ }
263
+
264
+ insertChild(child: VirtualNode, index?: number) {
265
+ ChildNodes.insert(this, child, index)
266
+ }
267
+
268
+ replaceChild(child: VirtualNode, node: VirtualNode) {
269
+ ChildNodes.replace(this, child, node)
270
+ }
271
+
272
+ moveChild(child: VirtualNode, from: number, to: number) {
273
+ ChildNodes.move(this, child, from, to)
274
+ }
275
+
276
+ removeChild(child: VirtualNode) {
277
+ ChildNodes.remove(this, child)
278
+ }
279
+
280
+ get nodeType(): string {
281
+ return WebComponent.NodeType
282
+ }
283
+ }
284
+
285
+ export default WebComponent