UNPKG

21.9 kBJavaScriptView Raw
1/*
2 Terminal Kit
3
4 Copyright (c) 2009 - 2020 Cédric Ronvel
5
6 The MIT License (MIT)
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all
16 copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25*/
26
27"use strict" ;
28
29
30
31const misc = require( '../misc.js' ) ;
32const string = require( 'string-kit' ) ;
33const NextGenEvents = require( 'nextgen-events' ) ;
34
35
36
37function Element( options = {} ) {
38 this.setInterruptible( true ) ;
39
40 this.parent = options.parent && options.parent.elementType ? options.parent : null ;
41 this.document = null ;
42 this.inlineTerm = options.inlineTerm || null ; // inline mode, with this terminal as output
43 this.strictInline = !! (
44 this.inlineTerm && this.strictInlineSupport
45 && ( options.strictInline || options.strictInline === undefined )
46 ) ;
47
48 this.outputDst = options.outputDst || ( options.parent && options.parent.inputDst ) ,
49 this.inputDst = null ;
50 this.label = options.label || '' ;
51 this.key = options.key || null ;
52
53 if ( this.value === undefined ) {
54 // Because it can be set already by the derivative class before calling Element (preprocessing of userland values)
55 this.value = options.value === undefined ? null : options.value ;
56 }
57
58 this.childId = options.childId === undefined ? null : options.childId ; // An ID given to this element by its parent, often the index in its children array
59 this.def = options.def || null ; // internal usage, store the original def object that created the item, if any...
60 this.hidden = !! options.hidden ; // hidden: not visible and no interaction possible with this element, it also affects children
61 this.disabled = !! options.disabled ; // disabled: mostly for user-input, the element is often grayed and unselectable, effect depends on the element's type
62
63 this.content = '' ;
64 this.contentHasMarkup = false ;
65 this.contentWidth = 0 ;
66
67 if ( this.setContent === Element.prototype.setContent ) {
68 this.setContent( options.content || '' , options.contentHasMarkup , true ) ;
69 }
70
71 this.meta = options.meta ; // associate data to the element for userland business logic
72
73 this.outputX = options.outputX || options.x || 0 ;
74 this.outputY = options.outputY || options.y || 0 ;
75 this.savedZIndex = this.zIndex = options.zIndex || options.z || 0 ;
76 this.interceptTempZIndex = !! options.interceptTempZIndex ; // intercept child .topZ()/.bottomZ()/.restoreZ()
77 this.outputWidth = options.outputWidth || options.width || ( this.strictInline ? this.inlineTerm.width : 1 ) ;
78 this.outputHeight = options.outputHeight || options.height || ( this.strictInline ? this.inlineTerm.height : 1 ) ;
79
80 this.savedCursorX = 0 ;
81 this.savedCursorY = 0 ;
82
83 this.hasFocus = false ;
84 this.children = [] ;
85 this.zChildren = [] ; // like children, but ordered by zIndex
86 //this.onKey = this.onKey.bind( this ) , writable: true } ,
87
88 // Children needs an inputDst, by default, everything is the same as for output (except for Container)
89 this.inputDst = this.outputDst ;
90 this.inputX = this.outputX ;
91 this.inputY = this.outputY ;
92 this.inputWidth = this.outputWidth ;
93 this.inputHeight = this.outputHeight ;
94
95 if ( this.parent ) { this.parent.attach( this , options.id ) ; }
96
97 if ( options.shortcuts && this.document ) {
98 if ( Array.isArray( options.shortcuts ) ) { this.document.createShortcuts( this , ... options.shortcuts ) ; }
99 else { this.document.createShortcuts( this , options.shortcuts ) ; }
100 }
101}
102
103module.exports = Element ;
104
105Element.prototype = Object.create( NextGenEvents.prototype ) ;
106Element.prototype.constructor = Element ;
107Element.prototype.elementType = 'Element' ;
108
109
110
111// Destroy the element and all its children, detaching them and removing listeners
112Element.prototype.destroy = function( isSubDestroy = false , noDraw = false ) {
113 var i , iMax ;
114
115 // Destroy children first
116 for ( i = 0 , iMax = this.children.length ; i < iMax ; i ++ ) {
117 this.children[ i ].destroy( true ) ;
118 }
119
120 this.children.length = 0 ;
121 this.zChildren.length = 0 ;
122 this.document.removeElementShortcuts( this ) ;
123
124 if ( ! isSubDestroy ) {
125 this.detach( noDraw ) ;
126 }
127 else {
128 delete this.document.elements[ this.id ] ;
129 this.id = null ;
130 this.parent = null ;
131 this.document = null ;
132 }
133} ;
134
135
136
137// Clear the Element, destroy all children
138Element.prototype.clear = function() {
139 var i , iMax ;
140
141 // Destroy children first
142 for ( i = 0 , iMax = this.children.length ; i < iMax ; i ++ ) {
143 this.children[ i ].destroy( true ) ;
144 }
145
146 this.children.length = 0 ;
147 this.zChildren.length = 0 ;
148 this.draw() ;
149} ;
150
151
152
153Element.prototype.attach = function( child , id ) {
154 // Insert it if it is not already a child
155 if ( this.children.indexOf( child ) === -1 ) {
156 child.parent = this ;
157 this.children.push( child ) ;
158 this.zInsert( child ) ;
159 //this.zSort() ;
160
161 //this.document.assignId( this , options.id ) ;
162
163 // Re-assign the child's outputDst to this inputDst
164 child.outputDst = this.inputDst ;
165 if ( ! child.inputDst ) { child.inputDst = child.outputDst ; }
166
167 if ( this.document !== child.document ) {
168 child.recursiveFixAttachment( this.document , id ) ;
169 }
170 }
171
172 // /!\ Draw? /!\
173
174 return this ;
175} ;
176
177
178
179Element.prototype.attachTo = function( parent , id ) {
180 if ( parent.elementType ) { parent.attach( this , id ) ; }
181 return this ;
182} ;
183
184
185
186Element.prototype.recursiveFixAttachment = function( document , id = this.id ) {
187 var i , iMax ;
188
189 // Can be null when in inline mode, or when detaching
190 if ( document ) { document.assignId( this , id ) ; }
191 else if ( this.document ) { this.document.unassignId( this , this.id ) ; } // force actual id here
192 else { this.id = null ; }
193
194 this.document = document || null ;
195
196 if ( this.parent ) {
197 // Re-assign the outputDst to the parent's inputDst
198 this.outputDst = this.parent.inputDst ;
199 if ( ! this.inputDst ) { this.inputDst = this.outputDst ; }
200 }
201
202 for ( i = 0 , iMax = this.children.length ; i < iMax ; i ++ ) {
203 //console.error( ">>>" , i , iMax ) ;
204 this.children[ i ].recursiveFixAttachment( document ) ;
205 }
206} ;
207
208
209
210
211Element.prototype.detach = function( noDraw = false ) {
212 var index , parent = this.parent ;
213
214 // Already detached
215 if ( ! parent ) { return ; }
216
217 index = parent.children.indexOf( this ) ;
218 if ( index >= 0 ) { parent.children.splice( index , 1 ) ; }
219
220 index = parent.zChildren.indexOf( this ) ;
221 if ( index >= 0 ) { parent.zChildren.splice( index , 1 ) ; }
222
223 delete this.document.elements[ this.id ] ;
224 this.parent = null ;
225 this.recursiveFixAttachment( null ) ;
226
227 // Redraw
228 if ( ! noDraw ) {
229 // /!\ Draw parent should work, but not always /!\
230 //parent.draw() ;
231 parent.document.draw() ;
232 }
233
234 return this ;
235} ;
236
237
238
239// Sort zChildren, only necessary when a child zIndex changed
240Element.prototype.zSort = function() {
241 this.zChildren.sort( ( a , b ) => a.zIndex - b.zIndex ) ;
242} ;
243
244
245
246// Insert a child into the zChildren array, shift all greater zIndex to the left
247// Used this instead of .push() and .zSort()
248Element.prototype.zInsert = function( child ) {
249 var current ,
250 i = this.zChildren.length ;
251
252 while ( i -- ) {
253 current = this.zChildren[ i ] ;
254 if ( child.zIndex >= current.zIndex ) {
255 this.zChildren[ i + 1 ] = child ;
256 return ;
257 }
258
259 this.zChildren[ i + 1 ] = current ;
260 }
261
262 this.zChildren[ 0 ] = child ;
263} ;
264
265
266
267// Change zIndex and call parent.zSort() immediately
268Element.prototype.updateZ = Element.prototype.updateZIndex = function( z ) {
269 this.savedZIndex = this.zIndex = z ;
270 this.parent.zSort() ;
271} ;
272
273
274
275// Change zIndex to make it on top of all siblings
276Element.prototype.topZ = function() {
277 if ( this.parent.interceptTempZIndex ) { return this.parent.topZ() ; }
278
279 if ( ! this.parent.zChildren.length ) { return ; }
280 this.zIndex = this.parent.zChildren[ this.parent.zChildren.length - 1 ].zIndex + 1 ;
281 this.parent.zSort() ;
282} ;
283
284
285
286// Change zIndex to make it on bottom of all siblings
287Element.prototype.bottomZ = function() {
288 if ( this.parent.interceptTempZIndex ) { return this.parent.bottomZ() ; }
289
290 if ( ! this.parent.zChildren.length ) { return ; }
291 this.zIndex = this.parent.zChildren[ 0 ].zIndex - 1 ;
292 this.parent.zSort() ;
293} ;
294
295
296
297// Change zIndex to make it on bottom of all siblings
298Element.prototype.restoreZ = function() {
299 if ( this.parent.interceptTempZIndex ) { return this.parent.restoreZ() ; }
300
301 this.zIndex = this.savedZIndex ;
302 this.parent.zSort() ;
303} ;
304
305
306
307Element.computeContentWidth = ( content , hasMarkup ) => {
308 if ( Array.isArray( content ) ) {
309 if ( hasMarkup ) {
310 return Math.max( ... content.map( line => misc.markupWidth( line ) ) ) ;
311 }
312
313 return Math.max( ... content.map( line => string.unicode.width( line ) ) ) ;
314 }
315 else if ( hasMarkup ) {
316 return misc.markupWidth( content ) ;
317 }
318
319 return string.unicode.width( content ) ;
320} ;
321
322Element.truncateContent = ( content , maxWidth , hasMarkup ) =>
323 hasMarkup ? misc.truncateMarkupString( content , maxWidth ) : string.unicode.truncateWidth( content , maxWidth ) ;
324
325Element.wordwrapContent = // <-- DEPRECATED
326Element.wordWrapContent = ( content , width , hasMarkup ) =>
327 hasMarkup ? misc.wordWrapMarkup( content , width ) : string.wordwrap( content , { width , fill: true , noJoin: true } ) ;
328
329
330
331Element.prototype.setContent = function( content , hasMarkup , dontDraw ) {
332 this.content = content ;
333 this.contentHasMarkup = !! hasMarkup ;
334 this.contentWidth = Element.computeContentWidth( content , this.contentHasMarkup ) ;
335
336 if ( ! dontDraw ) { this.redraw() ; }
337} ;
338
339
340
341Element.prototype.isAncestorOf = function( element ) {
342 var currentElement = element ;
343
344 for ( ;; ) {
345 if ( currentElement === this ) {
346 // Self found: ancestor match!
347 return true ;
348 }
349 else if ( ! currentElement.parent ) {
350 // The element is either detached or attached to another parent element
351 return false ;
352 }
353 else if ( currentElement.parent.children.indexOf( currentElement ) === -1 ) {
354 // Detached but still retain a ref to its parent.
355 // It's probably a bug, so we will remove that link now.
356 currentElement.parent = null ;
357 return false ;
358 }
359
360 currentElement = currentElement.parent ;
361 }
362} ;
363
364
365
366Element.prototype.getParentContainer = function() {
367 var currentElement = this ;
368
369 for ( ;; ) {
370 if ( ! currentElement.parent ) { return null ; }
371 if ( currentElement.parent.isContainer ) { return currentElement.parent ; }
372
373 currentElement = currentElement.parent ;
374 }
375} ;
376
377
378
379// Internal: get the index of the direct child that have the focus or have a descendant having the focus
380Element.prototype.getFocusBranchIndex = function() {
381 var index , currentElement ;
382
383 if ( ! this.document.focusElement ) { return null ; }
384
385 currentElement = this.document.focusElement ;
386
387 for ( ;; ) {
388 if ( currentElement === this ) {
389 // Self found: ancestor match!
390 return null ;
391 }
392 else if ( ! currentElement.parent ) {
393 // The element is either detached or attached to another parent element
394 return null ;
395 }
396
397 if ( currentElement.parent === this ) {
398 index = currentElement.parent.children.indexOf( currentElement ) ;
399
400 if ( index === -1 ) {
401 // Detached but still retain a ref to its parent.
402 // It's probably a bug, so we will remove that link now.
403 currentElement.parent = null ;
404 return null ;
405 }
406
407 return index ;
408 }
409
410 currentElement = currentElement.parent ;
411 }
412} ;
413
414
415
416Element.prototype.focusNextChild = function( loop = true ) {
417 var index , startingIndex , focusAware ;
418
419 if ( ! this.children.length || ! this.document ) { return null ; }
420
421 //if ( ! this.document.focusElement || ( index = this.children.indexOf( this.document.focusElement ) ) === -1 )
422 if ( ! this.document.focusElement || ( index = this.getFocusBranchIndex() ) === null ) {
423 index = this.children.length - 1 ;
424 }
425
426 startingIndex = index ;
427
428 for ( ;; ) {
429 index ++ ;
430 if ( index >= this.children.length ) {
431 if ( loop ) { index = 0 ; }
432 else { index = this.children.length - 1 ; break ; }
433 }
434
435 focusAware = this.document.giveFocusTo_( this.children[ index ] , 'cycle' ) ;
436
437 // Exit if the focus was given to a focus-aware element or if we have done a full loop already
438 if ( focusAware || startingIndex === index ) { break ; }
439 }
440
441 return this.children[ index ] ;
442} ;
443
444
445
446Element.prototype.focusPreviousChild = function( loop = true ) {
447 var index , startingIndex , focusAware ;
448
449 if ( ! this.children.length || ! this.document ) { return null ; }
450
451 //if ( ! this.document.focusElement || ( index = this.children.indexOf( this.document.focusElement ) ) === -1 )
452 if ( ! this.document.focusElement || ( index = this.getFocusBranchIndex() ) === null ) {
453 index = 0 ;
454 }
455
456 startingIndex = index ;
457
458 for ( ;; ) {
459 index -- ;
460 if ( index < 0 ) {
461 if ( loop ) { index = this.children.length - 1 ; }
462 else { index = 0 ; break ; }
463 }
464
465 focusAware = this.document.giveFocusTo_( this.children[ index ] , 'cycle' ) ;
466
467 // Exit if the focus was given to a focus-aware element or if we have done a full loop already
468 if ( focusAware || startingIndex === index ) { break ; }
469 }
470
471 return this.children[ index ] ;
472} ;
473
474
475
476// Get all child element matching a x,y coordinate relative to the current element
477Element.prototype.childrenAt = function( x , y , filter = null , matches = [] ) {
478 var i , current ;
479
480 // Search children, order by descending zIndex, because we want the top element first
481 i = this.zChildren.length ;
482 while ( i -- ) {
483 current = this.zChildren[ i ] ;
484
485 // Filter out hidden element now
486 if ( current.hidden ) { continue ; }
487
488 //console.error( 'Checking: ' , x , y , current.id , current.outputX , current.outputY , current.outputWidth , current.outputHeight ) ;
489
490 if (
491 x >= current.outputX && x <= current.outputX + current.outputWidth - 1 &&
492 y >= current.outputY && y <= current.outputY + current.outputHeight - 1
493 ) {
494 // Bounding box match!
495
496 // Check and add children of children first (depth-first)
497 if ( current.isContainer ) {
498 current.childrenAt( x - current.outputX , y - current.outputY , filter , matches ) ;
499 }
500 else {
501 current.childrenAt( x , y , filter , matches ) ;
502 }
503
504 if ( ! filter || filter( current ) ) {
505 matches.push( { element: current , x: x - current.outputX , y: y - current.outputY } ) ;
506 }
507 }
508 else if ( ! current.isContainer ) {
509 // If it is not a container, give a chance to its children to get selected
510 current.childrenAt( x , y , filter , matches ) ;
511 }
512 }
513
514 return matches ;
515} ;
516
517
518
519Element.prototype.saveCursor = function() {
520 if ( this.inputDst ) {
521 this.savedCursorX = this.inputDst.cx ;
522 this.savedCursorY = this.inputDst.cy ;
523 }
524 else if ( this.outputDst ) {
525 this.savedCursorX = this.outputDst.cx ;
526 this.savedCursorY = this.outputDst.cy ;
527 }
528
529 return this ;
530} ;
531
532
533
534Element.prototype.restoreCursor = function() {
535 if ( this.inputDst ) {
536 this.inputDst.cx = this.savedCursorX ;
537 this.inputDst.cy = this.savedCursorY ;
538 this.inputDst.drawCursor() ;
539 }
540 else if ( this.outputDst ) {
541 this.outputDst.cx = this.savedCursorX ;
542 this.outputDst.cy = this.savedCursorY ;
543 this.outputDst.drawCursor() ;
544 }
545
546 return this ;
547} ;
548
549
550
551Element.prototype.draw = function() {
552 if ( ! this.document || this.hidden ) { return this ; }
553
554 if ( ! this.strictInline ) { this.saveCursor() ; }
555 this.descendantDraw() ;
556 this.ascendantDraw() ;
557 if ( ! this.strictInline ) { this.drawCursor() ; }
558 return this ;
559} ;
560
561
562
563// .draw() is used when drawing the current Element is enough: the Element has not moved, and has not been resized.
564// If it has, then it is necessary to draw the closest ancestor which is a container.
565Element.prototype.redraw = function() {
566 if ( ! this.document || this.hidden ) { return this ; }
567
568 var container = this.getParentContainer() ;
569
570 //console.error( "parentContainer:" , container ) ;
571 if ( ! container ) { this.draw() ; }
572 else { container.draw() ; }
573
574 return this ;
575} ;
576
577
578
579// Draw all the children
580Element.prototype.descendantDraw = function( isSubcall ) {
581 var i , iMax ;
582
583 if ( this.hidden ) { return this ; }
584
585 if ( this.preDrawSelf ) {
586 //console.error( 'preDrawSelf: ' , this.elementType , this.id ) ;
587 this.preDrawSelf( ! isSubcall ) ;
588 }
589
590 // Draw children, order by ascending zIndex
591 for ( i = 0 , iMax = this.zChildren.length ; i < iMax ; i ++ ) {
592 this.zChildren[ i ].descendantDraw( true ) ;
593 }
594
595 if ( isSubcall && this.postDrawSelf ) {
596 //console.error( 'postDrawSelf: ' , this.elementType , this.id ) ;
597 this.postDrawSelf( ! isSubcall ) ;
598 }
599
600 return this ;
601} ;
602
603
604
605// Post-draw from the current element through all the ancestor chain
606Element.prototype.ascendantDraw = function() {
607 //console.error( '\nascendantDraw: ' , this.elementType , this.id ) ;
608 var currentElement ;
609
610 if ( this.postDrawSelf && ! this.hidden ) {
611 //console.error( 'postDrawSelf: ' , this.elementType , this.id ) ;
612 this.postDrawSelf( true ) ;
613 }
614
615 currentElement = this ;
616
617 while ( currentElement.parent && currentElement.outputDst !== currentElement.document.outputDst ) {
618 currentElement = currentElement.parent ;
619
620 if ( currentElement.outputDst !== currentElement.inputDst && currentElement.postDrawSelf && ! currentElement.hidden ) {
621 //console.error( 'postDrawSelf: ' , currentElement.elementType , currentElement.id ) ;
622 currentElement.postDrawSelf( false ) ;
623 }
624 }
625
626 return this ;
627} ;
628
629
630
631// Draw cursor from the current element through all the ancestor chain
632Element.prototype.drawCursor = function() {
633 var currentElement ;
634
635 if ( this.drawSelfCursor && ! this.hidden ) {
636 this.drawSelfCursor( true ) ;
637 }
638
639 currentElement = this ;
640
641 while ( currentElement.outputDst !== currentElement.document.outputDst && currentElement.parent ) {
642 currentElement = currentElement.parent ;
643
644 if ( currentElement.drawSelfCursor && ! currentElement.hidden ) {
645 currentElement.drawSelfCursor( false ) ;
646 }
647 }
648
649 return this ;
650} ;
651
652
653
654// For inline widget, having eventually a document just for him, that fit its own size
655Element.createInline = async function( term , Type , options ) {
656 // Clone options if necessary
657 options = ! options ? {} : options.internal ? options : Object.create( options ) ;
658 options.internal = true ;
659
660 options.inlineTerm = term ;
661 //options.outputDst = term ;
662 //options.eventSource = term ;
663
664 var cursorPosition , position = {
665 x: options.outputX || options.x ,
666 y: options.outputY || options.y
667 } ;
668
669 delete options.x ;
670 delete options.y ;
671 delete options.outputX ;
672 delete options.outputY ;
673
674 var element = new Type( options ) ;
675
676 if ( position.x === undefined || position.y === undefined ) {
677 if ( element.strictInline ) {
678 // We do not want any asyncness for pure inline elements, and since we draw in inline mode, we don't care about it...
679 // ... BUT we need a position anyway for the clipping purpose! It can't be 0 since we draw on the terminal and top-left is (1,1).
680 position.x = position.y = 1 ;
681 }
682 else {
683 cursorPosition = await term.getCursorLocation() ;
684
685 if ( position.x === undefined ) {
686 position.x = cursorPosition.x ;
687
688 if ( cursorPosition.x > 1 && element.inlineNewLine ) {
689 position.x = 1 ;
690 if ( position.y === undefined ) { position.y = cursorPosition.y + 1 ; }
691 }
692 }
693
694 if ( position.y === undefined ) { position.y = cursorPosition.y ; }
695 }
696 }
697
698 if ( ! element.strictInline ) {
699 let scrollY = position.y + element.outputHeight - term.height ;
700 //console.error( "INLINE -- element.outputHeight" , element.outputHeight ) ;
701 //console.error( "INLINE -- element.outputY" , element.outputY ) ;
702 //console.error( "INLINE -- scrollY" , scrollY ) ;
703
704 if ( scrollY > 0 ) {
705 term.scrollUp( scrollY ) ;
706 position.y -= scrollY ;
707 }
708 }
709
710 var documentOptions = {
711 internal: true ,
712 inlineTerm: term ,
713 strictInline: element.strictInline ,
714 outputX: position.x ,
715 outputY: position.y ,
716 outputWidth: element.outputWidth ,
717 outputHeight: element.outputHeight ,
718 outputDst: term ,
719 eventSource: term ,
720 noDraw: true
721 } ;
722
723 var document = new Document( documentOptions ) ;
724
725 document.attach( element ) ;
726
727 // Should probably resize the container
728 element.on( 'resize' , () => { throw new Error( 'not coded!' ) ; } ) ;
729
730 element.draw() ;
731
732 return element ;
733} ;
734
735
736
737// Should be redefined
738Element.prototype.isContainer = false ; // boolean, true if it's a container, having a different inputDst and outputDst and local coords
739Element.prototype.noChildFocus = false ; // boolean, true if the focus should not be transmitted to children of this Element
740Element.prototype.computeBoundingBoxes = null ; // function, bounding boxes for elements that can be drawn
741Element.prototype.preDrawSelf = null ; // function, things to draw for the element before drawing its children
742Element.prototype.postDrawSelf = null ; // function, things to draw for the element after drawing its children
743Element.prototype.drawSelfCursor = null ; // function, draw the element cursor
744Element.prototype.getValue = () => null ; // function, get the value of the element if any...
745Element.prototype.setValue = () => undefined ; // function, set the value of the element if any...
746Element.prototype.strictInlineSupport = false ; // No support for strictInline mode by default
747
748const Document = require( './Document.js' ) ;
749