bootstrap 5 WIP (#7891)

New standard theme that follows your chosen terminal colors, Bootstrap 5 & Angular 15 upgrade
This commit is contained in:
Eugene
2023-02-26 20:42:31 +01:00
committed by GitHub
parent 7a8108b20d
commit 1e5cfd1d4b
215 changed files with 2982 additions and 2447 deletions

View File

@@ -24,7 +24,7 @@
"cli-spinner": "^0.2.10",
"dataurl": "0.1.0",
"hexer": "^1.5.0",
"ngx-colors": "^3.0.4",
"ngx-colors": "^3.4.0",
"patch-package": "^6.5.0",
"ps-node": "^0.1.6",
"runes": "^0.4.2",

View File

@@ -0,0 +1,15 @@
diff --git a/node_modules/ansi-color/lib/ansi-color.js b/node_modules/ansi-color/lib/ansi-color.js
index 1062c87..4fc2847 100644
--- a/node_modules/ansi-color/lib/ansi-color.js
+++ b/node_modules/ansi-color/lib/ansi-color.js
@@ -32,8 +32,8 @@ exports.set = function(str, color) {
var color_attrs = color.split("+");
var ansi_str = "";
for(var i=0, attr; attr = color_attrs[i]; i++) {
- ansi_str += "\033[" + ANSI_CODES[attr] + "m";
+ ansi_str += "\x1b[" + ANSI_CODES[attr] + "m";
}
- ansi_str += str + "\033[" + ANSI_CODES["off"] + "m";
+ ansi_str += str + "\x1b[" + ANSI_CODES["off"] + "m";
return ansi_str;
};

View File

@@ -1,9 +1,9 @@
import { Observable, Subject, first, auditTime } from 'rxjs'
import { Spinner } from 'cli-spinner'
import colors from 'ansi-colors'
import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags } from '@angular/core'
import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags, Component } from '@angular/core'
import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations'
import { AppService, ConfigService, BaseTabComponent, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer, MenuItemOptions, PlatformService, HostWindowService, ResettableTimeout, TranslateService } from 'tabby-core'
import { AppService, ConfigService, BaseTabComponent, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer, MenuItemOptions, PlatformService, HostWindowService, ResettableTimeout, TranslateService, ThemesService } from 'tabby-core'
import { BaseSession } from '../session'
@@ -17,6 +17,7 @@ import { MultifocusService } from '../services/multifocus.service'
/**
* A class to base your custom terminal tabs on
*/
@Component({ template: '' })
export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends BaseTabComponent implements OnInit, OnDestroy {
static template: string = require<string>('../components/baseTerminalTab.component.pug')
static styles: string[] = [require<string>('../components/baseTerminalTab.component.scss')]
@@ -121,6 +122,7 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
protected hostWindow: HostWindowService
protected translate: TranslateService
protected multifocus: MultifocusService
protected themes: ThemesService
// Deps end
protected logger: Logger
@@ -193,6 +195,7 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
this.hostWindow = injector.get(HostWindowService)
this.translate = injector.get(TranslateService)
this.multifocus = injector.get(MultifocusService)
this.themes = injector.get(ThemesService)
this.logger = this.log.create('baseTerminalTab')
this.setTitle(this.translate.instant('Terminal'))
@@ -512,7 +515,7 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
configure (): void {
this.frontend?.configure(this.profile)
if (this.config.store.terminal.background === 'colorScheme') {
if (!this.themes.findCurrentTheme().followsColorScheme && this.config.store.terminal.background === 'colorScheme') {
const scheme = this.profile.terminalColorScheme ?? this.config.store.terminal.colorScheme
if (scheme.background) {
this.backgroundColor = scheme.background
@@ -666,9 +669,9 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
let wheelDeltaY = 0
if ('wheelDeltaY' in event) {
wheelDeltaY = (event as WheelEvent)['wheelDeltaY']
wheelDeltaY = (event as unknown as WheelEvent)['wheelDeltaY']
} else {
wheelDeltaY = (event as WheelEvent).deltaY
wheelDeltaY = (event as unknown as WheelEvent).deltaY
}
if (event.altKey) {

View File

@@ -55,59 +55,76 @@ h3.mb-3(translate) Appearance
color-scheme-preview([scheme]='config.store.terminal.colorScheme', [fontPreview]='true')
.content-box
.form-line
.form-line(*ngIf='!themes.findCurrentTheme().followsColorScheme')
.header
.title(translate) Terminal background
.btn-group(
[(ngModel)]='config.store.terminal.background',
(ngModelChange)='config.save()',
ngbRadioGroup
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"theme"'
)
.btn-group(role='group')
input.btn-check(
type='radio',
name='background',
[(ngModel)]='config.store.terminal.background',
(ngModelChange)='config.save()',
id='backgroundTheme',
[value]='"theme"'
)
label.btn.btn-secondary(
for='backgroundTheme'
)
span(translate) From theme
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"colorScheme"'
)
input.btn-check(
type='radio',
name='background',
[(ngModel)]='config.store.terminal.background',
(ngModelChange)='config.save()',
id='backgroundColorScheme',
[value]='"colorScheme"'
)
label.btn.btn-secondary(
for='backgroundColorScheme'
)
span(translate) From color scheme
.form-line
.header
.title(translate) Cursor shape
.btn-group(
[(ngModel)]='config.store.terminal.cursor',
(ngModelChange)='config.save()',
ngbRadioGroup
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"block"'
)
.btn-group(role='group')
input.btn-check(
type='radio',
name='cursor',
[(ngModel)]='config.store.terminal.cursor',
(ngModelChange)='config.save()',
id='cursorBlock',
[value]='"block"'
)
label.btn.btn-secondary(
for='cursorBlock'
)
| █
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"beam"'
)
input.btn-check(
type='radio',
name='cursor',
[(ngModel)]='config.store.terminal.cursor',
(ngModelChange)='config.save()',
id='cursorBeam',
[value]='"beam"'
)
label.btn.btn-secondary(
for='cursorBeam'
)
| |
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"underline"'
)
input.btn-check(
type='radio',
name='cursor',
[(ngModel)]='config.store.terminal.cursor',
(ngModelChange)='config.save()',
id='cursorUnderline',
[value]='"underline"'
)
label.btn.btn-secondary(
for='cursorUnderline'
)
| ▁
.form-line

View File

@@ -3,18 +3,19 @@ import { Observable, debounceTime, distinctUntilChanged, map } from 'rxjs'
import { debounce } from 'utils-decorators/dist/esm/debounce/debounce'
import { Component } from '@angular/core'
import { ConfigService, getCSSFontFamily, PlatformService } from 'tabby-core'
import { ConfigService, getCSSFontFamily, PlatformService, ThemesService } from 'tabby-core'
/** @hidden */
@Component({
template: require('./appearanceSettingsTab.component.pug'),
styles: [require('./appearanceSettingsTab.component.scss')],
templateUrl:'./appearanceSettingsTab.component.pug',
styleUrls: ['./appearanceSettingsTab.component.scss'],
})
export class AppearanceSettingsTabComponent {
fonts: string[] = []
constructor (
public config: ConfigService,
public themes: ThemesService,
private platform: PlatformService,
) { }

View File

@@ -1,3 +1,7 @@
:host {
display: inline-block;
}
div {
width: 32px;
height: 32px;

View File

@@ -3,8 +3,8 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'
/** @hidden */
@Component({
selector: 'color-picker',
template: require('./colorPicker.component.pug'),
styles: [require('./colorPicker.component.scss')],
templateUrl:'./colorPicker.component.pug',
styleUrls: ['./colorPicker.component.scss'],
})
export class ColorPickerComponent {
@Input() model: string

View File

@@ -5,8 +5,8 @@ import { TerminalColorScheme } from '../api/interfaces'
/** @hidden */
@Component({
selector: 'color-scheme-preview',
template: require('./colorSchemePreview.component.pug'),
styles: [require('./colorSchemePreview.component.scss')],
templateUrl:'./colorSchemePreview.component.pug',
styleUrls: ['./colorSchemePreview.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ColorSchemePreviewComponent extends BaseComponent {

View File

@@ -1,31 +1,29 @@
.head
.bg-dark.p-3.mb-4(*ngIf='model')
.bg-dark.p-3.mb-4(*ngIf='model != null')
.d-flex.align-items-center
span {{model.name}}
.mr-auto
.me-auto
a.btn-link((click)='selectScheme(null); $event.preventDefault()', href='#', translate) Clear
color-scheme-preview([scheme]='model')
.input-group.mb-3
.input-group-prepend
.input-group-text
i.fas.fa-fw.fa-search
.input-group-text
i.fas.fa-fw.fa-search
input.form-control(type='search', [placeholder]='"Search color schemes"|translate', [(ngModel)]='filter')
.body
.list-group-light.mb-3
.list-group.list-group-light.mb-3
ng-container(*ngFor='let scheme of allColorSchemes')
.list-group-item.list-group-item-action(
[hidden]='filter && !scheme.name.toLowerCase().includes(filter.toLowerCase())',
(click)='selectScheme(scheme)',
[class.active]='(currentCustomScheme || currentStockScheme) === scheme'
(click)='selectScheme(scheme)'
)
.d-flex.w-100.align-items-center
i.fas.fa-fw([class.fa-check]='model?.name === scheme.name')
.ml-2
.ms-2
.mr-auto {{scheme.name}}
.me-auto {{scheme.name}}
color-scheme-preview([scheme]='scheme')

View File

@@ -11,7 +11,7 @@ _('Search color schemes')
/** @hidden */
@Component({
selector: 'color-scheme-selector',
template: require('./colorSchemeSelector.component.pug'),
templateUrl:'./colorSchemeSelector.component.pug',
styles: [`
:host {
display: block;
@@ -45,7 +45,7 @@ export class ColorSchemeSelectorComponent {
this.changeDetector.markForCheck()
}
selectScheme (scheme: TerminalColorScheme) {
selectScheme (scheme: TerminalColorScheme|null) {
this.model = scheme
this.modelChange.emit(scheme)
this.changeDetector.markForCheck()

View File

@@ -3,12 +3,12 @@
.d-flex.align-items-center(*ngIf='!editing')
span {{getCurrentSchemeName()}}
.mr-auto
.me-auto
.btn-toolbar
button.btn.btn-secondary((click)='editScheme()')
i.fas.fa-pen
span(translate) Edit
.mr-1
.me-1
button.btn.btn-danger(
(click)='deleteScheme(config.store.terminal.colorScheme)',
*ngIf='currentCustomScheme'
@@ -17,11 +17,11 @@
span(translate) Delete
div(*ngIf='editing')
.form-group
.mb-3
label(translate) Name
input.form-control(type='text', [(ngModel)]='config.store.terminal.colorScheme.name')
.form-group
.mb-3
color-picker(
[(model)]='config.store.terminal.colorScheme.foreground',
(modelChange)='config.save()',
@@ -69,11 +69,11 @@
color-scheme-preview([scheme]='config.store.terminal.colorScheme')
.btn-toolbar.d-flex.mt-2(*ngIf='editing')
.mr-auto
.me-auto
button.btn.btn-primary((click)='saveScheme()')
i.fas.fa-check
span(translate) Save
.mr-1
.me-1
button.btn.btn-secondary((click)='cancelEditing()')
i.fas.fa-times
span(translate) Cancel
@@ -81,13 +81,12 @@
hr.mt-3.mb-4
.input-group.mb-3
.input-group-prepend
.input-group-text
i.fas.fa-fw.fa-search
.input-group-text
i.fas.fa-fw.fa-search
input.form-control(type='search', [placeholder]='"Search color schemes"|translate', [(ngModel)]='filter')
.body
.list-group-light.mb-3
.list-group.list-group-light.mb-3
ng-container(*ngFor='let scheme of allColorSchemes')
.list-group-item.list-group-item-action(
[hidden]='filter && !scheme.name.toLowerCase().includes(filter.toLowerCase())',
@@ -97,11 +96,11 @@
.d-flex.w-100.align-items-center
i.fas.fa-fw([class.fa-check]='(currentCustomScheme || currentStockScheme) === scheme')
.ml-2
.ms-2
.mr-auto
.me-auto
span {{scheme.name}}
.badge.badge-info.ml-2(*ngIf='customColorSchemes.includes(scheme)', translate) Custom
.badge.text-bg-info.ms-2(*ngIf='customColorSchemes.includes(scheme)', translate) Custom
div
.d-flex

View File

@@ -11,8 +11,8 @@ _('Search color schemes')
/** @hidden */
@Component({
template: require('./colorSchemeSettingsTab.component.pug'),
styles: [require('./colorSchemeSettingsTab.component.scss')],
templateUrl:'./colorSchemeSettingsTab.component.pug',
styleUrls: ['./colorSchemeSettingsTab.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ColorSchemeSettingsTabComponent {

View File

@@ -1,4 +1,5 @@
div([sortablejs]='scripts')
div
//TODO ([sortablejs]='scripts')
.input-group.flex-grow-1(*ngFor='let script of scripts')
input.form-control(
type='text',
@@ -10,7 +11,7 @@ div([sortablejs]='scripts')
placeholder='Send',
[(ngModel)]='script.send'
)
.input-group-append.input-group-text.p-0.border-0
.input-group-text.p-0.border-0
.hover-reveal(ngbDropdown)
button.btn.btn-link.btn-sm(ngbDropdownToggle)
i.fas.fa-fw.fa-cog

View File

@@ -7,7 +7,7 @@ import { LoginScript, LoginScriptsOptions } from '../middleware/loginScriptProce
/** @hidden */
@Component({
selector: 'login-scripts-settings',
template: require('./loginScriptsSettings.component.pug'),
templateUrl:'./loginScriptsSettings.component.pug',
})
export class LoginScriptsSettingsComponent {
@Input() options: LoginScriptsOptions

View File

@@ -9,8 +9,7 @@
(keyup.esc)='close.emit()',
[placeholder]='"Search"|translate'
)
.input-group-append(*ngIf='state.resultCount > 0')
.input-group-text.result-counter {{state.resultIndex + 1}} / {{state.resultCount}}
.input-group-text.result-counter(*ngIf='state.resultCount > 0') {{state.resultIndex ?? 0 + 1}} / {{state.resultCount}}
ng-container(*ngIf='state.resultCount > 0')
button.btn.btn-link(
@@ -27,7 +26,7 @@ ng-container(*ngIf='state.resultCount > 0')
[fastHtmlBind]='icons.arrowDown'
)
.mr-2
.me-2
button.btn.btn-link(
(click)='options.caseSensitive = !options.caseSensitive; saveSearchOptions()',
@@ -53,7 +52,7 @@ button.btn.btn-link(
[fastHtmlBind]='icons.wholeWord'
)
.mr-2
.me-2
button.btn.btn-link(
(click)='close.emit()',

View File

@@ -5,8 +5,8 @@ import { ConfigService, NotificationsService, TranslateService } from 'tabby-cor
@Component({
selector: 'search-panel',
template: require('./searchPanel.component.pug'),
styles: [require('./searchPanel.component.scss')],
templateUrl:'./searchPanel.component.pug',
styleUrls: ['./searchPanel.component.scss'],
})
export class SearchPanelComponent {
@Input() query: string

View File

@@ -6,7 +6,7 @@ import { StreamProcessingOptions } from '../middleware/streamProcessing'
/** @hidden */
@Component({
selector: 'stream-processing-settings',
template: require('./streamProcessingSettings.component.pug'),
templateUrl:'./streamProcessingSettings.component.pug',
})
export class StreamProcessingSettingsComponent {
@Input() options: StreamProcessingOptions

View File

@@ -168,35 +168,40 @@ div.mt-4
.form-line
.header
.title(translate) Terminal bell
.btn-group(
[(ngModel)]='config.store.terminal.bell',
(ngModelChange)='config.save()',
ngbRadioGroup
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"off"'
)
.btn-group
input.btn-check(
type='radio',
name='bell',
[(ngModel)]='config.store.terminal.bell',
(ngModelChange)='config.save()',
id='bellOff',
[value]='"off"'
)
label.btn.btn-secondary(for='bellOff')
span(translate) Off
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"visual"'
)
input.btn-check(
type='radio',
name='bell',
[(ngModel)]='config.store.terminal.bell',
(ngModelChange)='config.save()',
id='bellVisual',
[value]='"visual"'
)
label.btn.btn-secondary(for='bellVisual')
span(translate) Visual
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"audible"'
)
input.btn-check(
type='radio',
name='bell',
[(ngModel)]='config.store.terminal.bell',
(ngModelChange)='config.save()',
id='bellAudible',
[value]='"audible"'
)
label.btn.btn-secondary(for='bellAudible')
span(translate) Audible
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.bell != "audible" && (config.store.terminal.profile || "").startsWith("wsl")')
.mr-auto(translate) WSL terminal bell can only be muted via Volume Mixer
.me-auto(translate) WSL terminal bell can only be muted via Volume Mixer
button.btn.btn-secondary((click)='openWSLVolumeMixer()', translate) Show Mixer
.mt-4

View File

@@ -3,7 +3,7 @@ import { ConfigService, HostAppService, Platform, PlatformService, altKeyName, m
/** @hidden */
@Component({
template: require('./terminalSettingsTab.component.pug'),
templateUrl:'./terminalSettingsTab.component.pug',
})
export class TerminalSettingsTabComponent {
Platform = Platform

View File

@@ -1,8 +1,13 @@
:host {
background: #000000bf;
padding: 5px 15px 5px 15px;
display: flex;
z-index: 3;
::ng-deep .btn {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.content {
@@ -16,9 +21,3 @@
cursor: move;
opacity: .3;
}
::ng-deep .btn {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}

View File

@@ -6,8 +6,8 @@ import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
/** @hidden */
@Component({
selector: 'terminal-toolbar',
template: require('./terminalToolbar.component.pug'),
styles: [require('./terminalToolbar.component.scss')],
templateUrl:'./terminalToolbar.component.pug',
styleUrls: ['./terminalToolbar.component.scss'],
})
export class TerminalToolbarComponent {
@Input() tab: BaseTerminalTabComponent<any>

View File

@@ -1,6 +1,6 @@
import { BehaviorSubject, filter, firstValueFrom, takeUntil } from 'rxjs'
import { Injector } from '@angular/core'
import { ConfigService, getCSSFontFamily, HostAppService, HotkeysService, Platform, PlatformService } from 'tabby-core'
import { ConfigService, getCSSFontFamily, HostAppService, HotkeysService, Platform, PlatformService, ThemesService } from 'tabby-core'
import { Frontend, SearchOptions, SearchState } from './frontend'
import { Terminal, ITheme } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
@@ -87,6 +87,7 @@ export class XTermFrontend extends Frontend {
private hotkeysService: HotkeysService
private platformService: PlatformService
private hostApp: HostAppService
private themes: ThemesService
constructor (injector: Injector) {
super(injector)
@@ -94,6 +95,7 @@ export class XTermFrontend extends Frontend {
this.hotkeysService = injector.get(HotkeysService)
this.platformService = injector.get(PlatformService)
this.hostApp = injector.get(HostAppService)
this.themes = injector.get(ThemesService)
this.xterm = new Terminal({
allowTransparency: true,
@@ -363,7 +365,7 @@ export class XTermFrontend extends Frontend {
foreground: scheme!.foreground,
selectionBackground: scheme!.selection ?? '#88888888',
selectionForeground: scheme!.selectionForeground ?? undefined,
background: config.terminal.background === 'colorScheme' ? scheme!.background : '#00000000',
background: !this.themes.findCurrentTheme().followsColorScheme && config.terminal.background === 'colorScheme' ? scheme!.background : '#00000000',
cursor: scheme!.cursor,
cursorAccent: scheme!.cursorAccent,
}

View File

@@ -1,5 +1,5 @@
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { ToastrModule } from 'ngx-toastr'
@@ -37,7 +37,7 @@ import { TerminalCLIHandler } from './cli'
/** @hidden */
@NgModule({
imports: [
BrowserModule,
CommonModule,
FormsModule,
NgbModule,
ToastrModule,
@@ -61,12 +61,6 @@ import { TerminalCLIHandler } from './cli'
{ provide: CLIHandler, useClass: TerminalCLIHandler, multi: true },
],
entryComponents: [
AppearanceSettingsTabComponent,
ColorSchemeSettingsTabComponent,
TerminalSettingsTabComponent,
ColorSchemeSelectorComponent,
],
declarations: [
ColorPickerComponent,
ColorSchemePreviewComponent,

View File

@@ -1,15 +0,0 @@
const config = require('../webpack.plugin.config')
module.exports = config({
name: 'terminal',
dirname: __dirname,
externals: [
'opentype.js',
],
rules: [
{
test: /lib[\\/]xterm-addon-image-worker.js$/i,
type: 'asset/source',
},
],
})
module.exports.resolve.modules.push('node_modules/xterm/src')

View File

@@ -0,0 +1,23 @@
import * as path from 'path'
import * as url from 'url'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
import config from '../webpack.plugin.config.mjs'
export default () => {
const cfg = config({
name: 'terminal',
dirname: __dirname,
externals: [
'opentype.js',
],
rules: [
{
test: /lib[\\/]xterm-addon-image-worker.js$/i,
type: 'asset/source',
},
],
})
cfg.resolve.modules.push('node_modules/xterm/src')
return cfg
}

View File

@@ -293,10 +293,10 @@ minimist@^1.2.6:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
ngx-colors@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/ngx-colors/-/ngx-colors-3.0.4.tgz#69b760760e6e1e92fda5da51fa9b4bea7e555d40"
integrity sha512-peNvVpYkm8pe3nP8/TbaFFqo/RxZevGljKrzFa2g1hPPacdx+WdfwAN4uJfcGk7qTYbqlV64SSfb3Pnx8qjzDA==
ngx-colors@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/ngx-colors/-/ngx-colors-3.4.0.tgz#522d37563202053bd5b68ce8726afbfddf2f83a5"
integrity sha512-aJBpOC7t6DUDcEfkyFo6+6JzdHH82hCuj+zdqAmdrFTMyOVFZCw0FhCfcEuQRNsR1yYtyMRhSs6zUXr7oTiNlw==
dependencies:
tslib "^2.0.0"