Files
rich-text-elements-ui/src/components/rt-toolbar/rt-toolbar.component.ts
Giuliano Silvestro 30775d5a01 feat: initial rich-text-elements-ui library implementation
TipTap-powered rich text editing library with WYSIWYG editor, markdown editor,
template editor, collaboration support (Yjs), mentions, track changes,
comments, code blocks, and table insertion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:33:25 +10:00

97 lines
3.4 KiB
TypeScript

import {
Component,
ChangeDetectionStrategy,
input,
output,
inject,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ButtonComponent, IconComponent, IconRegistry, SelectComponent, type SelectOption, TooltipDirective } from '@sda/base-ui';
import type { RtToolbarGroup, RtToolbarAction } from '../../types/toolbar.types';
import type { RtActiveMarks, RtActiveNodes } from '../../types/editor.types';
import { registerRtIcons } from '../../utils/icons.utils';
@Component({
selector: 'rt-toolbar',
standalone: true,
imports: [CommonModule, FormsModule, ButtonComponent, IconComponent, SelectComponent, TooltipDirective],
templateUrl: './rt-toolbar.component.html',
styleUrl: './rt-toolbar.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RtToolbarComponent {
readonly groups = input.required<RtToolbarGroup[]>();
readonly activeMarks = input.required<RtActiveMarks>();
readonly activeNodes = input.required<RtActiveNodes>();
readonly canUndo = input(false);
readonly canRedo = input(false);
readonly action = output<{ action: RtToolbarAction; value?: string }>();
readonly headingOptions: SelectOption[] = [
{ value: '0', label: 'Normal text' },
{ value: '1', label: 'Heading 1' },
{ value: '2', label: 'Heading 2' },
{ value: '3', label: 'Heading 3' },
{ value: '4', label: 'Heading 4' },
];
constructor() {
registerRtIcons(inject(IconRegistry));
}
/** Check if a toolbar action is currently active */
isActive(actionName: RtToolbarAction): boolean {
const marks = this.activeMarks();
const nodes = this.activeNodes();
switch (actionName) {
case 'bold': return marks.bold;
case 'italic': return marks.italic;
case 'underline': return marks.underline;
case 'strikethrough': return marks.strike;
case 'code': return marks.code;
case 'subscript': return marks.subscript;
case 'superscript': return marks.superscript;
case 'link': return marks.link;
case 'highlight-color': return marks.highlight;
case 'bullet-list': return nodes.bulletList;
case 'ordered-list': return nodes.orderedList;
case 'task-list': return nodes.taskList;
case 'blockquote': return nodes.blockquote;
case 'code-block': return nodes.codeBlock;
case 'align-left': return nodes.textAlign === 'left';
case 'align-center': return nodes.textAlign === 'center';
case 'align-right': return nodes.textAlign === 'right';
case 'align-justify': return nodes.textAlign === 'justify';
default: return false;
}
}
/** Check if a toolbar action is disabled */
isDisabled(actionName: RtToolbarAction): boolean {
switch (actionName) {
case 'undo': return !this.canUndo();
case 'redo': return !this.canRedo();
default: return false;
}
}
/** Get the current heading level as a string value */
getCurrentHeadingValue(): string {
const heading = this.activeNodes().heading;
return heading ? String(heading) : '0';
}
/** Emit action event */
onAction(actionName: RtToolbarAction, value?: string): void {
this.action.emit({ action: actionName, value });
}
/** Handle heading select change */
onHeadingChange(value: string | number): void {
this.action.emit({ action: 'heading', value: String(value) });
}
}