import { Component, EventEmitter, Input, Output } from '@angular/core'; @Component({ selector: 'vsp-number-stepper', template: `
@if (unit) { {{ unit }} }
`, }) export class VspNumberStepper { @Input() value = 0; @Output() valueChange = new EventEmitter(); @Input() min?: number; @Input() max?: number; @Input() step = 1; @Input() unit?: string; @Input() disabled = false; @Input() id?: string; set(v: number): void { let n = v; if (this.min != null && n < this.min) n = this.min; if (this.max != null && n > this.max) n = this.max; this.value = n; this.valueChange.emit(n); } onInput(e: Event): void { const raw = (e.target as HTMLInputElement).value; const n = Number(raw); if (raw !== '' && raw !== '-' && Number.isFinite(n)) { this.value = n; this.valueChange.emit(n); } } } @Component({ selector: 'vsp-copy-button', template: ``, }) export class VspCopyButton { @Input() text = ''; @Input() label = 'Copy'; @Input() size = 'sm'; done = false; get cls(): string { return 'btn btn-ghost' + (this.size === 'sm' ? ' btn-sm' : ''); } async copy(): Promise { try { await navigator.clipboard?.writeText(this.text); } catch { /* clipboard unavailable */ } this.done = true; setTimeout(() => (this.done = false), 1400); } } @Component({ selector: 'vsp-inline-edit', template: `@if (editing) { } @else { {{ value || placeholder }} }`, }) export class VspInlineEdit { @Input() value = ''; @Input() placeholder = 'Empty'; @Output() save = new EventEmitter(); editing = false; draft = ''; start(): void { this.draft = this.value; this.editing = true; } commit(): void { this.editing = false; if (this.draft !== this.value) this.save.emit(this.draft); } cancel(): void { this.draft = this.value; this.editing = false; } }