import {
ChangeDetectionStrategy,
Component,
ElementRef,
computed,
input,
output,
viewChild,
} from '@angular/core';
/**
* 底部输入坞(Demo `.dock`)
*
* Demo 形态:圆角卡片 + 自适应高度 textarea + 工具行(附件 / 目标 / 发送)。
* 与桌面 `composer` 的区别:桌面版工具行在上、输入在下;移动端输入在上、
* 工具行在下(拇指可达区)。事件语义与桌面一致(draftChange / send / attach)。
*
* 安全区:坞底由页面负责加 `--mob-safe-bottom`。
*/
@Component({
selector: 'app-m-dock',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@if (target()) {
{{ target() }}
}
@if (uploading()) {
}
`,
styles: [
`
:host {
display: block;
}
.m-dock {
margin: 0 var(--sp-4);
padding: var(--sp-3) var(--sp-4);
border: 1px solid var(--edge);
border-radius: var(--r-lg);
background: var(--surface);
box-shadow: var(--shadow-pop);
box-sizing: border-box;
}
textarea {
display: block;
width: 100%;
min-height: 44px;
max-height: 120px;
padding: var(--sp-2) 0;
border: 0;
outline: 0;
resize: none;
background: transparent;
color: var(--ink);
font: inherit;
font-size: var(--fs-17);
line-height: 1.5;
}
.m-dock-tools {
display: flex;
align-items: center;
gap: var(--sp-3);
color: var(--muted);
}
.m-tool {
display: grid;
place-items: center;
width: 36px;
height: 36px;
border: 0;
border-radius: var(--r-tag);
background: none;
color: var(--muted);
font-size: var(--fs-17);
cursor: pointer;
}
.m-target {
display: inline-flex;
align-items: center;
gap: var(--sp-1);
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--muted);
font-size: var(--fs-13);
}
.m-send {
margin-left: auto;
display: grid;
place-items: center;
flex: none;
width: 36px;
height: 36px;
border: 0;
border-radius: var(--r-tag);
background: var(--primary-subtle);
color: var(--muted);
font-size: var(--fs-15);
cursor: pointer;
transition:
background var(--motion-fast) var(--ease-standard),
color var(--motion-fast) var(--ease-standard);
}
.m-send.ready {
background: var(--primary);
color: var(--primary-content);
}
.m-send:disabled {
cursor: not-allowed;
}
.m-dock-progress {
height: 3px;
margin-top: var(--sp-2);
border-radius: var(--r-tag);
background: var(--surface-active);
overflow: hidden;
}
.m-dock-progress i {
display: block;
height: 100%;
background: var(--primary);
transition: width var(--motion-detail) var(--ease-standard);
}
`,
],
})
export class MDockComponent {
readonly draft = input('');
readonly placeholder = input('发消息…');
/** 右侧显示的目标(数字生命名称);空则不显示 */
readonly target = input('');
readonly disabled = input(false);
readonly sending = input(false);
readonly uploading = input(false);
readonly pct = input(0);
readonly draftChange = output();
readonly send = output();
readonly attach = output();
/** 是否已有待发附件(由页面传入;纯附件也可发送) */
readonly hasAttachments = input(false);
protected readonly fileRef = viewChild.required>('fileRef');
/** 文本非空 **或** 有待发附件即可发送 */
protected readonly hasContent = computed(
() => (this.draft() ?? '').trim().length > 0 || this.hasAttachments(),
);
protected onInput(e: Event): void {
const el = e.target as HTMLTextAreaElement;
this.draftChange.emit(el.value);
el.style.height = 'auto';
el.style.height = `${Math.min(el.scrollHeight, 120)}px`;
}
/** 回车发送,Shift+Enter 换行 */
protected onEnter(e: Event): void {
const ke = e as KeyboardEvent;
if (ke.shiftKey) return;
ke.preventDefault();
this.submit();
}
protected submit(): void {
if (this.disabled() || this.sending() || !this.hasContent()) return;
this.send.emit(this.draft().trim());
}
/** 打开系统文件选择器 */
protected pickFile(): void {
this.fileRef().nativeElement.click();
}
protected onFile(e: Event): void {
const input = e.target as HTMLInputElement;
if (input.files && input.files.length > 0) this.attach.emit(input.files);
input.value = '';
}
}