ng-terminal
Version:
NgTerminal is a terminal component on Angular 16 or higher.
772 lines (766 loc) • 64.6 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Optional, Component, ViewEncapsulation, EventEmitter, ChangeDetectionStrategy, Output, Input, ViewChild, NgModule } from '@angular/core';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { Subject } from 'rxjs';
import * as i2 from 'angular-resizable-element';
import { ResizableModule } from 'angular-resizable-element';
import * as i3 from '@angular/common';
import { CommonModule } from '@angular/common';
/**
*
* In the past, indivisual property changes caused very unexpected behavior on NgTerminal.
* To solve issues related to property changes, Queue is applied.
*/
class LinearRenderService {
constructor(hostRef) {
this.hostRef = hostRef;
this.handlerToCheckElementConnection = undefined;
/**
* This queue has items to have a influence on the view and items are handled in order.
*/
this.propertyChangeQueue = [];
this.itemsToBeHandled = new Subject();
/**
* Handle a next set of changes if it exists.
*/
this.handleImmediate = () => {
if (!this.handlerToCheckElementConnection) {
let list = this.propertyChangeQueue.splice(0, 1);
if (list.length == 1) {
this.itemsToBeHandled.next(list[0]);
}
}
};
}
get renderObservable() {
return this.itemsToBeHandled;
}
/**
* {@link handleNextOne()} calls {@link handleImmediate()} if an element is connected.
* Otherwise, {@link pollAndHandle()} is called.
*/
handleNextOne(lazy = false) {
if (!this.hostRef.nativeElement.isConnected || lazy) {
this.pollAndHandle(!this.hostRef.nativeElement.isConnected
? 'NOT_CONNECTED_ELEMENT'
: 'LAZY');
}
else {
this.handleImmediate();
}
}
/**
* This method pushes item into {@link propertyChangeQueue}.
* @param item
*/
pushAndHandle(item, lazy = false) {
let changeWithDefault = Object.assign({}, item);
this.propertyChangeQueue.push(changeWithDefault);
this.handleNextOne(lazy);
}
/**
* {@link pollAndHandle()} continues checking whether new item is put on a queue to call {@link handleImmediate()}.
*/
pollAndHandle(mode) {
const pollFunction = () => {
if (this.handlerToCheckElementConnection)
return;
const interval = setInterval(() => {
if (this.hostRef.nativeElement.isConnected) {
clearInterval(interval);
this.handlerToCheckElementConnection = undefined;
this.handleImmediate();
}
}, mode === 'NOT_CONNECTED_ELEMENT' ? 500 : 30);
this.handlerToCheckElementConnection = interval;
};
// Start polling
pollFunction();
}
ngOnDestroy() {
if (this.handlerToCheckElementConnection)
clearInterval(this.handlerToCheckElementConnection);
}
}
LinearRenderService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LinearRenderService, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Injectable });
LinearRenderService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LinearRenderService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LinearRenderService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }]; } });
class ModuleOptions {
constructor() {
this.enableLog = true;
}
}
class LoggingService {
constructor(_options) {
this.enableLog = false;
if (_options)
this.enableLog = _options.enableLog;
}
log(loggingFunc) {
if (this.enableLog)
loggingFunc();
}
}
LoggingService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LoggingService, deps: [{ token: ModuleOptions, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
LoggingService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LoggingService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LoggingService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () {
return [{ type: ModuleOptions, decorators: [{
type: Optional
}] }];
} });
class GlobalStyleComponent {
constructor() { }
ngOnInit() {
}
}
GlobalStyleComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: GlobalStyleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
GlobalStyleComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: GlobalStyleComponent, selector: "global-style", ngImport: i0, template: "", styles: ["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * https://github.com/chjj/term.js\n * @license MIT\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n */.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;inset:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;inset:0;z-index:10;color:transparent;pointer-events:none}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{-webkit-text-decoration:double underline;text-decoration:double underline}.xterm-underline-3{-webkit-text-decoration:wavy underline;text-decoration:wavy underline}.xterm-underline-4{-webkit-text-decoration:dotted underline;text-decoration:dotted underline}.xterm-underline-5{-webkit-text-decoration:dashed underline;text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{-webkit-text-decoration:overline double underline;text-decoration:overline double underline}.xterm-overline.xterm-underline-3{-webkit-text-decoration:overline wavy underline;text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{-webkit-text-decoration:overline dotted underline;text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{-webkit-text-decoration:overline dashed underline;text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}\n"], encapsulation: i0.ViewEncapsulation.None });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: GlobalStyleComponent, decorators: [{
type: Component,
args: [{ selector: 'global-style', encapsulation: ViewEncapsulation.None, template: "", styles: ["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * https://github.com/chjj/term.js\n * @license MIT\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n */.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;inset:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;inset:0;z-index:10;color:transparent;pointer-events:none}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{-webkit-text-decoration:double underline;text-decoration:double underline}.xterm-underline-3{-webkit-text-decoration:wavy underline;text-decoration:wavy underline}.xterm-underline-4{-webkit-text-decoration:dotted underline;text-decoration:dotted underline}.xterm-underline-5{-webkit-text-decoration:dashed underline;text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{-webkit-text-decoration:overline double underline;text-decoration:overline double underline}.xterm-overline.xterm-underline-3{-webkit-text-decoration:overline wavy underline;text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{-webkit-text-decoration:overline dotted underline;text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{-webkit-text-decoration:overline dashed underline;text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}\n"] }]
}], ctorParameters: function () { return []; } });
class NgTerminalComponent {
/**
* A datsource is an observable where NgTerminal reads charactors.
*/
set _dataSource(dataSource) {
if (!dataSource)
return;
if (this.dataSourceSubscription != undefined)
this.dataSourceSubscription.unsubscribe();
this.dataSource = dataSource;
this.dataSourceSubscription = this.dataSource.subscribe((data) => this.write(data));
}
/**
* Toggle draggable.
*/
set draggable(draggable) {
this._draggable = draggable;
}
get draggable() {
return this._draggable;
}
setMinWidth(width) {
this._minWidthInput = width;
}
setMinHeight(height) {
this._minHeightInput = height;
}
setDraggable(draggable) {
this._draggable = draggable;
this.lastDraggedPosition = undefined; // Reset position values
}
setXtermOptions(options) {
this._xtermOptions = options;
this.linearRender.pushAndHandle({ time: new Date(), type: 'none' });
}
setRows(rows) {
if (this._rowsInput != rows) {
this._rowsInput = rows;
this.linearRender.pushAndHandle({
time: new Date(),
type: 'rowChanged',
});
}
}
setCols(cols) {
if (this._colsInput != cols) {
this._colsInput = cols;
this.linearRender.pushAndHandle({
time: new Date(),
type: 'columnChanged',
});
}
}
setStyle(styleObject) {
var _a;
if (JSON.stringify((_a = this._stylesInput) !== null && _a !== void 0 ? _a : {}) !=
JSON.stringify(styleObject !== null && styleObject !== void 0 ? styleObject : {})) {
this._stylesInput = styleObject;
this.linearRender.pushAndHandle({
time: new Date(),
type: 'none',
});
}
}
constructor(renderer, //Render is being used for fast rendering without markForCheck().
hostRef, loggingService) {
this.renderer = renderer;
this.hostRef = hostRef;
this.loggingService = loggingService;
this.dataSubject = new Subject();
this.keySubject = new Subject();
this.keyInputSubjectSubscription = undefined;
this.keyEventSubjectSubscription = undefined;
this.allLogsSubjectSubscription = undefined;
this.dataSourceSubscription = undefined;
this.resizableObservers = [];
this.dataSource = undefined;
this.paddingSize = 5;
this.stylesForResizeBox = { display: 'block' };
/**
* @deprecated use (data)='' instead.
* An emitter emits printable characters pushed from xterm's onData() when a user typed on the terminal.
*/
this.keyInputEmitter = new EventEmitter();
/**
* @deprecated use (key)='' instead.
* An emitter emits key and keybaord event pushed from xterm's onKey() when a user typed on the terminal.
*/
this.keyEventEmitter = new EventEmitter();
/**
* An emitter emits printable characters pushed from xterm's onData() when a user typed on the terminal.
*/
this.dataEmitter = this.keyInputEmitter;
/**
* An emitter emits key and keybaord event pushed from xterm's onKey() when a user typed on the terminal.
*/
this.keyEmitter = this.keyEventEmitter;
/**
* An wrapper of {@link ITerminalOptions} for Xterm.
*/
this._xtermOptions = {};
this.lastDraggedPosition = undefined;
this._draggable = false;
this._stylesInput = {};
this.handleToCheckLazyContainer = undefined;
this.lastDetectedWidth = 0;
this.linearRender = new LinearRenderService(hostRef);
}
setup() {
if (this.xterm) {
this.xterm.onData((input) => {
this.dataSubject.next(input);
});
this.xterm.onKey((e) => {
this.keySubject.next(e);
});
}
this.keyInputSubjectSubscription = this.dataSubject.subscribe((data) => {
this.keyInputEmitter.emit(data);
});
this.keyEventSubjectSubscription = this.keySubject.subscribe((e) => {
this.keyEventEmitter.emit(e);
});
this.setupResizeObservers();
// if (ob3) this.resizableObservers.push(ob3);
this.allLogsSubjectSubscription =
this.linearRender.renderObservable.subscribe((change) => {
if (change)
this.coordinateOuterAndTerminal(change);
else
this.coordinateOuterAndTerminal(change);
this.linearRender.handleNextOne();
});
this.linearRender.handleNextOne();
}
/**
* set dimensions
*/
setOuterDimensions(left, top, width, height) {
this.linearRender.pushAndHandle({
time: new Date(),
type: 'dragged',
dragged: { draggedWidth: `${width}px`, draggedHeight: `${height}px` },
});
}
applyStylesToResizeBox() {
Object.keys(this.stylesForResizeBox)
.map((key) => {
return { key, value: this.stylesForResizeBox[key] };
})
.forEach(({ key, value }) => {
if (this.resizeBox) {
if (value)
this.renderer.setStyle(this.resizeBox.nativeElement, key, value);
else {
this.renderer.removeStyle(this.resizeBox.nativeElement, key);
}
}
});
this.stylesForResizeBox = this.stylesForResizeBox; //invalidate
}
ngOnInit() { }
/**
* It creates new terminal in #terminal.
*/
ngAfterViewInit() {
this.fitAddon = new FitAddon();
this.xterm = new Terminal({
allowProposedApi: true,
});
if (!this.terminalOuter.nativeElement.isConnected) {
this.handleToCheckLazyContainer = setInterval(() => {
if (this.terminalOuter.nativeElement.isConnected) {
try {
this.loggingService.log(() => console.debug("The container's been connected."));
this.xterm.open(this.terminalOuter.nativeElement);
this.xterm.loadAddon(this.fitAddon);
this.setup();
this.linearRender.pushAndHandle({ time: new Date(), type: 'none' });
}
finally {
if (this.handleToCheckLazyContainer)
clearInterval(this.handleToCheckLazyContainer);
}
}
}, 500);
}
else {
this.xterm.open(this.terminalOuter.nativeElement);
this.xterm.loadAddon(this.fitAddon);
this.setup();
this.linearRender.pushAndHandle({ time: new Date(), type: 'none' });
}
}
ngOnChanges(changes) {
var _a, _b, _c, _d;
this.loggingService.log(() => console.group('onChanges'));
this.loggingService.log(() => console.debug('prop: ', changes));
this.loggingService.log(() => console.groupEnd());
if (changes === null || changes === void 0 ? void 0 : changes['_rowsInput']) {
if (((_a = changes === null || changes === void 0 ? void 0 : changes['_rowsInput']) === null || _a === void 0 ? void 0 : _a.previousValue) !=
((_b = changes === null || changes === void 0 ? void 0 : changes['_rowsInput']) === null || _b === void 0 ? void 0 : _b.currentValue)) {
this.linearRender.pushAndHandle({
time: new Date(),
type: 'rowChanged',
});
}
}
if (changes === null || changes === void 0 ? void 0 : changes['_colsInput']) {
if (((_c = changes === null || changes === void 0 ? void 0 : changes['_colsInput']) === null || _c === void 0 ? void 0 : _c.previousValue) !=
((_d = changes === null || changes === void 0 ? void 0 : changes['_colsInput']) === null || _d === void 0 ? void 0 : _d.currentValue)) {
this.linearRender.pushAndHandle({
time: new Date(),
type: 'columnChanged',
});
}
}
if (changes === null || changes === void 0 ? void 0 : changes['draggable'])
this.linearRender.pushAndHandle({ time: new Date(), type: 'none' });
}
/**
* It serves a callback function to adjust the dimensions of the xterm-screen, xterm-view, and resize box
* after making any changes to the outer box, rows, or columns, or when the resize box is being dragged.
*
* There several factors that affect dimensions, as I mentioned earlier.
* Regardless of whether the draggable feature is on, if new row or column value is input, this value will be applied.
* - Draggable = New specified Row/Column value > Full (Default)
* @param change This argument represents a single change that occured.
* @returns
*/
coordinateOuterAndTerminal(change) {
var _a;
this.loggingService.log(() => console.debug(`changeList: ${JSON.stringify(change)}`));
if (!this.xterm)
return;
const isHostElementVisible = ((_a = this.hostRef.nativeElement) === null || _a === void 0 ? void 0 : _a.offsetParent) !== null;
if (!isHostElementVisible) {
// Do nothing if the host element is invisible.
this.loggingService.log(() => console.debug('`display` of host element was set to `none`'));
return;
}
this.doUpdateXtermStyles();
this.doAdjustDimensionOfResizeBox(change);
this.doAdjustSizeOfXtermScreen(change);
this.doUpdateViewportAndResizeBoxWithPixcelUnit();
}
/**
* apply options to the xterm terminal
* @returns
*/
doUpdateXtermStyles() {
var _a, _b;
if (!this.xterm)
return;
this.xterm.options = Object.assign({}, this._xtermOptions);
// apply the theme to the background of the handle
if ((_a = this._xtermOptions.theme) === null || _a === void 0 ? void 0 : _a.background) {
if (!this.resizeHandleStyleRule)
this.resizeHandleStyleRule = this.findCssStyleRule('.resize-handle[');
if (this.resizeHandleStyleRule)
this.resizeHandleStyleRule.style.backgroundColor =
this._xtermOptions.theme.background;
}
if ((_b = this._xtermOptions.theme) === null || _b === void 0 ? void 0 : _b.border) {
if (!this.resizeHandleActiveStyleRule)
this.resizeHandleActiveStyleRule =
this.findCssStyleRule('.handle-active');
if (this.resizeHandleActiveStyleRule)
this.resizeHandleActiveStyleRule.style.backgroundColor =
this._xtermOptions.theme.border;
}
}
/**
* If the resize handles are moved, the resize box adjusts to the new dimensions;
* otherwise, it defaults to a maximized size.
* @param change
*/
doAdjustDimensionOfResizeBox(change) {
var _a, _b;
this.stylesForResizeBox = Object.assign(Object.assign(Object.assign({}, this.stylesForResizeBox), this._stylesInput), { width: this.stylesForResizeBox.width, height: this.stylesForResizeBox.height });
// Reset styles of the resize element
if (change.type === 'dragged') {
const minWidth = (_a = this._minWidthInput) !== null && _a !== void 0 ? _a : 24;
const minHeight = (_b = this._minHeightInput) !== null && _b !== void 0 ? _b : 24;
const width = parseInt(change.dragged.draggedWidth) > minWidth
? change.dragged.draggedWidth
: `${minWidth}px`;
const height = parseInt(change.dragged.draggedHeight) > minHeight
? change.dragged.draggedHeight
: `${minHeight}px`;
this.stylesForResizeBox.width = width;
this.stylesForResizeBox.height = height;
this.lastDraggedPosition = {
width,
height,
};
this.applyStylesToResizeBox();
}
else if (!(this.draggable && this.lastDraggedPosition)) {
// When `_colsInput` and `draggable` is not enabled,
// it fits the size to the host element.
const currentHostWidth = getComputedStyle(this.hostRef.nativeElement).width;
const detectBoxWidth = getComputedStyle(this.detectBox.nativeElement).width;
let smallParent = false;
if (parseFloat(detectBoxWidth) < parseFloat(currentHostWidth)) {
// the width of the parent is smaller than that of resize-box element
smallParent = true;
}
if (smallParent) {
// It's been written to solve https://github.com/qwefgh90/ng-terminal/issues/79
// If the width of the flex-box (that is the parent of the host element) is smaller than that of child element, the host element is adjusted to the width of child element
// host element: 1000px, resize-box(child): 985px -> host element: 985px, resize-box(child): 970px -> ... -> stop
// This code check if the parent element (that is the parent of `<ng-terminal>), is smaller than `.resize-box`
// and ensures that the width of the `<ng-terminal>` adjusts to match that of the parent element rather than the child elements, in the subsequent events.
this.stylesForResizeBox.width = `${parseFloat(detectBoxWidth)}px`;
this.applyStylesToResizeBox();
}
else {
// but if the dimension of host element is resized, update width and height
// If `_rowsInput` is specified, NgTerminal keep the current height; otherwise, the height is set to 100%
if (!this._rowsInput)
this.stylesForResizeBox.height = '100%';
if (!this._colsInput)
this.stylesForResizeBox.width = '100%';
this.applyStylesToResizeBox();
}
}
}
/**
* This function uses fitAddon() to adjust the dimension of xterm-screen to character unit
* If the draggable value is true or there are no fixed values for the row or column,
* it fits the xterm terminal boxes into the resize box;
* otherwise, it resizes the xterm terminal with specified row and column values.
*/
doAdjustSizeOfXtermScreen(change) {
var _a, _b, _c;
if (!this.xterm)
return;
if ((change.type == 'rowChanged' && this._rowsInput) ||
(change.type == 'columnChanged' && this._colsInput)) {
this.xterm.resize((_a = this._colsInput) !== null && _a !== void 0 ? _a : this.xterm.cols, (_b = this._rowsInput) !== null && _b !== void 0 ? _b : this.xterm.rows);
}
else {
if (this.xterm.element) {
// The fitAddon.fit() operation doesn't recognize the padding values of terminalOuter.
// It seems to be using the padding values of xterm element instead.
// Therefore, we establish a brief time frame to adjust the padding values before and after executing fitAddon.fit().
// If this line is removed, when dragging resize-box vertically, the width is decreased.
this.xterm.element.style.paddingLeft = `${this.paddingSize}px`;
this.printDimension('Before fitAddon.fit() of Xterm');
(_c = this.fitAddon) === null || _c === void 0 ? void 0 : _c.fit();
this.printDimension('After fitAddon.fit() of Xterm');
this.xterm.element.style.paddingLeft = `${0}px`;
}
}
}
/**
* This functions sets width of the resize box, xterm-viewport and xterm-screen with specific pixel values.
*/
doUpdateViewportAndResizeBoxWithPixcelUnit() {
var _a;
if ((_a = this.xterm) === null || _a === void 0 ? void 0 : _a.element) {
let xtermScreen = this.xterm.element.getElementsByClassName('xterm-screen')[0];
let xtermViewport = this.xterm.element.getElementsByClassName('xterm-viewport')[0];
const screenWidth = xtermScreen.clientWidth;
const screenHeight = xtermScreen.clientHeight;
const core = this.underlying._core;
const scrollBarWidth = core.viewport.scrollBarWidth;
const hostWidth = parseInt(getComputedStyle(this.hostRef.nativeElement).width);
// It fixes a bug where the viewport's width isn't updated by fitAddon.fit()
this.renderer.setStyle(xtermViewport, 'width', `${screenWidth + scrollBarWidth}px`);
// It adjusts the dimension of the resize box to the xterm-screen element.
const calulatedBoxWidth = screenWidth + scrollBarWidth + this.paddingSize * 2;
const componentElement = this.hostRef.nativeElement;
const componentWith = parseInt(getComputedStyle(componentElement).width);
const restrictedWidth = calulatedBoxWidth > componentWith ? componentWith : calulatedBoxWidth;
this.stylesForResizeBox = Object.assign(Object.assign({}, this.stylesForResizeBox), { width: `${restrictedWidth}px`, height: `${screenHeight + this.paddingSize * 2}px` });
this.applyStylesToResizeBox();
this.printDimension('After update the dimensions for all boxes with pixel values');
}
}
printDimension(title) {
var _a;
if ((_a = this.xterm) === null || _a === void 0 ? void 0 : _a.element) {
let resizeBox = this.resizeBox.nativeElement;
let xtermScreen = this.xterm.element.getElementsByClassName('xterm-screen')[0];
let xtermViewport = this.xterm.element.getElementsByClassName('xterm-viewport')[0];
const screenWidth = xtermScreen.clientWidth;
const screenHeight = xtermScreen.clientHeight;
this.loggingService.log(() => console.group(`${title}`));
this.loggingService.log(() => console.debug(`width(resizeBox): ${getComputedStyle(resizeBox).width},
width(viewport): ${getComputedStyle(xtermViewport).width},
width(screen): ${screenWidth}
scrollBarWidth: ${this.scrollBarWidth}`));
this.loggingService.log(() => console.debug(`height(resizeBox): ${getComputedStyle(resizeBox).height},
height(viewport) ${getComputedStyle(xtermViewport).height},
height(screen): ${screenHeight}`));
this.loggingService.log(() => console.groupEnd());
}
}
/**
* If pushAndHandle() were used, there could be an issue
* because it can adjust the size of elements during a loop of ResizeObserver.
*/
setupResizeObservers() {
this.resizableObservers = [];
let ob1 = this.observeXtermViewportDimension();
let ob2 = this.observeHostDimension();
if (ob1)
this.resizableObservers.push(ob1);
if (ob2)
this.resizableObservers.push(ob2);
}
observeXtermViewportDimension() {
let xtermViewport = this.terminalOuter.nativeElement.querySelector('.xterm-viewport');
if (xtermViewport) {
const resizeObserver = new ResizeObserver((entries) => {
var _a, _b;
const outerDivWidth = (_a = this.terminalOuter.nativeElement) === null || _a === void 0 ? void 0 : _a.clientWidth;
const outerDivHeight = (_b = this.terminalOuter.nativeElement) === null || _b === void 0 ? void 0 : _b.clientHeight;
for (let entry of entries) {
if (entry.contentBoxSize.length > 0) {
let width = entry.target.clientWidth;
let height = entry.target.clientHeight;
if ((outerDivWidth && width > outerDivWidth) ||
(outerDivHeight && height > outerDivHeight)) {
this.loggingService.log(() => console.debug('Changes on a xterm viewport element will be handled.'));
this.linearRender.pushAndHandle({
time: new Date(),
type: 'xtermViewportExceedingOuterDiv',
xtermViewportExceedingOuterDiv: {
width: `${width}`,
height: `${height}`,
outerDivWidth: `${outerDivWidth}`,
outerDivHeight: `${outerDivHeight}`,
},
}, true);
}
}
}
});
resizeObserver.observe(xtermViewport);
return resizeObserver;
}
else {
this.loggingService.log(() => console.error('Invalid state is detected. xterm element should exist below .terminal-outer.'));
}
return undefined;
}
observeHostDimension() {
let hostElement = this.hostRef.nativeElement;
let detectBox = this.detectBox.nativeElement;
if (hostElement && detectBox) {
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
if (entry.target === hostElement) {
if (entry.contentBoxSize.length > 0) {
let width = getComputedStyle(entry.target).width;
let height = getComputedStyle(entry.target).height;
if (parseInt(width) >= 0 && parseInt(height) >= 0) {
this.loggingService.log(() => console.debug('Changes on a host element will be handled.'));
this.linearRender.pushAndHandle({
time: new Date(),
type: 'hostResized',
hostResized: { width: `${width}`, height: `${height}` },
}, true);
}
}
}
if (entry.target === detectBox) {
if (entry.contentBoxSize.length > 0) {
let width = getComputedStyle(entry.target).width;
if (parseInt(width) >= 0 &&
parseInt(width) <= this.lastDetectedWidth) {
this.loggingService.log(() => console.debug('Changes on a detect-box element will be handled.'));
this.linearRender.pushAndHandle({
time: new Date(),
type: 'detectBoxResized',
detectBoxResized: { width: `${width}` },
}, true);
}
this.lastDetectedWidth = parseInt(width);
}
}
}
});
resizeObserver.observe(hostElement);
resizeObserver.observe(detectBox);
return resizeObserver;
}
else {
this.loggingService.log(() => console.error('Invalid state is detected. xterm element should exist below .terminal-outer.'));
}
return undefined;
}
/**
* clean all resources
*/
ngOnDestroy() {
this.resizableObservers.forEach((ob) => ob.disconnect());
if (this.keyInputSubjectSubscription)
this.keyInputSubjectSubscription.unsubscribe();
if (this.dataSourceSubscription)
this.dataSourceSubscription.unsubscribe();
if (this.keyEventSubjectSubscription)
this.keyEventSubjectSubscription.unsubscribe();
if (this.allLogsSubjectSubscription)
this.allLogsSubjectSubscription.unsubscribe();
if (this.handleToCheckLazyContainer)
clearInterval(this.handleToCheckLazyContainer);
if (this.xterm)
this.xterm.dispose();
this.loggingService.log(() => console.debug('All resources has been cleaned up.'));
}
write(chars) {
var _a;
(_a = this.xterm) === null || _a === void 0 ? void 0 : _a.write(chars);
}
get keyInput() {
return this.dataSubject;
}
onData() {
return this.dataSubject;
}
get keyEventInput() {
return this.keySubject;
}
onKey() {
return this.keySubject;
}
get underlying() {
return this.xterm;
}
get isDraggableOnEdgeActivated() {
// return this.displayOption.activateDraggableOnEdge != undefined && this.displayOption.fixedGrid == undefined;
return this._draggable;
}
get scrollBarWidth() {
const core = this.underlying._core;
return core.viewport.scrollBarWidth;
}
/**
* After user coordinate dimensions of terminal, it's called.
* @param left
* @param top
* @param width
* @param height
*/
onResizeEnd(left, top, width, height) {
this.setOuterDimensions(left, top, width, height);
}
/**
* Before onResizeEnd is called, it valiates dimensions to change.
* @param re dimension to be submitted from resizable stuff
*/
validatorFactory() {
const comp = this;
return (re) => {
if (this._draggable) {
return true;
}
else
return false;
};
}
findCssStyleRule(containingSelector) {
for (let i = 0; i < document.styleSheets.length; i++) {
let sheet = document.styleSheets.item(i);
if (sheet) {
for (let i = 0; i < sheet.cssRules.length; i++) {
let rule = sheet.cssRules.item(i);
if ('selectorText' in rule)
if (rule.selectorText.includes(containingSelector))
return rule;
}
}
}
return undefined;
}
}
NgTerminalComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgTerminalComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: LoggingService }], target: i0.ɵɵFactoryTarget.Component });
NgTerminalComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: NgTerminalComponent, selector: "ng-terminal", inputs: { _dataSource: ["dataSource", "_dataSource"], _rowsInput: ["rows", "_rowsInput"], _colsInput: ["cols", "_colsInput"], _minWidthInput: ["minWidth", "_minWidthInput"], _minHeightInput: ["minHeight", "_minHeightInput"], draggable: "draggable", _xtermOptions: ["xtermOptions", "_xtermOptions"] }, outputs: { keyInputEmitter: "keyInput", keyEventEmitter: "keyEvent", dataEmitter: "data", keyEmitter: "key" }, providers: [LinearRenderService], viewQueries: [{ propertyName: "terminalOuter", first: true, predicate: ["terminal"], descendants: true, static: true }, { propertyName: "resizeBox", first: true, predicate: ["resizeBox"], descendants: true, static: true }, { propertyName: "detectBox", first: true, predicate: ["detectBox"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Hierarchy: parent element(user) > host element(ng-terminal) > resize-box element > outer element -->\n<global-style></global-style>\n<div mwlResizable #resizeBox class=\"resize-box\" [validateResize]=\"validatorFactory()\" [enableGhostResize]=\"true\"\n (resizeEnd)=\"onResizeEnd($event.rectangle.left, $event.rectangle.top, $event.rectangle.width??0, $event.rectangle.height??0)\">\n <div #terminal class=\"terminal-outer\">\n </div>\n <div class=\"resize-handle resize-handle-top\" mwlResizeHandle [ngClass]=\"{'handle-active' : isDraggableOnEdgeActivated}\"\n [resizeEdges]=\"{ top: false }\"></div>\n <div class=\"resize-handle resize-handle-left\" mwlResizeHandle [ngClass]=\"{'handle-active' : isDraggableOnEdgeActivated}\"\n [resizeEdges]=\"{ left: false }\"></div>\n <div class=\"resize-handle resize-handle-right\" mwlResizeHandle [ngClass]=\"{'handle-active' : isDraggableOnEdgeActivated}\"\n [resizeEdges]=\"isDraggableOnEdgeActivated ? { right: true } : { right: false }\"></div>\n <div class=\"resize-handle resize-handle-bottom\" mwlResizeHandle [ngClass]=\"{'handle-active' : isDraggableOnEdgeActivated}\"\n [resizeEdges]=\"isDraggableOnEdgeActivated ? { bottom: true } : { bottom: false }\"></div>\n</div>\n<div #detectBox style=\"width: 100%; height: 0px; position: absolute;\">\n</div>", styles: [".terminal-outer{box-sizing:border-box;height:100%;width:100%;padding:5px}:host{display:block;height:100%;width:100%}.resize-box{box-sizing:border-box;height:100%;width:100%;position:relative}div>.handle-active{z-index:100;background-color:#85858a!important}.resize-handle{background-color:#000}.resize-handle-top.handle-active,.resize-handle-bottom.handle-active{cursor:row-resize}.resize-handle-left.handle-active,.resize-handle-right.handle-active{cursor:col-resize}.resize-handle-top,.resize-handle-bottom{position:absolute;height:5px;width:100%}.resize-handle-top{top:0}.resize-handle-bottom{bottom:0}.resize-handle-left,.resize-handle-right{position:absolute;height:100%;width:5px}.resize-handle-left{left:0;top:0}.resize-handle-right{right:0;top:0}\n"], dependencies: [{ kind: "directive", type: i2.ResizableDirective, selector: "[mwlResizable]", inputs: ["validateResize", "enableGhostResize", "resizeSnapGrid", "resizeCursors", "ghostElementPositioning", "allowNegativeResizes", "mouseMoveThrottleMS"], outputs: ["resizeStart", "resizing", "resizeEnd"], exportAs: ["mwlResizable"] }, { kind: "directive", type: i2.ResizeHandleDirective, selector: "[mwlResizeHandle]", inputs: ["resizeEdges", "resizableContainer"] }, { kind: "directive", type: i3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: GlobalStyleComponent, selector: "global-style" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgTerminalComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-terminal', providers: [LinearRenderService], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Hierarchy: parent element(user) > host element(ng-terminal) > resize-box element > outer element -->\n<global-style></global-style>\n<div mwlResizable #resizeBox class=\"resize-box\" [validateResize]=\"validatorFactory()\" [enableGhostResize]=\"true\"\n (resizeEnd)=\"onResizeEnd($event.rectangle.left, $event.rectangle.top, $event.rectangle.width??0, $event.rectangle.height??0)\">\n <div #terminal class=\"terminal-outer\">\n </div>\n <div class=\"resize-handle resize-handle-top\" mwlResizeHandle [ngClass]=\"{'handle-active' : isDraggableOnEdgeActivated}\"\n [resizeEdges]=\"{ top: false }\"></div>\n <div class=\"resize-handle resize-handle-left\" mwlResizeHandle [ngClass]=\"{'handle-active' : isDraggableOnEdgeActivated}\"\n [resizeEdges]=\"{ left: false }\"></div>\n <div class=\"resize-handle resize-handle-right\" mwlResizeHandle [ngClass]=\"{'handle-active' : isDraggableOnEdgeActivated}\"\n [resizeEdges]=\"isDraggableOnEdgeActivated ? { right: true } : { right: false }\"></div>\n <div class=\"resize-handle resize-handle-bottom\" mwlResizeHandle [ngClass]=\"{'handle-active' : isDraggableOnEdgeActivated}\"\n [resizeEdges]=\"isDraggableOnEdgeActivated ? { bottom: true } : { bottom: false }\"></div>\n</div>\n<div #detectBox style=\"width: 100%; height: 0px; position: absolute;\">\n</div>", styles: [".terminal-outer{box-sizing:border-box;height:100%;width:100%;padding:5px}:host{display:block;height:100%;width:100%}.resize-box{box-sizing:border-box;height:100%;width:100%;position:relative}div>.handle-active{z-index:100;background-color:#85858a!important}.resize-handle{background-color:#000}.resize-handle-top.handle-active,.resize-handle-bottom.handle-active{cursor:row-resize}.resize-handle-left.handle-active,.resize-handle-right.handle-active{cursor:col-resize}.resize-handle-top,.resize-handle-bottom{position:absolute;height:5px;width:100%}.resize-handle-top{top:0}.resize-handle-bottom{bottom:0}.resize-handle-left,.resize-handle-right{position:absolute;height:100%;width:5px}.resize-handle-left{left:0;top:0}.resize-handle-right{right:0;top:0}\n"] }]
}], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: LoggingService }]; }, propDecorators: { keyInputEmitter: [{
type: Output,
args: ['keyInput']
}], keyEventEmitter: [{
type: Output,
args: ['keyEvent']
}], dataEmitter: [{
type: Output,
args: ['data']
}], keyEmitter: [{
type: Output,
args: ['key']
}], _dataSource: [{
type: Input,
args: ['dataSource']
}], _rowsInput: [{
type: Input,
args: ['rows']
}], _colsInput: [{
type: Input,
args: ['cols']
}], _minWidthInput: [{
type: Input,
args: ['minWidth']
}], _minHeightInput: [{
type: Input,
args: ['minHeight']
}], draggable: [{
type: Input,
args: ['draggable']
}], _xtermOptions: [{
type: Input,
args: ['xtermOptions']
}], terminalOuter: [{
type: ViewChild,
args: ['terminal', { static: true }]
}], resizeBox: [{
type: ViewChild,
args: ['resizeBox', { static: true }]
}], detectBox: [{
type: ViewChild,
args: ['detectBox', { static: true }]
}] } });
class NgTerminalModule {
static forRoot(config) {
return {
ngModule: NgTerminalModule,
providers: [
{ provide: ModuleOptions, useValue: config