UNPKG

terminal-kit

Version:

256 colors, keys and mouse, input field, progress bars, screen buffer (including 32-bit composition and image loading), text buffer, and many more... Whether you just need colors and styles, build a simple interactive command line tool or a complexe termi

958 lines (689 loc) 29.3 kB
/* Terminal Kit Copyright (c) 2009 - 2022 Cédric Ronvel The MIT License (MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ "use strict" ; const misc = require( '../misc.js' ) ; const string = require( 'string-kit' ) ; const NextGenEvents = require( 'nextgen-events' ) ; // Avoid requiring Document at top-level, it could cause circular require troubles //const Document = require( './Document.js' ) ; var autoId = 0 ; function Element( options = {} ) { this.setInterruptible( true ) ; this.uid = autoId ++ ; // Useful for debugging this.parent = options.parent && options.parent.elementType ? options.parent : null ; //console.error( "Creating " + this.elementType + " #" + this.uid + ( this.parent ? " (from parent " + this.parent.elementType + " #" + this.parent.uid + ")" : '' ) ) ; this.document = null ; this.destroyed = false ; // Event handler bindings this.onKey = this.onKey.bind( this ) ; this.inlineTerm = options.inlineTerm || null ; // inline mode, with this terminal as output this.strictInline = !! ( this.inlineTerm && this.strictInlineSupport && ( options.strictInline || options.strictInline === undefined ) ) ; this.restoreCursorAfterDraw = !! ( this.inlineTerm && this.inlineCursorRestoreAfterDraw && ! this.strictInline ) ; this.outputDst = options.outputDst || ( options.parent && options.parent.inputDst ) , this.inputDst = null ; this.label = options.label || '' ; this.key = options.key || null ; if ( this.value === undefined ) { // Because it can be set already by the derivative class before calling Element (preprocessing of userland values) this.value = options.value === undefined ? null : options.value ; } this.childId = options.childId === undefined ? null : options.childId ; // An ID given to this element by its parent, often the index in its children array this.def = options.def || null ; // internal usage, store the original def object that created the item, if any... this.hidden = !! options.hidden ; // hidden: not visible and no interaction possible with this element, it also affects children this.disabled = !! options.disabled ; // disabled: mostly for user-input, the element is often grayed and unselectable, effect depends on the element's type // Default value (ensure it's not already set) this.content = this.content ?? '' ; this.contentHasMarkup = this.contentHasMarkup ?? false ; this.contentWidth = this.contentWidth ?? 0 ; this.contentHeight = this.contentHeight ?? 0 ; if ( this.setContent === Element.prototype.setContent ) { this.setContent( options.content || '' , options.contentHasMarkup , true , true ) ; } this.meta = options.meta ; // associate data to the element for userland business logic this.autoWidth = + options.autoWidth || 0 ; this.autoHeight = + options.autoHeight || 0 ; this.outputX = options.outputX || options.x || 0 ; this.outputY = options.outputY || options.y || 0 ; this.savedZIndex = this.zIndex = options.zIndex || options.z || 0 ; this.interceptTempZIndex = !! options.interceptTempZIndex ; // intercept child .topZ()/.bottomZ()/.restoreZ() this.outputWidth = this.autoWidth && this.outputDst ? Math.round( this.outputDst.width * this.autoWidth ) : options.outputWidth ? options.outputWidth : options.width ? options.width : this.strictInline ? this.inlineTerm.width : 1 ; this.outputHeight = this.autoHeight && this.outputDst ? Math.round( this.outputDst.height * this.autoHeight ) : options.outputHeight ? options.outputHeight : options.height ? options.height : this.strictInline ? this.inlineTerm.height : 1 ; this.contentAdaptativeWidth = this.contentAdaptativeWidth ?? !! options.contentAdaptativeWidth ; this.contentAdaptativeHeight = this.contentAdaptativeHeight ?? !! options.contentAdaptativeHeight ; // Used by .updateDraw() this.needOuterDraw = false ; this.savedCursorX = 0 ; this.savedCursorY = 0 ; this.hasFocus = false ; this.children = [] ; this.zChildren = [] ; // like children, but ordered by zIndex // Children needs an inputDst, by default, everything is the same as for output (except for Container) this.inputDst = this.outputDst ; this.inputX = this.outputX ; this.inputY = this.outputY ; this.inputWidth = this.outputWidth ; this.inputHeight = this.outputHeight ; if ( this.parent ) { this.parent.attach( this , options.id ) ; } if ( options.shortcuts && this.document ) { if ( Array.isArray( options.shortcuts ) ) { this.document.createShortcuts( this , ... options.shortcuts ) ; } else { this.document.createShortcuts( this , options.shortcuts ) ; } } } module.exports = Element ; Element.prototype = Object.create( NextGenEvents.prototype ) ; Element.prototype.constructor = Element ; Element.prototype.elementType = 'Element' ; const termkit = require( '../termkit.js' ) ; // Destroy the element and all its children, detaching them and removing listeners Element.prototype.destroy = function( isSubDestroy = false , noDraw = false ) { if ( this.destroyed ) { return ; } //console.error( "Destroying" , this.elementType , this.uid , this.key ) ; var i , iMax , document = this.document ; // Destroy children first for ( i = 0 , iMax = this.children.length ; i < iMax ; i ++ ) { this.children[ i ].destroy( true ) ; } this.removeAllListeners() ; this.children.length = 0 ; this.zChildren.length = 0 ; this.document.removeElementShortcuts( this ) ; if ( ! isSubDestroy ) { this.detach( noDraw ) ; if ( this.inlineTerm && document !== this ) { document.destroy() ; } } else { delete this.document.elements[ this.id ] ; this.id = null ; this.parent = null ; this.document = null ; } this.destroyed = true ; } ; // User API Element.prototype.destroyNoRedraw = function() { return this.destroy( undefined , true ) ; } ; Element.inherit = function( Class , FromClass = Element ) { Class.prototype = Object.create( FromClass.prototype ) ; Class.prototype.constructor = Class ; Class.prototype.elementType = Class.name ; Class.prototype.userActions = Object.create( FromClass.prototype.userActions ) ; Class.prototype.userActions.__parent = FromClass.prototype.userActions ; } ; // Debug function Element.prototype.debugId = function() { return this.elementType + '#' + this.uid ; } ; Element.prototype.show = function( noDraw = false ) { if ( ! this.hidden ) { return this ; } this.hidden = false ; if ( ! noDraw ) { this.outerDraw() ; } return this ; } ; Element.prototype.hide = function( noDraw = false ) { if ( this.hidden ) { return this ; } this.hidden = true ; if ( ! noDraw ) { // .outerDraw() with the 'force' option on, because .outerDraw() does nothing if the element is hidden, but here we want to clear it from its parent this.outerDraw( true ) ; } return this ; } ; // Clear the Element, destroy all children Element.prototype.clear = function() { var i , iMax ; // Destroy children first for ( i = 0 , iMax = this.children.length ; i < iMax ; i ++ ) { this.children[ i ].destroy( true ) ; } this.children.length = 0 ; this.zChildren.length = 0 ; this.draw() ; } ; Element.prototype.attach = function( child , id ) { // Insert it if it is not already a child if ( this.children.indexOf( child ) === -1 ) { child.parent = this ; this.children.push( child ) ; this.zInsert( child ) ; //this.zSort() ; //this.document.assignId( this , options.id ) ; // Re-assign the child's outputDst to this inputDst child.outputDst = this.inputDst ; if ( ! child.inputDst ) { child.inputDst = child.outputDst ; } if ( this.document !== child.document ) { child.recursiveFixAttachment( this.document , id ) ; } } // /!\ Draw? /!\ return this ; } ; Element.prototype.attachTo = function( parent , id ) { if ( parent.elementType ) { parent.attach( this , id ) ; } return this ; } ; Element.prototype.recursiveFixAttachment = function( document , id = this.id ) { var i , iMax ; // Can be null when in inline mode, or when detaching if ( document ) { document.assignId( this , id ) ; } else if ( this.document ) { this.document.unassignId( this , this.id ) ; } // force actual id here else { this.id = null ; } this.document = document || null ; if ( this.parent ) { // Re-assign the outputDst to the parent's inputDst this.outputDst = this.parent.inputDst ; if ( ! this.inputDst ) { this.inputDst = this.outputDst ; } } for ( i = 0 , iMax = this.children.length ; i < iMax ; i ++ ) { this.children[ i ].recursiveFixAttachment( document ) ; } } ; Element.prototype.detach = function( noDraw = false ) { var index , parent = this.parent ; // Already detached if ( ! parent ) { return ; } index = parent.children.indexOf( this ) ; if ( index >= 0 ) { parent.children.splice( index , 1 ) ; } index = parent.zChildren.indexOf( this ) ; if ( index >= 0 ) { parent.zChildren.splice( index , 1 ) ; } delete this.document.elements[ this.id ] ; this.parent = null ; this.recursiveFixAttachment( null ) ; // Redraw if ( ! noDraw ) { // /!\ Draw parent should work, but not always /!\ //parent.draw() ; parent.document.draw() ; } return this ; } ; // Resize the element to its content Element.prototype.resizeToContent = function() { this.outputWidth = this.contentWidth ; this.outputHeight = this.contentHeight ; } ; // Sort zChildren, only necessary when a child zIndex changed Element.prototype.zSort = function() { this.zChildren.sort( ( a , b ) => a.zIndex - b.zIndex ) ; } ; // Insert a child into the zChildren array, shift all greater zIndex to the left // Use this instead of .push() and .zSort() Element.prototype.zInsert = function( child ) { var current , i = this.zChildren.length ; while ( i -- ) { current = this.zChildren[ i ] ; if ( child.zIndex >= current.zIndex ) { this.zChildren[ i + 1 ] = child ; return ; } this.zChildren[ i + 1 ] = current ; } this.zChildren[ 0 ] = child ; } ; // Change zIndex and call parent.zSort() immediately Element.prototype.updateZ = Element.prototype.updateZIndex = function( z ) { this.savedZIndex = this.zIndex = z ; this.parent.zSort() ; } ; // Change zIndex to make it on top of all siblings Element.prototype.topZ = function() { if ( this.parent.interceptTempZIndex ) { return this.parent.topZ() ; } if ( ! this.parent.zChildren.length ) { return ; } this.zIndex = this.parent.zChildren[ this.parent.zChildren.length - 1 ].zIndex + 1 ; this.parent.zSort() ; } ; // Change zIndex to make it on bottom of all siblings Element.prototype.bottomZ = function() { if ( this.parent.interceptTempZIndex ) { return this.parent.bottomZ() ; } if ( ! this.parent.zChildren.length ) { return ; } this.zIndex = this.parent.zChildren[ 0 ].zIndex - 1 ; this.parent.zSort() ; } ; Element.prototype.restoreZ = function() { if ( this.parent.interceptTempZIndex ) { return this.parent.restoreZ() ; } this.zIndex = this.savedZIndex ; this.parent.zSort() ; } ; Element.computeContentWidth = ( content , hasMarkup ) => { if ( Array.isArray( content ) ) { return ( hasMarkup === 'ansi' || hasMarkup === 'legacyAnsi' ? Math.max( ... content.map( line => misc.ansiWidth( line ) ) ) : hasMarkup ? Math.max( ... content.map( line => misc.markupWidth( line ) ) ) : Math.max( ... content.map( line => string.unicode.width( line ) ) ) ) ; } return ( hasMarkup === 'ansi' || hasMarkup === 'legacyAnsi' ? misc.ansiWidth( content ) : hasMarkup ? misc.markupWidth( content ) : string.unicode.width( content ) ) ; } ; var lastTruncateWidth = 0 ; Element.getLastTruncateWidth = () => lastTruncateWidth ; Element.truncateContent = ( content , maxWidth , hasMarkup ) => { var str ; if ( hasMarkup === 'ansi' || hasMarkup === 'legacyAnsi' ) { str = misc.truncateAnsiString( content , maxWidth ) ; lastTruncateWidth = misc.getLastTruncateWidth() ; } else if ( hasMarkup ) { str = misc.truncateMarkupString( content , maxWidth ) ; lastTruncateWidth = misc.getLastTruncateWidth() ; } else { str = string.unicode.truncateWidth( content , maxWidth ) ; lastTruncateWidth = string.unicode.getLastTruncateWidth() ; } return str ; } ; Element.wordwrapContent = // <-- DEPRECATED Element.wordWrapContent = ( content , width , hasMarkup ) => hasMarkup === 'ansi' || hasMarkup === 'legacyAnsi' ? misc.wordWrapAnsi( content , width ) : hasMarkup ? misc.wordWrapMarkup( content , width ) : string.wordwrap( content , { width , fill: true , noJoin: true } ) ; Element.prototype.setContent = function( content , hasMarkup , dontDraw = false , dontResize = false ) { if ( this.forceContentArray && ! Array.isArray( content ) ) { content = [ content || '' ] ; } var oldOutputWidth = this.outputWidth , oldOutputHeight = this.outputHeight ; this.content = content ; this.contentHasMarkup = hasMarkup ; this.contentWidth = Element.computeContentWidth( content , this.contentHasMarkup ) ; this.contentHeight = Array.isArray( content ) ? content.length : 1 ; if ( ! dontResize && this.resizeOnContent ) { this.resizeOnContent() ; } if ( ! dontDraw ) { if ( this.outputWidth < oldOutputWidth || this.outputHeight < oldOutputHeight ) { this.outerDraw() ; } else { this.draw() ; } } } ; Element.prototype.isAncestorOf = function( element ) { var currentElement = element ; for ( ;; ) { if ( currentElement === this ) { // Self found: ancestor match! return true ; } else if ( ! currentElement.parent ) { // The element is either detached or attached to another parent element return false ; } else if ( currentElement.parent.children.indexOf( currentElement ) === -1 ) { // Detached but still retain a ref to its parent. // It's probably a bug, so we will remove that link now. currentElement.parent = null ; return false ; } currentElement = currentElement.parent ; } } ; Element.prototype.getParentContainer = function() { var currentElement = this ; for ( ;; ) { if ( ! currentElement.parent ) { return null ; } if ( currentElement.parent.isContainer ) { return currentElement.parent ; } currentElement = currentElement.parent ; } } ; // Internal: get the index of the direct child that have the focus or have a descendant having the focus Element.prototype.getFocusBranchIndex = function() { var index , currentElement ; if ( ! this.document.focusElement ) { return null ; } currentElement = this.document.focusElement ; for ( ;; ) { if ( currentElement === this ) { // Self found: ancestor match! return null ; } else if ( ! currentElement.parent ) { // The element is either detached or attached to another parent element return null ; } if ( currentElement.parent === this ) { index = currentElement.parent.children.indexOf( currentElement ) ; if ( index === -1 ) { // Detached but still retain a ref to its parent. // It's probably a bug, so we will remove that link now. currentElement.parent = null ; return null ; } return index ; } currentElement = currentElement.parent ; } } ; Element.prototype.focusNextChild = function( loop = true , type = 'cycle' ) { var index , startingIndex , focusAware ; if ( ! this.children.length || ! this.document ) { return null ; } //if ( ! this.document.focusElement || ( index = this.children.indexOf( this.document.focusElement ) ) === -1 ) if ( ! this.document.focusElement || ( index = this.getFocusBranchIndex() ) === null ) { index = this.children.length - 1 ; } startingIndex = index ; for ( ;; ) { index ++ ; if ( index >= this.children.length ) { if ( loop ) { index = 0 ; } else { index = this.children.length - 1 ; break ; } } focusAware = this.document.giveFocusTo_( this.children[ index ] , type ) ; // Exit if the focus was given to a focus-aware element or if we have done a full loop already if ( focusAware || startingIndex === index ) { break ; } } return this.children[ index ] ; } ; Element.prototype.focusPreviousChild = function( loop = true ) { var index , startingIndex , focusAware ; if ( ! this.children.length || ! this.document ) { return null ; } //if ( ! this.document.focusElement || ( index = this.children.indexOf( this.document.focusElement ) ) === -1 ) if ( ! this.document.focusElement || ( index = this.getFocusBranchIndex() ) === null ) { index = 0 ; } startingIndex = index ; for ( ;; ) { index -- ; if ( index < 0 ) { if ( loop ) { index = this.children.length - 1 ; } else { index = 0 ; break ; } } focusAware = this.document.giveFocusTo_( this.children[ index ] , 'backCycle' ) ; // Exit if the focus was given to a focus-aware element or if we have done a full loop already if ( focusAware || startingIndex === index ) { break ; } } return this.children[ index ] ; } ; // Get all child element matching a x,y coordinate relative to the current element Element.prototype.childrenAt = function( x , y , filter = null , matches = [] ) { var i , current ; // Search children, order by descending zIndex, because we want the top element first i = this.zChildren.length ; while ( i -- ) { current = this.zChildren[ i ] ; // Filter out hidden element now if ( current.hidden ) { continue ; } if ( x >= current.outputX && x <= current.outputX + current.outputWidth - 1 && y >= current.outputY && y <= current.outputY + current.outputHeight - 1 ) { // Bounding box match! // Check and add children of children first (depth-first) if ( current.isContainer ) { current.childrenAt( x - current.inputX , y - current.inputY , filter , matches ) ; } else { current.childrenAt( x , y , filter , matches ) ; } if ( ! filter || filter( current ) ) { matches.push( { element: current , x: x - current.outputX , y: y - current.outputY } ) ; } } else if ( ! current.isContainer ) { // If it is not a container, give a chance to its children to get selected current.childrenAt( x , y , filter , matches ) ; } } return matches ; } ; Element.prototype.saveCursor = function() { if ( this.inputDst ) { this.savedCursorX = this.inputDst.cx ; this.savedCursorY = this.inputDst.cy ; } else if ( this.outputDst ) { this.savedCursorX = this.outputDst.cx ; this.savedCursorY = this.outputDst.cy ; } return this ; } ; Element.prototype.restoreCursor = function() { if ( this.inputDst ) { this.inputDst.cx = this.savedCursorX ; this.inputDst.cy = this.savedCursorY ; this.inputDst.drawCursor() ; } else if ( this.outputDst ) { this.outputDst.cx = this.savedCursorX ; this.outputDst.cy = this.savedCursorY ; this.outputDst.drawCursor() ; } return this ; } ; Element.prototype.draw = function( isInitialInlineDraw = false ) { //console.error( "\n----------------------------\nCalling .draw() for" , this.debugId() , new Error( 'trace:' ) ) ; if ( ! this.document || this.hidden ) { return this ; } if ( ! isInitialInlineDraw ) { if ( this.restoreCursorAfterDraw ) { this.inlineTerm.saveCursor() ; } else if ( ! this.strictInline ) { this.saveCursor() ; } } this.descendantDraw() ; this.ascendantDraw() ; if ( ! isInitialInlineDraw ) { if ( this.restoreCursorAfterDraw ) { this.inlineTerm.restoreCursor() ; } else if ( ! this.strictInline ) { this.drawCursor() ; } } return this ; } ; // .draw() is used when drawing the current Element is enough: the Element has not moved, and has not been resized. // If it has, then it is necessary to draw the closest ancestor which is a container. // /!\ IS THIS METHOD WRONG? it should draw the parent container, but don't redraw any children of its children Container // Option 'force' redraw even if the element is hidden, in fact it is used by the .hide() method to effectively hide the element on the parent container. Element.prototype.redraw = // DEPRECATED name, use .outerDraw() Element.prototype.outerDraw = function( force = false ) { if ( ! this.document || ( this.hidden && ! force ) ) { return this ; } var container = this.getParentContainer() ; if ( ! container ) { this.draw() ; } else { container.draw() ; } return this ; } ; // Hard to find a good name, .draw() or .outerDraw() depending on what have been updated Element.prototype.updateDraw = function() { if ( this.needOuterDraw ) { this.outerDraw() ; } else { this.draw() ; } this.needOuterDraw = false ; } ; // Draw all the children Element.prototype.descendantDraw = function( isSubcall ) { var i , iMax ; if ( this.hidden ) { return this ; } if ( this.preDrawSelf ) { this.preDrawSelf( ! isSubcall ) ; } // Draw children, order by ascending zIndex for ( i = 0 , iMax = this.zChildren.length ; i < iMax ; i ++ ) { this.zChildren[ i ].descendantDraw( true ) ; } if ( isSubcall && this.postDrawSelf ) { this.postDrawSelf( ! isSubcall ) ; } return this ; } ; // Post-draw from the current element through all the ancestor chain Element.prototype.ascendantDraw = function() { var currentElement ; if ( this.postDrawSelf && ! this.hidden ) { this.postDrawSelf( true ) ; } currentElement = this ; while ( currentElement.parent && currentElement.outputDst !== currentElement.document.outputDst ) { currentElement = currentElement.parent ; if ( currentElement.outputDst !== currentElement.inputDst && currentElement.postDrawSelf && ! currentElement.hidden ) { currentElement.postDrawSelf( false ) ; } } return this ; } ; // Draw cursor from the current element through all the ancestor chain Element.prototype.drawCursor = function() { var currentElement ; if ( this.drawSelfCursor && ! this.hidden ) { this.drawSelfCursor( true ) ; } currentElement = this ; while ( currentElement.outputDst !== currentElement.document.outputDst && currentElement.parent ) { currentElement = currentElement.parent ; if ( currentElement.drawSelfCursor && ! currentElement.hidden ) { currentElement.drawSelfCursor( false ) ; } } return this ; } ; // TODOC Element.prototype.bindKey = function( key , action ) { this.keyBindings[ key ] = action ; } ; // TODOC Element.prototype.getKeyBinding = function( key ) { return this.keyBindings[ key ] ?? null ; } ; // TODOC Element.prototype.getKeyBindings = function( key ) { return Object.assign( {} , this.keyBindings ) ; } ; // TODOC Element.prototype.getActionBinding = function( action , ui = false ) { var keys = [] ; for ( let key in this.keyBindings ) { if ( this.keyBindings[ key ] === action ) { keys.push( ui ? misc.keyToUserInterfaceName( key ) : key ) ; } } return keys ; } ; // For inline widget, having eventually a document just for him, that fit its own size Element.createInline = async function( term , Type , options ) { // Clone options if necessary options = ! options ? {} : options.internal ? options : Object.create( options ) ; options.internal = true ; options.inlineTerm = term ; //options.outputDst = term ; //options.eventSource = term ; var cursorPosition , position = { x: options.outputX || options.x , y: options.outputY || options.y } ; // Don't use 'delete', because options = Object.create( options ) -- doesn't work with inheritance options.x = options.y = options.outputX = options.outputY = 0 ; var element = new Type( options ) ; if ( position.x === undefined || position.y === undefined ) { if ( element.strictInline ) { // We do not want any asyncness for pure inline elements, and since we draw in inline mode, we don't care about it... // ... 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). position.x = position.y = 1 ; } else { cursorPosition = await term.getCursorLocation() ; if ( position.x === undefined ) { position.x = cursorPosition.x ; if ( cursorPosition.x > 1 && element.inlineNewLine ) { position.x = 1 ; if ( position.y === undefined ) { position.y = cursorPosition.y + 1 ; } } } if ( position.y === undefined ) { position.y = cursorPosition.y ; } } } if ( ! element.strictInline ) { let scrollY = position.y + element.outputHeight - term.height ; if ( scrollY > 0 ) { term.scrollUp( scrollY ) ; term.up( scrollY ) ; // move the cursor up, so save/restore cursor could work position.y -= scrollY ; } } if ( element.inlineResizeToContent ) { element.resizeToContent() ; } var documentOptions = { internal: true , inlineTerm: term , strictInline: element.strictInline , noInput: element.strictInline || ! element.needInput , outputX: position.x , outputY: position.y , outputWidth: element.outputWidth , outputHeight: element.outputHeight , outputDst: term , eventSource: term , noDraw: true } ; var document = new termkit.Document( documentOptions ) ; document.attach( element ) ; // Should probably resize the container element.on( 'resize' , () => { throw new Error( 'not coded!' ) ; } ) ; element.draw( true ) ; term.styleReset() ; if ( element.staticInline ) { element.destroy( undefined , true ) ; } return element ; } ; // Default 'key' event management, suitable for almost all use-case, but could be derivated if needed Element.prototype.onKey = function( key , trash , data ) { var action = this.keyBindings[ key ] ; //console.error( this.debugId() , "Key:" , key , "Actions:" , action , !! this.userActions?.[ action ] ) ; // action && this.userActions[ action ] ? "fn: " + this.userActions[ action ].toString() : '' ) ; if ( action ) { if ( action === 'meta' ) { if ( this.document ) { this.document.setMetaKeyPrefix( 'META' , 'CTRL' ) ; } return true ; // Do not bubble up } else if ( this.userActions[ action ] ) { // Do not bubble up except if explicitly false return ( this.userActions[ action ].call( this , key , trash , data ) ?? true ) || undefined ; } } else if ( data && data.isCharacter ) { if ( this.userActions.character ) { // Do not bubble up except if explicitly false return ( this.userActions.character.call( this , key , trash , data ) ?? true ) || undefined ; } } else if ( this.userActions.specialKey ) { // Do not bubble up except if explicitly false return ( this.userActions.specialKey.call( this , key , trash , data ) ?? true ) || undefined ; } // Nothing found, bubble up return ; } ; // Should be redefined Element.prototype.isContainer = false ; // boolean, true if it's a container, having a different inputDst and outputDst and local coords Element.prototype.forceContentArray = false ; // boolean, true if content should be an array of string instead of a string Element.prototype.noChildFocus = false ; // boolean, true if the focus should not be transmitted to children of this Element Element.prototype.computeBoundingBoxes = null ; // function, bounding boxes for elements that can be drawn Element.prototype.resizeOnContent = null ; // function, if set, resize on content update, called by .setContent() Element.prototype.preDrawSelf = null ; // function, things to draw for the element before drawing its children Element.prototype.postDrawSelf = null ; // function, things to draw for the element after drawing its children Element.prototype.drawSelfCursor = null ; // function, draw the element cursor Element.prototype.getValue = () => null ; // function, get the value of the element if any... Element.prototype.setValue = () => undefined ; // function, set the value of the element if any... Element.prototype.strictInlineSupport = false ; // no support for strictInline mode by default Element.prototype.staticInline = false ; // boolean, true if the inline version is static and could be destroyed immediately after been drawn Element.prototype.inlineCursorRestoreAfterDraw = false ; // when set, save/restore cursor in inline mode (forced when strictInline is true) Element.prototype.needInput = false ; // no need for input by default (used to configure inline mode) Element.prototype.outerDrag = false ; // boolean, true if drag event are sent when out of bounds (e.g. useful for moving windows) Element.prototype.keyBindings = {} ; // object, store key bindings, the key is a Terminal Kit key code, the value is an user-action name Element.prototype.userActions = {} ; // object, the key is an user-action name, the value is a function... THIS IS INHERITED