feat(terminal): add dynamic bright/dark theming for terminal components and terminal preview
This commit is contained in:
BIN
.playwright-mcp/dees-workspace-bright-theme.png
Normal file
BIN
.playwright-mcp/dees-workspace-bright-theme.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
BIN
.playwright-mcp/dees-workspace-dark-theme.png
Normal file
BIN
.playwright-mcp/dees-workspace-dark-theme.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
10
changelog.md
10
changelog.md
@@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-12-31 - 3.21.0 - feat(terminal)
|
||||||
|
add dynamic bright/dark theming for terminal components and terminal preview
|
||||||
|
|
||||||
|
- Add bright/dark theme PNG assets under .playwright-mcp for previews.
|
||||||
|
- Replace hardcoded terminal background/colors with cssManager.bdTheme in workspace terminal and preview styles.
|
||||||
|
- Introduce getTerminalTheme helper to compute xterm theme for bright/dark modes.
|
||||||
|
- Subscribe to themeManager.themeObservable and apply updates to xterm (terminal.options.theme) so terminals update live on theme change.
|
||||||
|
- Remove hardcoded background property/CSS var and unused background accessor from workspace terminal.
|
||||||
|
- Ensure proper cleanup: unsubscribe theme subscriptions and dispose terminals in disconnectedCallback.
|
||||||
|
|
||||||
## 2025-12-31 - 3.20.1 - fix(dees-workspace)
|
## 2025-12-31 - 3.20.1 - fix(dees-workspace)
|
||||||
fix demo wrapper and workspace layout; always render terminal preview
|
fix demo wrapper and workspace layout; always render terminal preview
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-catalog',
|
name: '@design.estate/dees-catalog',
|
||||||
version: '3.20.1',
|
version: '3.21.0',
|
||||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -417,7 +417,6 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
terminal.style.opacity = '0';
|
terminal.style.opacity = '0';
|
||||||
terminal.style.transform = 'translateY(8px) scale(0.99)';
|
terminal.style.transform = 'translateY(8px) scale(0.99)';
|
||||||
terminal.style.transition = 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)';
|
terminal.style.transition = 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)';
|
||||||
terminal.background = 'hsl(220 13% 8%)';
|
|
||||||
terminal.style.boxShadow = '0 25px 50px -12px rgb(0 0 0 / 0.5), 0 0 0 1px rgb(255 255 255 / 0.05)';
|
terminal.style.boxShadow = '0 25px 50px -12px rgb(0 0 0 / 0.5), 0 0 0 1px rgb(255 255 255 / 0.05)';
|
||||||
terminal.style.maxWidth = `calc(${maincontainer.clientWidth}px -240px)`;
|
terminal.style.maxWidth = `calc(${maincontainer.clientWidth}px -240px)`;
|
||||||
terminal.style.maxHeight = `calc(${maincontainer.clientHeight}px - 24px)`;
|
terminal.style.maxHeight = `calc(${maincontainer.clientHeight}px - 24px)`;
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
|||||||
private fitAddon: FitAddon | null = null;
|
private fitAddon: FitAddon | null = null;
|
||||||
private lastLineCount: number = 0;
|
private lastLineCount: number = 0;
|
||||||
private resizeObserver: ResizeObserver | null = null;
|
private resizeObserver: ResizeObserver | null = null;
|
||||||
|
private terminalThemeSubscription: any = null;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
themeDefaultStyles,
|
themeDefaultStyles,
|
||||||
@@ -69,8 +70,8 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #000000;
|
background: ${cssManager.bdTheme('#ffffff', '#000000')};
|
||||||
border: 1px solid hsl(0 0% 20%);
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -80,20 +81,20 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
background: hsl(0 0% 10%);
|
background: ${cssManager.bdTheme('hsl(0 0% 96%)', 'hsl(0 0% 10%)')};
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
||||||
color: hsl(0 0% 60%);
|
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')};
|
||||||
border-bottom: 1px solid hsl(0 0% 20%);
|
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal-header-icon {
|
.terminal-header-icon {
|
||||||
color: hsl(0 0% 50%);
|
color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 50%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal-header-command {
|
.terminal-header-command {
|
||||||
color: hsl(0 0% 80%);
|
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 80%)')};
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,8 +149,8 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.xterm .composition-view {
|
.xterm .composition-view {
|
||||||
background: #000000;
|
background: ${cssManager.bdTheme('#ffffff', '#000000')};
|
||||||
color: #fff;
|
color: ${cssManager.bdTheme('#333333', '#ffffff')};
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -161,7 +162,7 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.xterm .xterm-viewport {
|
.xterm .xterm-viewport {
|
||||||
background-color: #000000;
|
background-color: ${cssManager.bdTheme('#ffffff', '#000000')};
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -243,16 +244,16 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.xterm .xterm-viewport::-webkit-scrollbar-track {
|
.xterm .xterm-viewport::-webkit-scrollbar-track {
|
||||||
background: hsl(0 0% 8%);
|
background: ${cssManager.bdTheme('hsl(0 0% 96%)', 'hsl(0 0% 8%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.xterm .xterm-viewport::-webkit-scrollbar-thumb {
|
.xterm .xterm-viewport::-webkit-scrollbar-thumb {
|
||||||
background: hsl(0 0% 25%);
|
background: ${cssManager.bdTheme('hsl(0 0% 80%)', 'hsl(0 0% 25%)')};
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xterm .xterm-viewport::-webkit-scrollbar-thumb:hover {
|
.xterm .xterm-viewport::-webkit-scrollbar-thumb:hover {
|
||||||
background: hsl(0 0% 35%);
|
background: ${cssManager.bdTheme('hsl(0 0% 70%)', 'hsl(0 0% 35%)')};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@@ -271,6 +272,27 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get terminal theme colors based on bright/dark mode
|
||||||
|
*/
|
||||||
|
private getTerminalTheme(isBright: boolean) {
|
||||||
|
return isBright
|
||||||
|
? {
|
||||||
|
background: '#ffffff',
|
||||||
|
foreground: '#333333',
|
||||||
|
cursor: '#333333',
|
||||||
|
cursorAccent: '#ffffff',
|
||||||
|
selectionBackground: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
background: '#000000',
|
||||||
|
foreground: '#cccccc',
|
||||||
|
cursor: '#cccccc',
|
||||||
|
cursorAccent: '#000000',
|
||||||
|
selectionBackground: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async firstUpdated(
|
public async firstUpdated(
|
||||||
_changedProperties: Map<string | number | symbol, unknown>
|
_changedProperties: Map<string | number | symbol, unknown>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@@ -279,6 +301,10 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
|||||||
const container = this.shadowRoot?.getElementById('xterm-container');
|
const container = this.shadowRoot?.getElementById('xterm-container');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
|
// Get current theme from domtools
|
||||||
|
const domtoolsInstance = await this.domtoolsPromise;
|
||||||
|
const isBright = domtoolsInstance.themeManager.goBrightBoolean;
|
||||||
|
|
||||||
// Create xterm terminal in read-only mode
|
// Create xterm terminal in read-only mode
|
||||||
this.terminal = new Terminal({
|
this.terminal = new Terminal({
|
||||||
convertEol: true,
|
convertEol: true,
|
||||||
@@ -286,13 +312,17 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
|||||||
disableStdin: true,
|
disableStdin: true,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontFamily: "'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace",
|
fontFamily: "'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace",
|
||||||
theme: {
|
theme: this.getTerminalTheme(isBright),
|
||||||
background: '#000000',
|
|
||||||
foreground: '#cccccc',
|
|
||||||
},
|
|
||||||
scrollback: 1000,
|
scrollback: 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Subscribe to theme changes
|
||||||
|
this.terminalThemeSubscription = domtoolsInstance.themeManager.themeObservable.subscribe((goBright: boolean) => {
|
||||||
|
if (this.terminal) {
|
||||||
|
this.terminal.options.theme = this.getTerminalTheme(goBright);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.fitAddon = new FitAddon();
|
this.fitAddon = new FitAddon();
|
||||||
this.terminal.loadAddon(this.fitAddon);
|
this.terminal.loadAddon(this.fitAddon);
|
||||||
this.terminal.open(container);
|
this.terminal.open(container);
|
||||||
@@ -334,6 +364,10 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
|||||||
this.resizeObserver.disconnect();
|
this.resizeObserver.disconnect();
|
||||||
this.resizeObserver = null;
|
this.resizeObserver = null;
|
||||||
}
|
}
|
||||||
|
if (this.terminalThemeSubscription) {
|
||||||
|
this.terminalThemeSubscription.unsubscribe();
|
||||||
|
this.terminalThemeSubscription = null;
|
||||||
|
}
|
||||||
if (this.terminal) {
|
if (this.terminal) {
|
||||||
this.terminal.dispose();
|
this.terminal.dispose();
|
||||||
this.terminal = null;
|
this.terminal = null;
|
||||||
|
|||||||
@@ -47,9 +47,6 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
@property()
|
@property()
|
||||||
accessor environmentVariables: { [key: string]: string } = {};
|
accessor environmentVariables: { [key: string]: string } = {};
|
||||||
|
|
||||||
@property()
|
|
||||||
accessor background: string = '#000000';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Promise that resolves when the environment is ready.
|
* Promise that resolves when the environment is ready.
|
||||||
* @deprecated Use executionEnvironment directly
|
* @deprecated Use executionEnvironment directly
|
||||||
@@ -57,6 +54,9 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
private environmentDeferred = new domtools.plugins.smartpromise.Deferred<IExecutionEnvironment>();
|
private environmentDeferred = new domtools.plugins.smartpromise.Deferred<IExecutionEnvironment>();
|
||||||
public environmentPromise = this.environmentDeferred.promise;
|
public environmentPromise = this.environmentDeferred.promise;
|
||||||
|
|
||||||
|
// Theme subscription for dynamic theme updates
|
||||||
|
private terminalThemeSubscription: any = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.resizeObserver = new ResizeObserver((entries) => {
|
this.resizeObserver = new ResizeObserver((entries) => {
|
||||||
@@ -72,10 +72,9 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
themeDefaultStyles,
|
themeDefaultStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
|
||||||
:host {
|
:host {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background: var(--dees-terminal-background, #000000);
|
background: ${cssManager.bdTheme('#ffffff', '#000000')};
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -170,8 +169,8 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
|
|
||||||
.xterm .composition-view {
|
.xterm .composition-view {
|
||||||
/* TODO: Composition position got messed up somewhere */
|
/* TODO: Composition position got messed up somewhere */
|
||||||
background: var(--dees-terminal-background, #000000);
|
background: ${cssManager.bdTheme('#ffffff', '#000000')};
|
||||||
color: #fff;
|
color: ${cssManager.bdTheme('#333333', '#ffffff')};
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -184,7 +183,7 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
|
|
||||||
.xterm .xterm-viewport {
|
.xterm .xterm-viewport {
|
||||||
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
||||||
background-color: var(--dees-terminal-background, #000000);
|
background-color: ${cssManager.bdTheme('#ffffff', '#000000')};
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -275,25 +274,51 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
private fitAddon: FitAddon;
|
private fitAddon: FitAddon;
|
||||||
private terminal: Terminal | null = null;
|
private terminal: Terminal | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get terminal theme colors based on bright/dark mode
|
||||||
|
*/
|
||||||
|
private getTerminalTheme(isBright: boolean) {
|
||||||
|
return isBright
|
||||||
|
? {
|
||||||
|
background: '#ffffff',
|
||||||
|
foreground: '#333333',
|
||||||
|
cursor: '#333333',
|
||||||
|
cursorAccent: '#ffffff',
|
||||||
|
selectionBackground: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
background: '#000000',
|
||||||
|
foreground: '#cccccc',
|
||||||
|
cursor: '#cccccc',
|
||||||
|
cursorAccent: '#000000',
|
||||||
|
selectionBackground: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async firstUpdated(
|
public async firstUpdated(
|
||||||
_changedProperties: Map<string | number | symbol, unknown>
|
_changedProperties: Map<string | number | symbol, unknown>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const domtools = await this.domtoolsPromise;
|
const domtoolsInstance = await this.domtoolsPromise;
|
||||||
super.firstUpdated(_changedProperties);
|
super.firstUpdated(_changedProperties);
|
||||||
|
|
||||||
// Sync CSS variable with background property
|
// Get current theme
|
||||||
this.style.setProperty('--dees-terminal-background', this.background);
|
const isBright = domtoolsInstance.themeManager.goBrightBoolean;
|
||||||
|
|
||||||
const container = this.shadowRoot.getElementById('container');
|
const container = this.shadowRoot.getElementById('container');
|
||||||
|
|
||||||
const term = new Terminal({
|
const term = new Terminal({
|
||||||
convertEol: true,
|
convertEol: true,
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
theme: {
|
theme: this.getTerminalTheme(isBright),
|
||||||
background: this.background,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
this.terminal = term;
|
this.terminal = term;
|
||||||
|
|
||||||
|
// Subscribe to theme changes
|
||||||
|
this.terminalThemeSubscription = domtoolsInstance.themeManager.themeObservable.subscribe((goBright: boolean) => {
|
||||||
|
if (this.terminal) {
|
||||||
|
this.terminal.options.theme = this.getTerminalTheme(goBright);
|
||||||
|
}
|
||||||
|
});
|
||||||
this.fitAddon = new FitAddon();
|
this.fitAddon = new FitAddon();
|
||||||
term.loadAddon(this.fitAddon);
|
term.loadAddon(this.fitAddon);
|
||||||
|
|
||||||
@@ -383,6 +408,14 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
|
|
||||||
async disconnectedCallback(): Promise<void> {
|
async disconnectedCallback(): Promise<void> {
|
||||||
this.resizeObserver.unobserve(this);
|
this.resizeObserver.unobserve(this);
|
||||||
|
if (this.terminalThemeSubscription) {
|
||||||
|
this.terminalThemeSubscription.unsubscribe();
|
||||||
|
this.terminalThemeSubscription = null;
|
||||||
|
}
|
||||||
|
if (this.terminal) {
|
||||||
|
this.terminal.dispose();
|
||||||
|
this.terminal = null;
|
||||||
|
}
|
||||||
await super.disconnectedCallback();
|
await super.disconnectedCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user