import { booleanAttribute, ChangeDetectionStrategy, Component, DestroyRef, effect, ElementRef, inject, input, signal, ViewEncapsulation, } from "@angular/core"; import { injectSdSystemConfigResource } from "../../core/config/injectSdSystemConfigResource"; import { SdResizeDirective, type SdResizeEvent } from "../../core/events/sd-resize"; @Component({ selector: "sd-dock", changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, standalone: true, imports: [], hostDirectives: [{ directive: SdResizeDirective, outputs: ["sdResize"] }], host: { "[attr.data-sd-position]": "position()", "[attr.data-sd-resizable]": "resizable()", "(sdResize)": "onHostResize($event)", }, template: ` @if (resizable()) {
} `, styles: [ /* language=SCSS */ ` sd-dock { display: block; position: absolute; overflow: auto; &[data-sd-resizable="true"] { > ._resize-bar { position: absolute; } &[data-sd-position="top"] { > ._resize-bar { bottom: 0; left: 0; width: 100%; height: 2px; cursor: ns-resize; } } &[data-sd-position="bottom"] { > ._resize-bar { top: 0; left: 0; width: 100%; height: 2px; cursor: ns-resize; } } &[data-sd-position="left"] { > ._resize-bar { top: 0; right: 0; height: 100%; width: 2px; cursor: ew-resize; } } &[data-sd-position="right"] { > ._resize-bar { top: 0; left: 0; height: 100%; width: 2px; cursor: ew-resize; } } } } `, ], }) export class SdDock { private readonly _elRef = inject(ElementRef); private readonly _destroyRef = inject(DestroyRef); private _dragCleanup: (() => void) | undefined; key = input(); position = input<"top" | "bottom" | "right" | "left">("top"); resizable = input(false, { transform: booleanAttribute }); size = signal(0); private readonly _config = injectSdSystemConfigResource<{ size?: string }>({ key: this.key }); constructor() { this._destroyRef.onDestroy(() => { this._dragCleanup?.(); }); effect(() => { const conf = this._config.value(); if (this.resizable() && conf && conf.size != null) { if (["right", "left"].includes(this.position())) { this._elRef.nativeElement.style.width = conf.size; } if (["top", "bottom"].includes(this.position())) { this._elRef.nativeElement.style.height = conf.size; } } }); } assignStyle(style: Partial) { Object.assign(this._elRef.nativeElement.style, style); } onHostResize(event: SdResizeEvent) { if (["top", "bottom"].includes(this.position()) && event.heightChanged) { this.size.set(this._elRef.nativeElement.clientHeight); } if (["right", "left"].includes(this.position()) && event.widthChanged) { this.size.set(this._elRef.nativeElement.clientWidth); } } onResizeBarMousedown(event: MouseEvent) { this._dragCleanup?.(); const thisEl = this._elRef.nativeElement; const startX = event.clientX; const startY = event.clientY; const startHeight = thisEl.clientHeight; const startWidth = thisEl.clientWidth; const doDrag = (e: MouseEvent): void => { e.stopPropagation(); e.preventDefault(); if (this.position() === "bottom") { thisEl.style.height = `${Math.max(0, startHeight - e.clientY + startY)}px`; } else if (this.position() === "right") { thisEl.style.width = `${Math.max(0, startWidth - e.clientX + startX)}px`; } else if (this.position() === "top") { thisEl.style.height = `${Math.max(0, startHeight + e.clientY - startY)}px`; } else { // left thisEl.style.width = `${Math.max(0, startWidth + e.clientX - startX)}px`; } }; const stopDrag = (e: MouseEvent) => { e.stopPropagation(); e.preventDefault(); this._dragCleanup?.(); if (this.key() != null) { const newConf: { size?: string } = {}; if (["right", "left"].includes(this.position())) { newConf.size = thisEl.style.width; } else { newConf.size = thisEl.style.height; } this._config.set(newConf); } }; document.addEventListener("mousemove", doDrag); document.addEventListener("mouseup", stopDrag); this._dragCleanup = () => { document.removeEventListener("mousemove", doDrag); document.removeEventListener("mouseup", stopDrag); this._dragCleanup = undefined; }; } }