web-toolkit-x 0.0.1 → 0.70.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 (45) hide show
  1. package/AUTHORS +3 -0
  2. package/LICENSE +1 -1
  3. package/NOTICE +7 -0
  4. package/README.md +162 -6
  5. package/dist/release/index.d.ts +650 -0
  6. package/dist/release/index.d.ts.map +1 -0
  7. package/dist/release/index.js +2831 -0
  8. package/dist/release/index.js.map +1 -0
  9. package/dist/release/toolkit-0.70.0.js +2834 -0
  10. package/dist/release/toolkit-0.70.0.js.map +1 -0
  11. package/package.json +71 -18
  12. package/src/core/bragi.ts +122 -0
  13. package/src/core/browser.ts +538 -0
  14. package/src/core/child-nodes.ts +77 -0
  15. package/src/core/comment.ts +24 -0
  16. package/src/core/component.ts +190 -0
  17. package/src/core/created-roots.ts +38 -0
  18. package/src/core/custom-element.ts +191 -0
  19. package/src/core/description.ts +241 -0
  20. package/src/core/diff.ts +590 -0
  21. package/src/core/dispatcher.ts +221 -0
  22. package/src/core/dom.ts +124 -0
  23. package/src/core/draft.ts +609 -0
  24. package/src/core/lifecycle.ts +301 -0
  25. package/src/core/nodes.ts +13 -0
  26. package/src/core/patch.ts +412 -0
  27. package/src/core/plugins.ts +231 -0
  28. package/src/core/reconciler.ts +185 -0
  29. package/src/core/renderer.ts +75 -0
  30. package/src/core/runtime.ts +36 -0
  31. package/src/core/sandbox.ts +128 -0
  32. package/src/core/service.ts +39 -0
  33. package/src/core/template.ts +472 -0
  34. package/src/core/text.ts +24 -0
  35. package/src/core/toolkit.ts +220 -0
  36. package/src/core/utils.ts +148 -0
  37. package/src/core/virtual-dom.ts +118 -0
  38. package/src/core/virtual-element.ts +72 -0
  39. package/src/core/virtual-node.ts +87 -0
  40. package/src/core/web-component.ts +285 -0
  41. package/src/globals.d.ts +10 -0
  42. package/src/index.ts +62 -0
  43. package/src/plugins/logger.ts +38 -0
  44. package/src/release.ts +3 -0
  45. package/index.js +0 -1
@@ -0,0 +1,538 @@
1
+ import { getEventName } from './utils.js'
2
+
3
+ /*
4
+ * Names of the event listeners supported in templates, in camel case, e.g.
5
+ * onPointerDown, generated from the events of Chromium 153, with the alias
6
+ * onDoubleClick of onDblClick. Only a type, as the events are checked
7
+ * against the browser in debug mode.
8
+ */
9
+ export type EventName =
10
+ | 'onAbort'
11
+ | 'onAfterPrint'
12
+ | 'onAnimationCancel'
13
+ | 'onAnimationEnd'
14
+ | 'onAnimationIteration'
15
+ | 'onAnimationStart'
16
+ | 'onAppInstalled'
17
+ | 'onAuxClick'
18
+ | 'onBeforeCopy'
19
+ | 'onBeforeCut'
20
+ | 'onBeforeInput'
21
+ | 'onBeforeInstallPrompt'
22
+ | 'onBeforeMatch'
23
+ | 'onBeforePaste'
24
+ | 'onBeforePrint'
25
+ | 'onBeforeToggle'
26
+ | 'onBeforeUnload'
27
+ | 'onBeforeXRSelect'
28
+ | 'onBlur'
29
+ | 'onCancel'
30
+ | 'onCanPlay'
31
+ | 'onCanPlayThrough'
32
+ | 'onChange'
33
+ | 'onClick'
34
+ | 'onClose'
35
+ | 'onCommand'
36
+ | 'onCompositionEnd'
37
+ | 'onCompositionStart'
38
+ | 'onCompositionUpdate'
39
+ | 'onContentVisibilityAutoStateChange'
40
+ | 'onContextLost'
41
+ | 'onContextMenu'
42
+ | 'onContextRestored'
43
+ | 'onCopy'
44
+ | 'onCueChange'
45
+ | 'onCut'
46
+ | 'onDblClick'
47
+ | 'onDeviceMotion'
48
+ | 'onDeviceOrientation'
49
+ | 'onDeviceOrientationAbsolute'
50
+ | 'onDoubleClick'
51
+ | 'onDrag'
52
+ | 'onDragEnd'
53
+ | 'onDragEnter'
54
+ | 'onDragLeave'
55
+ | 'onDragOver'
56
+ | 'onDragStart'
57
+ | 'onDrop'
58
+ | 'onDurationChange'
59
+ | 'onEmptied'
60
+ | 'onEncrypted'
61
+ | 'onEnded'
62
+ | 'onEnterPictureInPicture'
63
+ | 'onError'
64
+ | 'onFocus'
65
+ | 'onFormData'
66
+ | 'onFreeze'
67
+ | 'onFullscreenChange'
68
+ | 'onFullscreenError'
69
+ | 'onGamepadConnected'
70
+ | 'onGamepadDisconnected'
71
+ | 'onGotPointerCapture'
72
+ | 'onHashChange'
73
+ | 'onInput'
74
+ | 'onInvalid'
75
+ | 'onKeyDown'
76
+ | 'onKeyPress'
77
+ | 'onKeyUp'
78
+ | 'onLanguageChange'
79
+ | 'onLeavePictureInPicture'
80
+ | 'onLoad'
81
+ | 'onLoadedData'
82
+ | 'onLoadedMetadata'
83
+ | 'onLoadStart'
84
+ | 'onLostPointerCapture'
85
+ | 'onMessage'
86
+ | 'onMessageError'
87
+ | 'onMouseDown'
88
+ | 'onMouseEnter'
89
+ | 'onMouseLeave'
90
+ | 'onMouseMove'
91
+ | 'onMouseOut'
92
+ | 'onMouseOver'
93
+ | 'onMouseUp'
94
+ | 'onMouseWheel'
95
+ | 'onOffline'
96
+ | 'onOnline'
97
+ | 'onPageHide'
98
+ | 'onPageReveal'
99
+ | 'onPageShow'
100
+ | 'onPageSwap'
101
+ | 'onPaste'
102
+ | 'onPause'
103
+ | 'onPlay'
104
+ | 'onPlaying'
105
+ | 'onPointerCancel'
106
+ | 'onPointerDown'
107
+ | 'onPointerEnter'
108
+ | 'onPointerLeave'
109
+ | 'onPointerLockChange'
110
+ | 'onPointerLockError'
111
+ | 'onPointerMove'
112
+ | 'onPointerOut'
113
+ | 'onPointerOver'
114
+ | 'onPointerRawUpdate'
115
+ | 'onPointerUp'
116
+ | 'onPopState'
117
+ | 'onPrerenderingChange'
118
+ | 'onProgress'
119
+ | 'onRateChange'
120
+ | 'onReadyStateChange'
121
+ | 'onRejectionHandled'
122
+ | 'onReset'
123
+ | 'onResize'
124
+ | 'onResume'
125
+ | 'onScroll'
126
+ | 'onScrollEnd'
127
+ | 'onScrollSnapChange'
128
+ | 'onScrollSnapChanging'
129
+ | 'onSearch'
130
+ | 'onSecurityPolicyViolation'
131
+ | 'onSeeked'
132
+ | 'onSeeking'
133
+ | 'onSelect'
134
+ | 'onSelectionChange'
135
+ | 'onSelectStart'
136
+ | 'onSlotChange'
137
+ | 'onStalled'
138
+ | 'onStorage'
139
+ | 'onSubmit'
140
+ | 'onSuspend'
141
+ | 'onTimeUpdate'
142
+ | 'onToggle'
143
+ | 'onTouchCancel'
144
+ | 'onTouchEnd'
145
+ | 'onTouchMove'
146
+ | 'onTouchStart'
147
+ | 'onTransitionCancel'
148
+ | 'onTransitionEnd'
149
+ | 'onTransitionRun'
150
+ | 'onTransitionStart'
151
+ | 'onUnhandledRejection'
152
+ | 'onUnload'
153
+ | 'onVisibilityChange'
154
+ | 'onVolumeChange'
155
+ | 'onWaiting'
156
+ | 'onWaitingForKey'
157
+ | 'onWebkitAnimationEnd'
158
+ | 'onWebkitAnimationIteration'
159
+ | 'onWebkitAnimationStart'
160
+ | 'onWebkitFullscreenChange'
161
+ | 'onWebkitFullscreenError'
162
+ | 'onWebkitTransitionEnd'
163
+ | 'onWheel'
164
+
165
+ /*
166
+ * Names of the element attributes supported in templates, in camel case,
167
+ * e.g. tabIndex, generated from the attributes of Chromium 153, with the
168
+ * names supported before, e.g. allowFullScreen. Only a type, as the
169
+ * attributes are checked against the browser in debug mode.
170
+ */
171
+ export type AttributeName =
172
+ | 'abbr'
173
+ | 'accept'
174
+ | 'acceptCharset'
175
+ | 'accessKey'
176
+ | 'action'
177
+ | 'adAuctionHeaders'
178
+ | 'align'
179
+ | 'aLink'
180
+ | 'allow'
181
+ | 'allowFullscreen'
182
+ | 'allowFullScreen'
183
+ | 'allowPaymentRequest'
184
+ | 'alt'
185
+ | 'archive'
186
+ | 'ariaActions'
187
+ | 'ariaActiveDescendant'
188
+ | 'ariaAtomic'
189
+ | 'ariaAutoComplete'
190
+ | 'ariaBrailleLabel'
191
+ | 'ariaBrailleRoleDescription'
192
+ | 'ariaBusy'
193
+ | 'ariaChecked'
194
+ | 'ariaColCount'
195
+ | 'ariaColIndex'
196
+ | 'ariaColIndexText'
197
+ | 'ariaColSpan'
198
+ | 'ariaControls'
199
+ | 'ariaCurrent'
200
+ | 'ariaDescribedBy'
201
+ | 'ariaDescription'
202
+ | 'ariaDetails'
203
+ | 'ariaDisabled'
204
+ | 'ariaErrorMessage'
205
+ | 'ariaExpanded'
206
+ | 'ariaFlowTo'
207
+ | 'ariaHasPopup'
208
+ | 'ariaHidden'
209
+ | 'ariaInvalid'
210
+ | 'ariaKeyShortcuts'
211
+ | 'ariaLabel'
212
+ | 'ariaLabelledBy'
213
+ | 'ariaLabelLedBy'
214
+ | 'ariaLevel'
215
+ | 'ariaLive'
216
+ | 'ariaModal'
217
+ | 'ariaMultiLine'
218
+ | 'ariaMultiSelectable'
219
+ | 'ariaOrientation'
220
+ | 'ariaOwns'
221
+ | 'ariaPlaceholder'
222
+ | 'ariaPosInSet'
223
+ | 'ariaPressed'
224
+ | 'ariaReadOnly'
225
+ | 'ariaRelevant'
226
+ | 'ariaRequired'
227
+ | 'ariaRoleDescription'
228
+ | 'ariaRowCount'
229
+ | 'ariaRowIndex'
230
+ | 'ariaRowIndexText'
231
+ | 'ariaRowSpan'
232
+ | 'ariaSelected'
233
+ | 'ariaSetSize'
234
+ | 'ariaSort'
235
+ | 'ariaValueMax'
236
+ | 'ariaValueMin'
237
+ | 'ariaValueNow'
238
+ | 'ariaValueText'
239
+ | 'as'
240
+ | 'async'
241
+ | 'attributionSrc'
242
+ | 'autoCapitalize'
243
+ | 'autoComplete'
244
+ | 'autoCorrect'
245
+ | 'autoFocus'
246
+ | 'autoPlay'
247
+ | 'axis'
248
+ | 'background'
249
+ | 'bgColor'
250
+ | 'blocking'
251
+ | 'border'
252
+ | 'browsingTopics'
253
+ | 'cellPadding'
254
+ | 'cellSpacing'
255
+ | 'charset'
256
+ | 'checked'
257
+ | 'cite'
258
+ | 'clear'
259
+ | 'closedBy'
260
+ | 'code'
261
+ | 'codeBase'
262
+ | 'codeType'
263
+ | 'color'
264
+ | 'cols'
265
+ | 'colSpan'
266
+ | 'command'
267
+ | 'commandFor'
268
+ | 'compact'
269
+ | 'content'
270
+ | 'contentEditable'
271
+ | 'controls'
272
+ | 'coords'
273
+ | 'credentialless'
274
+ | 'crossOrigin'
275
+ | 'csp'
276
+ | 'data'
277
+ | 'dateTime'
278
+ | 'declare'
279
+ | 'decoding'
280
+ | 'default'
281
+ | 'defer'
282
+ | 'dir'
283
+ | 'dirName'
284
+ | 'disabled'
285
+ | 'disablePictureInPicture'
286
+ | 'disableRemotePlayback'
287
+ | 'download'
288
+ | 'draggable'
289
+ | 'elementTiming'
290
+ | 'encoding'
291
+ | 'encType'
292
+ | 'enterKeyHint'
293
+ | 'event'
294
+ | 'fetchPriority'
295
+ | 'focusGroupStart'
296
+ | 'for'
297
+ | 'form'
298
+ | 'formAction'
299
+ | 'formEnctype'
300
+ | 'formMethod'
301
+ | 'formNoValidate'
302
+ | 'formTarget'
303
+ | 'frame'
304
+ | 'frameBorder'
305
+ | 'headers'
306
+ | 'height'
307
+ | 'hidden'
308
+ | 'high'
309
+ | 'href'
310
+ | 'hrefLang'
311
+ | 'hspace'
312
+ | 'httpEquiv'
313
+ | 'id'
314
+ | 'imageSizes'
315
+ | 'imageSrcset'
316
+ | 'incremental'
317
+ | 'inert'
318
+ | 'inputMode'
319
+ | 'integrity'
320
+ | 'interestFor'
321
+ | 'is'
322
+ | 'isMap'
323
+ | 'itemProp'
324
+ | 'kind'
325
+ | 'label'
326
+ | 'lang'
327
+ | 'link'
328
+ | 'list'
329
+ | 'loading'
330
+ | 'longDesc'
331
+ | 'loop'
332
+ | 'low'
333
+ | 'lowSrc'
334
+ | 'marginHeight'
335
+ | 'marginWidth'
336
+ | 'max'
337
+ | 'maxLength'
338
+ | 'media'
339
+ | 'method'
340
+ | 'min'
341
+ | 'minLength'
342
+ | 'multiple'
343
+ | 'muted'
344
+ | 'name'
345
+ | 'noHref'
346
+ | 'noModule'
347
+ | 'nonce'
348
+ | 'noShade'
349
+ | 'noValidate'
350
+ | 'noWrap'
351
+ | 'open'
352
+ | 'optimum'
353
+ | 'part'
354
+ | 'pattern'
355
+ | 'ping'
356
+ | 'placeholder'
357
+ | 'playsInline'
358
+ | 'popover'
359
+ | 'popoverTarget'
360
+ | 'popoverTargetAction'
361
+ | 'poster'
362
+ | 'preload'
363
+ | 'privateToken'
364
+ | 'readOnly'
365
+ | 'referrerPolicy'
366
+ | 'rel'
367
+ | 'required'
368
+ | 'rev'
369
+ | 'reversed'
370
+ | 'role'
371
+ | 'rows'
372
+ | 'rowSpan'
373
+ | 'rules'
374
+ | 'sandbox'
375
+ | 'scheme'
376
+ | 'scope'
377
+ | 'scrolling'
378
+ | 'selected'
379
+ | 'shadowRootClonable'
380
+ | 'shadowRootCustomElementRegistry'
381
+ | 'shadowRootDelegatesFocus'
382
+ | 'shadowRootMode'
383
+ | 'shadowRootReferenceTarget'
384
+ | 'shadowRootSerializable'
385
+ | 'shadowRootSlotAssignment'
386
+ | 'shape'
387
+ | 'size'
388
+ | 'sizes'
389
+ | 'slot'
390
+ | 'span'
391
+ | 'spellCheck'
392
+ | 'src'
393
+ | 'srcDoc'
394
+ | 'srcLang'
395
+ | 'srcSet'
396
+ | 'standby'
397
+ | 'start'
398
+ | 'step'
399
+ | 'summary'
400
+ | 'tabIndex'
401
+ | 'target'
402
+ | 'title'
403
+ | 'translate'
404
+ | 'type'
405
+ | 'useMap'
406
+ | 'vAlign'
407
+ | 'value'
408
+ | 'version'
409
+ | 'virtualKeyboardPolicy'
410
+ | 'vLink'
411
+ | 'vspace'
412
+ | 'webkitDirectory'
413
+ | 'width'
414
+ | 'wrap'
415
+ | 'writingSuggestions'
416
+
417
+ /* Attributes set by other names than those of the element properties. */
418
+ const ATTRIBUTE_PROPERTIES: Record<string, string> = { for: 'htmlFor' }
419
+
420
+ /* Attributes valid on all elements, not reflected as element properties. */
421
+ const UNREFLECTED_ATTRIBUTES = new Set(['ariaOwns', 'is', 'itemProp'])
422
+
423
+ /* The lower-cased property names of the elements, read when first needed. */
424
+ const elementProperties = new Map<string, Set<string>>()
425
+
426
+ const getElementProperties = (tagName: string) => {
427
+ let properties = elementProperties.get(tagName)
428
+ if (!properties) {
429
+ properties = new Set()
430
+ // the attributes are reflected as properties, in the prototype chain
431
+ for (const name in document.createElement(tagName)) {
432
+ properties.add(name.toLowerCase())
433
+ }
434
+ elementProperties.set(tagName, properties)
435
+ }
436
+ return properties
437
+ }
438
+
439
+ /*
440
+ * Events without handler properties, e.g. oncompositionend, or with them
441
+ * only on touch devices, e.g. ontouchstart.
442
+ */
443
+ const EVENTS_WITHOUT_HANDLERS = new Set([
444
+ 'compositionstart',
445
+ 'compositionupdate',
446
+ 'compositionend',
447
+ 'touchstart',
448
+ 'touchmove',
449
+ 'touchend',
450
+ 'touchcancel',
451
+ ])
452
+
453
+ /* The style properties of the browser, read when first needed. */
454
+ let supportedStyles: Set<string> | null = null
455
+ const getSupportedStyles = () =>
456
+ (supportedStyles ??= new Set(Object.keys(document.documentElement.style)))
457
+
458
+ const Browser = {
459
+ /**
460
+ * Checks if the browser supports the attribute on the element, as
461
+ * a property of the element in any letter case, e.g. tabIndex. Meant
462
+ * for debug mode, as the check creates an element of each tag name.
463
+ */
464
+ isAttributeSupported(this: void, key: string, tagName: string): boolean {
465
+ if (UNREFLECTED_ATTRIBUTES.has(key)) {
466
+ return true
467
+ }
468
+ const properties = getElementProperties(tagName)
469
+ const name = (ATTRIBUTE_PROPERTIES[key] ?? key).toLowerCase()
470
+ // the ARIA relations are reflected as the related elements
471
+ return (
472
+ properties.has(name) ||
473
+ properties.has(`${name}element`) ||
474
+ properties.has(`${name}elements`)
475
+ )
476
+ },
477
+
478
+ /**
479
+ * Checks if the browser supports the event of the listener, e.g. onClick,
480
+ * on the element, as its handler property, e.g. onclick. Meant for debug
481
+ * mode, as the check creates an element of each tag name.
482
+ */
483
+ isEventSupported(this: void, key: string, tagName: string): boolean {
484
+ const event = getEventName(key)
485
+ return (
486
+ EVENTS_WITHOUT_HANDLERS.has(event) ||
487
+ getElementProperties(tagName).has(`on${event}`)
488
+ )
489
+ },
490
+
491
+ isStyleSupported(this: void, key: string): boolean {
492
+ return key.startsWith('--') || getSupportedStyles().has(key)
493
+ },
494
+ }
495
+
496
+ /*
497
+ * The filter functions of Chromium 153, in camel case, e.g. dropShadow for
498
+ * drop-shadow in CSS. Only a type, as the functions are checked against
499
+ * the browser in debug mode.
500
+ */
501
+ export type FilterName =
502
+ | 'blur'
503
+ | 'brightness'
504
+ | 'contrast'
505
+ | 'dropShadow'
506
+ | 'grayscale'
507
+ | 'hueRotate'
508
+ | 'invert'
509
+ | 'opacity'
510
+ | 'saturate'
511
+ | 'sepia'
512
+ | 'url'
513
+
514
+ /* The transform functions of Chromium 153, as named in CSS, e.g. translateX. */
515
+ export type TransformName =
516
+ | 'matrix'
517
+ | 'matrix3d'
518
+ | 'translate'
519
+ | 'translate3d'
520
+ | 'translateX'
521
+ | 'translateY'
522
+ | 'translateZ'
523
+ | 'scale'
524
+ | 'scale3d'
525
+ | 'scaleX'
526
+ | 'scaleY'
527
+ | 'scaleZ'
528
+ | 'rotate'
529
+ | 'rotate3d'
530
+ | 'rotateX'
531
+ | 'rotateY'
532
+ | 'rotateZ'
533
+ | 'skew'
534
+ | 'skewX'
535
+ | 'skewY'
536
+ | 'perspective'
537
+
538
+ export default Browser
@@ -0,0 +1,77 @@
1
+ import { invariant } from './utils.js'
2
+ import type VirtualElement from './virtual-element.js'
3
+ import type VirtualNode from './virtual-node.js'
4
+ import type WebComponent from './web-component.js'
5
+
6
+ /*
7
+ * A node with child nodes: an element, or a Web Component
8
+ * with the child nodes rendered in its light DOM.
9
+ */
10
+ export type ParentVirtualNode = VirtualElement | WebComponent
11
+
12
+ /*
13
+ * Operations on the child nodes of the parent nodes, which store them
14
+ * as `children` of an element or `childNodes` of a Web Component.
15
+ */
16
+ const ChildNodes = {
17
+ set(parent: ParentVirtualNode, nodes: VirtualNode[]) {
18
+ parent.childNodes = nodes
19
+ for (const child of nodes) {
20
+ parent.ref.appendChild(child.ref)
21
+ }
22
+ },
23
+
24
+ insert(parent: ParentVirtualNode, child: VirtualNode, index?: number) {
25
+ const nodes = parent.childNodes ?? []
26
+ if (index === undefined) {
27
+ index = nodes.length
28
+ }
29
+ const nextChild = nodes[index]
30
+ nodes.splice(index, 0, child)
31
+ parent.childNodes = nodes
32
+ parent.ref.insertBefore(child.ref, (nextChild && nextChild.ref) || null)
33
+ child.parentNode = parent
34
+ },
35
+
36
+ replace(parent: ParentVirtualNode, child: VirtualNode, node: VirtualNode) {
37
+ const nodes = parent.childNodes ?? []
38
+ const index = nodes.indexOf(child)
39
+ invariant(index >= 0, 'Specified node is not a child of this element!')
40
+ nodes.splice(index, 1, node)
41
+ child.parentNode = null
42
+ node.parentNode = parent
43
+ child.ref.replaceWith(node.ref)
44
+ },
45
+
46
+ move(
47
+ parent: ParentVirtualNode,
48
+ child: VirtualNode,
49
+ from: number,
50
+ to: number,
51
+ ) {
52
+ const nodes = parent.childNodes ?? []
53
+ invariant(
54
+ nodes[from] === child,
55
+ 'Specified node is not a child of this element!',
56
+ )
57
+ nodes.splice(from, 1)
58
+ nodes.splice(to, 0, child)
59
+ // the DOM position is taken from the virtual children, as element
60
+ // children would skip the text and comment nodes
61
+ const nextChild = nodes[to + 1]
62
+ parent.ref.insertBefore(child.ref, nextChild ? nextChild.ref : null)
63
+ },
64
+
65
+ remove(parent: ParentVirtualNode, child: VirtualNode) {
66
+ const nodes = parent.childNodes ?? []
67
+ const index = nodes.indexOf(child)
68
+ invariant(index >= 0, 'Specified node is not a child of this element!')
69
+ nodes.splice(index, 1)
70
+ if (!nodes.length) {
71
+ parent.childNodes = undefined
72
+ }
73
+ parent.ref.removeChild(child.ref)
74
+ },
75
+ }
76
+
77
+ export default ChildNodes
@@ -0,0 +1,24 @@
1
+ import type { CommentDescription } from './description.js'
2
+ import VirtualNode from './virtual-node.js'
3
+
4
+ class Comment extends VirtualNode {
5
+ static NodeType = 'comment'
6
+
7
+ declare description: CommentDescription
8
+ declare ref: globalThis.Comment
9
+
10
+ constructor(description: CommentDescription, parentNode?: VirtualNode) {
11
+ super(description, parentNode)
12
+ this.attachDOM()
13
+ }
14
+
15
+ get nodeType(): string {
16
+ return Comment.NodeType
17
+ }
18
+
19
+ attachDOM() {
20
+ this.ref = document.createComment(` ${this.description.text} `)
21
+ }
22
+ }
23
+
24
+ export default Comment