fix(dees-modal): theming
This commit is contained in:
178
ts_web/elements/wysiwyg/dees-formatting-menu.ts
Normal file
178
ts_web/elements/wysiwyg/dees-formatting-menu.ts
Normal file
@ -0,0 +1,178 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
DeesElement,
|
||||
type TemplateResult,
|
||||
cssManager,
|
||||
css,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import { WysiwygFormatting } from './wysiwyg.formatting.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-formatting-menu': DeesFormattingMenu;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-formatting-menu')
|
||||
export class DeesFormattingMenu extends DeesElement {
|
||||
private static instance: DeesFormattingMenu;
|
||||
|
||||
public static getInstance(): DeesFormattingMenu {
|
||||
if (!DeesFormattingMenu.instance) {
|
||||
DeesFormattingMenu.instance = new DeesFormattingMenu();
|
||||
document.body.appendChild(DeesFormattingMenu.instance);
|
||||
}
|
||||
return DeesFormattingMenu.instance;
|
||||
}
|
||||
|
||||
@state()
|
||||
public visible: boolean = false;
|
||||
|
||||
@state()
|
||||
private position: { x: number; y: number } = { x: 0, y: 0 };
|
||||
|
||||
private callback: ((command: string) => void | Promise<void>) | null = null;
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.formatting-menu {
|
||||
position: absolute;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#262626')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#404040')};
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.15);
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
pointer-events: auto;
|
||||
user-select: none;
|
||||
animation: fadeInScale 0.15s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeInScale {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.format-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: all 0.15s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${cssManager.bdTheme('#000000', '#e0e0e0')};
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.format-button:hover {
|
||||
background: ${cssManager.bdTheme('#f0f0f0', '#333333')};
|
||||
color: ${cssManager.bdTheme('#0066cc', '#4d94ff')};
|
||||
}
|
||||
|
||||
.format-button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.format-button.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.format-button.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.format-button.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.format-button .code-icon {
|
||||
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.visible) return html``;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="formatting-menu"
|
||||
style="left: ${this.position.x}px; top: ${this.position.y}px;"
|
||||
tabindex="-1"
|
||||
@mousedown="${(e: MouseEvent) => {
|
||||
// Prevent focus loss
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}"
|
||||
@click="${(e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}"
|
||||
@focus="${(e: FocusEvent) => {
|
||||
// Prevent menu from taking focus
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}"
|
||||
>
|
||||
${WysiwygFormatting.formatButtons.map(button => html`
|
||||
<button
|
||||
class="format-button ${button.command}"
|
||||
@click="${() => this.applyFormat(button.command)}"
|
||||
title="${button.label}${button.shortcut ? ` (${button.shortcut})` : ''}"
|
||||
>
|
||||
<span class="${button.command === 'code' ? 'code-icon' : ''}">${button.icon}</span>
|
||||
</button>
|
||||
`)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private applyFormat(command: string): void {
|
||||
if (this.callback) {
|
||||
this.callback(command);
|
||||
}
|
||||
// Don't hide menu after applying format (except for link)
|
||||
if (command === 'link') {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
public show(position: { x: number; y: number }, callback: (command: string) => void | Promise<void>): void {
|
||||
this.position = position;
|
||||
this.callback = callback;
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this.visible = false;
|
||||
this.callback = null;
|
||||
}
|
||||
|
||||
public updatePosition(position: { x: number; y: number }): void {
|
||||
this.position = position;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user