Skip to content

Commit

Permalink
feat: add resizable directive 2
Browse files Browse the repository at this point in the history
  • Loading branch information
igauch committed Jan 4, 2024
1 parent 9cb0c10 commit 068f7ca
Show file tree
Hide file tree
Showing 15 changed files with 265 additions and 134 deletions.
3 changes: 2 additions & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@
"browserTarget": "storybook:build",
"compodoc": true,
"compodocArgs": ["-e", "json", "-d", "."],
"outputDir": "dist"
"outputDir": "dist",
"enableProdMode": false // FIXME: https://github.com/storybookjs/storybook/issues/23534
}
}
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"JounQin <[email protected]> (https://www.1stG.me)"
],
"license": "MIT",
"packageManager": "[email protected]",
"keywords": [
"alauda",
"angular",
Expand Down
11 changes: 8 additions & 3 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ const watch = process.argv.includes('--watch');

const debugNgPackage = '../ng-package.debug.json';

const dest = (isDebug ? require(debugNgPackage).dest : 'release') + '/theme';
const releaseDest = isDebug ? require(debugNgPackage).dest : 'release';

function copyResources() {
const themeDest = path.resolve(releaseDest, 'theme');
gulp
.src([
'src/theme/_base-var.scss',
Expand All @@ -22,12 +23,16 @@ function copyResources() {
'src/theme/_theme-preset.scss',
'src/theme/_mixin.scss',
])
.pipe(gulp.dest(dest));
.pipe(gulp.dest(themeDest));

gulp
.src(['src/resizable/resizable.scss'])
.pipe(gulp.dest(path.resolve(releaseDest, 'resizable')));

gulp
.src('src/theme/style.scss')
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest(dest));
.pipe(gulp.dest(themeDest));
}

const packagr = ngPackagr
Expand Down
167 changes: 83 additions & 84 deletions src/resizable/resizable.directive.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,84 @@
import { DOCUMENT } from '@angular/common';
import {
AfterViewInit,
Directive,
ElementRef,
EventEmitter,
Inject,
inject,
Input,
OnDestroy,
Output,
Renderer2,
} from '@angular/core';
import { fromEvent, Subject, Subscription, takeUntil } from 'rxjs';
import { fromEvent, Subject, takeUntil } from 'rxjs';

import { handlePixel } from '../utils';

@Directive({
selector: '[auiContainerForResizable]',
host: {
class: 'aui-container-for-resizable',
},
standalone: true,
})
export class ContainerForResizableDirective {
el = inject<ElementRef<HTMLElement>>(ElementRef);
}

/**
* 使用此指令需要引入resizable.scss
* 因为参考线是基于absolute定位的,所以需要在预期的地方加上relative(不自动加是为了避免修改业务布局而导致使用者出现意外的异常)
*/
@Directive({
selector: '[auiResizable]',
host: {
class: 'aui-resizable',
},
standalone: true,
})
export class ResizableDirective implements AfterViewInit, OnDestroy {
@Input()
containerElement: Element; // 要插入参考线的容器元素
minWidth: string | number;

@Input()
minWidth: string;

@Input()
maxWidth: string;
maxWidth: string | number;

@Output()
resizeStartEvent = new EventEmitter<number>();
resizeStart = new EventEmitter<number>();

@Output()
resizingEvent = new EventEmitter<number>();
resizing = new EventEmitter<number>();

@Output()
resizeEndEvent = new EventEmitter<number>();
resizeEnd = new EventEmitter<number>();

elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
renderer2 = inject(Renderer2);
containerDirective = inject(ContainerForResizableDirective, {
optional: true,
});

private readonly element = this.elementRef.nativeElement;
containerElement = this.containerDirective?.el.nativeElement || this.element;

destroy$$ = new Subject<void>();

private readonly element: HTMLElement;
private initialWidth: number;
private mouseDownScreenX: number;
resizeHandle: HTMLElement; // 在指令作用的元素上插入引导分割线,hover到作用元素后出现,拖动事件的宿主
resizeOverlay: HTMLElement; // 在拖动时创建出一个透明的遮罩层,用以鼠标样式在拖动时总是拖动样式
resizeBar: HTMLElement; // 拖动时的参考线,该参考线会被插入到 containerElement
private mouseUpSubscription: Subscription;
private readonly document: Document;
private handleHasUp: boolean;

constructor(
element: ElementRef,
readonly renderer2: Renderer2,
@Inject(DOCUMENT)
private readonly doc: Document,
) {
this.element = element.nativeElement;
this.document = this.doc;
}
private readonly BAR_WIDTH = 2;

ngAfterViewInit() {
this.createResizeHandle();

this.binEvent(this.element, 'mouseover', () => {
this.binEvent(this.element, 'mouseover').subscribe(() => {
this.handleHasUp = true;
if (!this.resizeBar) {
this.setResizeHandleVisible(true);
}
});
this.binEvent(this.element, 'mouseout', () => {
this.binEvent(this.element, 'mouseout').subscribe(() => {
this.handleHasUp = false;
this.setResizeHandleVisible(false);
});
Expand All @@ -84,38 +92,36 @@ export class ResizableDirective implements AfterViewInit, OnDestroy {
private binEvent<E>(
target: HTMLElement | Document | Window,
eventType: string,
callback: (e: E) => void,
) {
return fromEvent<E>(target, eventType)
.pipe(takeUntil(this.destroy$$))
.subscribe(callback);
return fromEvent<E>(target, eventType).pipe(takeUntil(this.destroy$$));
}

private setResizeHandleVisible(isVisible: boolean) {
this.resizeHandle &&
if (this.resizeHandle) {
this.renderer2.setStyle(
this.resizeHandle,
'visibility',
isVisible ? 'visible' : 'hidden',
);
}
}

private createResizeHandle() {
if (!this.resizeHandle) {
this.resizeHandle = this.renderer2.createElement('div');
this.renderer2.addClass(this.resizeHandle, 'resize-handle');
if (this.resizeHandle) {
return;
}

this.binEvent(this.resizeHandle, 'click', (e: Event) =>
e.stopPropagation(),
);
this.binEvent(
this.resizeHandle,
'mousedown',
this.onMousedown.bind(this),
);
this.resizeHandle = this.renderer2.createElement('div');
this.renderer2.addClass(this.resizeHandle, 'resize-handle');

this.renderer2.appendChild(this.element, this.resizeHandle);
}
this.binEvent<Event>(this.resizeHandle, 'click').subscribe(e =>
e.stopPropagation(),
);
this.binEvent<MouseEvent>(this.resizeHandle, 'mousedown').subscribe(e => {
this.onMousedown(e);
});

this.renderer2.appendChild(this.element, this.resizeHandle);
}

private createResizeOverlay() {
Expand All @@ -130,7 +136,7 @@ export class ResizableDirective implements AfterViewInit, OnDestroy {
this.renderer2.setStyle(
this.resizeBar,
'left',
this.element.clientWidth + this.getOffset() + 'px',
this.element.clientWidth + this.getOffset() - this.BAR_WIDTH + 'px',
);
this.renderer2.appendChild(this.containerElement, this.resizeBar);
}
Expand All @@ -143,42 +149,44 @@ export class ResizableDirective implements AfterViewInit, OnDestroy {
this.setResizeHandleVisible(false);

this.initialWidth = this.element.clientWidth;
this.resizeStartEvent.emit(this.initialWidth);
this.resizeStart.emit(this.initialWidth);
this.mouseDownScreenX = event.clientX;
this.createResizeOverlay();
this.createResizeBar();

this.mouseUpSubscription = this.binEvent<MouseEvent>(
const mouseMoveSubscription = this.binEvent<MouseEvent>(
document,
'mouseup',
ev => this.onMouseup(ev),
);
'mousemove',
).subscribe(e => {
this.move(e);
});

this.binEvent(document, 'mousemove', this.move);
const mouseUpSubscription = this.binEvent<MouseEvent>(
document,
'mouseup',
).subscribe(ev => {
this.onMouseup(ev);
mouseUpSubscription.unsubscribe();
mouseMoveSubscription.unsubscribe();
});
}

private onMouseup(event: MouseEvent): void {
this.handleHasUp && this.setResizeHandleVisible(true);

const movementX = event.clientX - this.mouseDownScreenX;
const newWidth = this.initialWidth + movementX;
const finalWidth = this.getFinalWidth(newWidth);
if (this.handleHasUp) {
this.setResizeHandleVisible(true);
}

this.renderer2.removeChild(this.element, this.resizeOverlay);
this.renderer2.removeChild(this.containerElement, this.resizeOverlay);
this.resizeOverlay = null;
this.renderer2.removeChild(this.containerElement, this.resizeBar);
this.resizeBar = null;

this.resizeEndEvent.emit(finalWidth);

if (this.mouseUpSubscription && !this.mouseUpSubscription.closed) {
this._destroySubscription();
}

this.document.removeEventListener('mousemove', this.move);
const movementX = event.clientX - this.mouseDownScreenX;
const newWidth = this.initialWidth + movementX;
this.resizeEnd.emit(this.getFinalWidth(newWidth));
}

private readonly move = (event: MouseEvent) => {
private move(event: MouseEvent) {
if (!this.resizeBar) {
return;
}
Expand All @@ -189,46 +197,37 @@ export class ResizableDirective implements AfterViewInit, OnDestroy {
this.renderer2.setStyle(
this.resizeBar,
'left',
`${finalWidth + this.getOffset()}px`,
handlePixel(finalWidth + this.getOffset() - this.BAR_WIDTH),
);
this.resizingEvent.emit(finalWidth);
};
this.resizing.emit(finalWidth);
}

private getFinalWidth(newWidth: number): number {
// 不能超出边界
const _max = this.containerElement.clientWidth - this.getOffset();
const _max = this.containerElement.clientWidth + this.getOffset();
const minWidth = this.handleWidth(this.minWidth || this.getOffset());

const maxWidth = this.handleWidth(this.maxWidth || _max);
return Math.max(Math.min(maxWidth, newWidth), minWidth);
}

private getOffset() {
return (
this.element.getBoundingClientRect().left -
this.containerElement.getBoundingClientRect().left -
2 // 2 是resizeBar的宽度
this.containerElement.getBoundingClientRect().left
);
}

private handleWidth(width: string | number) {
if (!width) {
return;
}
if (typeof width === 'number') {
return width;
}
if (width.includes('%')) {
if (!width) {
return;
}
if (width.endsWith('%')) {
const tableWidth = this.containerElement.clientWidth;
return (tableWidth * Number.parseFloat(width)) / 100;
}
return Number.parseFloat(width.replace(/\D+/, ''));
}

private _destroySubscription() {
if (this.mouseUpSubscription) {
this.mouseUpSubscription.unsubscribe();
this.mouseUpSubscription = null;
}
}
}
5 changes: 5 additions & 0 deletions src/resizable/resizable.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
@import '../theme/var';

.aui-resizable,
.aui-container-for-resizable {
position: relative;
}

.resize-handle {
display: inline-block;
position: absolute;
Expand Down
8 changes: 2 additions & 6 deletions src/table/table-cell.directive.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { CdkCell, CdkColumnDef } from '@angular/cdk/table';
import { Directive, ElementRef, Input } from '@angular/core';

import { buildBem } from '../utils';

import { TABLE_PREFIX_CLASSNAME } from './table.component';

const bem = buildBem(TABLE_PREFIX_CLASSNAME);
import { tableBem } from './table.component';

/** Cell template container that adds the right classes and role. */
@Directive({
Expand All @@ -25,7 +21,7 @@ export class TableCellDirective extends CdkCell {
constructor(columnDef: CdkColumnDef, elementRef: ElementRef<HTMLElement>) {
super(columnDef, elementRef);
elementRef.nativeElement.classList.add(
bem.element(`column-${columnDef.cssClassFriendlyName}`),
tableBem.element(`column-${columnDef.cssClassFriendlyName}`),
);
}
}
Loading

0 comments on commit 068f7ca

Please sign in to comment.