Skip to main content
Glama
ContentSelector.component.ts3.66 kB
import { CommonModule } from '@angular/common'; import { Component, computed, type ElementRef, EventEmitter, HostListener, Input, type OnDestroy, type OnInit, Output, signal, ViewChild, } from '@angular/core'; const DEFAULT_PRESS_DETECT_DURATION = 250; @Component({ selector: 'app-content-selector', template: ` <span #containerRef role="button" tabindex="0" (click)="handleClick($event)" (mousedown)="handleMouseDown()" (mouseup)="handleMouseUp()" (mouseleave)="handleMouseUp()" (touchstart)="handleMouseDown()" (touchend)="handleMouseUp()" (touchcancel)="handleMouseUp()" (blur)="handleBlur()" (mouseenter)="onMouseEnter()" [ngStyle]="getContainerStyle()" > <ng-content></ng-content> </span> `, standalone: true, imports: [CommonModule], }) export class ContentSelectorComponent implements OnInit, OnDestroy { @Input() onClickOutside?: () => void; @Input() pressDuration?: number; @Input() isSelecting?: boolean; @Output() click = new EventEmitter<MouseEvent>(); @Output() press = new EventEmitter<void>(); @ViewChild('containerRef', { static: true }) containerRef!: ElementRef<HTMLSpanElement>; private isHovered = signal(false); private isSelectingState = signal(false); private pressTimerRef: ReturnType<typeof setTimeout> | null = null; private readonly isSelectingComputed = computed( () => this.isSelecting ?? this.isSelectingState() ); private readonly isStringSlot = computed(() => { // In Angular, we can't easily detect if content is just text // This would need to be determined differently or simplified return true; // Simplified for now }); ngOnInit(): void { this.isSelectingState.set(this.isSelecting ?? false); } ngOnDestroy(): void { this.clearPressTimer(); } @HostListener('document:mousedown', ['$event']) onDocumentMouseDown(event: MouseEvent): void { this.handleClickOutside(event); } private handleOnLongPress(): void { this.isSelectingState.set(true); this.press.emit(); } private startPressTimer(): void { this.pressTimerRef = setTimeout(() => { this.handleOnLongPress(); }, this.pressDuration ?? DEFAULT_PRESS_DETECT_DURATION); } private clearPressTimer(): void { if (this.pressTimerRef) { clearTimeout(this.pressTimerRef); this.pressTimerRef = null; } } handleMouseDown(): void { this.clearPressTimer(); this.startPressTimer(); } handleMouseUp(): void { this.isHovered.set(false); this.clearPressTimer(); } private handleClickOutside(event: MouseEvent): void { if ( this.containerRef?.nativeElement && !this.containerRef.nativeElement.contains(event.target as Node) ) { this.isSelectingState.set(false); this.onClickOutside?.(); } } handleClick(e: MouseEvent): void { if (this.isSelectingComputed()) { e.preventDefault(); e.stopPropagation(); } this.click.emit(e); } handleBlur(): void { this.isSelectingState.set(false); } onMouseEnter(): void { this.isHovered.set(true); } getContainerStyle(): { [key: string]: any } { return { display: this.isStringSlot() ? 'inline' : 'inline-block', cursor: 'pointer', userSelect: 'none', borderRadius: '0.375rem', outlineWidth: '2px', outlineOffset: '4px', outlineStyle: 'solid', outlineColor: this.isSelectingComputed() || this.isHovered() ? 'inherit' : 'transparent', transition: 'all 100ms 50ms ease-in-out', }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server