Skip to content

Commit 19f56c9

Browse files
committed
feat: add resizable directive 2
1 parent 9cb0c10 commit 19f56c9

15 files changed

+252
-122
lines changed

angular.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@
7474
"browserTarget": "storybook:build",
7575
"compodoc": true,
7676
"compodocArgs": ["-e", "json", "-d", "."],
77-
"outputDir": "dist"
77+
"outputDir": "dist",
78+
"enableProdMode": false // FIXME: https://github.com/storybookjs/storybook/issues/23534
7879
}
7980
}
8081
}

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"JounQin <admin@1stG.me> (https://www.1stG.me)"
1010
],
1111
"license": "MIT",
12+
"packageManager": "yarn@1.22.21",
1213
"keywords": [
1314
"alauda",
1415
"angular",

scripts/build.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ const watch = process.argv.includes('--watch');
1111

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

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

1616
function copyResources() {
17+
const themeDest = path.resolve(releaseDest, 'theme');
1718
gulp
1819
.src([
1920
'src/theme/_base-var.scss',
@@ -22,12 +23,16 @@ function copyResources() {
2223
'src/theme/_theme-preset.scss',
2324
'src/theme/_mixin.scss',
2425
])
25-
.pipe(gulp.dest(dest));
26+
.pipe(gulp.dest(themeDest));
27+
28+
gulp
29+
.src(['src/resizable/resizable.scss'])
30+
.pipe(gulp.dest(path.resolve(releaseDest, 'resizable')));
2631

2732
gulp
2833
.src('src/theme/style.scss')
2934
.pipe(sass().on('error', sass.logError))
30-
.pipe(gulp.dest(dest));
35+
.pipe(gulp.dest(themeDest));
3136
}
3237

3338
const packagr = ngPackagr

src/resizable/resizable.directive.ts

+73-73
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,86 @@
1-
import { DOCUMENT } from '@angular/common';
21
import {
32
AfterViewInit,
43
Directive,
54
ElementRef,
65
EventEmitter,
7-
Inject,
86
Input,
97
OnDestroy,
8+
Optional,
109
Output,
1110
Renderer2,
1211
} from '@angular/core';
13-
import { fromEvent, Subject, Subscription, takeUntil } from 'rxjs';
12+
import { fromEvent, Subject, takeUntil } from 'rxjs';
13+
14+
@Directive({
15+
selector: '[auiContainerForResizable]',
16+
host: {
17+
class: 'aui-container-for-resizable',
18+
},
19+
standalone: true,
20+
})
21+
export class ContainerForResizableDirective {
22+
constructor(readonly el: ElementRef) {}
23+
}
1424

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

2739
@Input()
28-
minWidth: string;
29-
30-
@Input()
31-
maxWidth: string;
40+
maxWidth: string | number;
3241

3342
@Output()
34-
resizeStartEvent = new EventEmitter<number>();
43+
resizeStart = new EventEmitter<number>();
3544

3645
@Output()
37-
resizingEvent = new EventEmitter<number>();
46+
resizing = new EventEmitter<number>();
3847

3948
@Output()
40-
resizeEndEvent = new EventEmitter<number>();
49+
resizeEnd = new EventEmitter<number>();
4150

4251
destroy$$ = new Subject<void>();
52+
containerElement: Element;
4353

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

5463
constructor(
5564
element: ElementRef,
5665
readonly renderer2: Renderer2,
57-
@Inject(DOCUMENT)
58-
private readonly doc: Document,
66+
@Optional()
67+
private readonly containerDirective: ContainerForResizableDirective,
5968
) {
6069
this.element = element.nativeElement;
61-
this.document = this.doc;
70+
this.containerElement =
71+
this.containerDirective?.el.nativeElement || this.element;
6272
}
6373

6474
ngAfterViewInit() {
6575
this.createResizeHandle();
6676

67-
this.binEvent(this.element, 'mouseover', () => {
77+
this.binEvent(this.element, 'mouseover').subscribe(() => {
6878
this.handleHasUp = true;
6979
if (!this.resizeBar) {
7080
this.setResizeHandleVisible(true);
7181
}
7282
});
73-
this.binEvent(this.element, 'mouseout', () => {
83+
this.binEvent(this.element, 'mouseout').subscribe(() => {
7484
this.handleHasUp = false;
7585
this.setResizeHandleVisible(false);
7686
});
@@ -84,38 +94,36 @@ export class ResizableDirective implements AfterViewInit, OnDestroy {
8494
private binEvent<E>(
8595
target: HTMLElement | Document | Window,
8696
eventType: string,
87-
callback: (e: E) => void,
8897
) {
89-
return fromEvent<E>(target, eventType)
90-
.pipe(takeUntil(this.destroy$$))
91-
.subscribe(callback);
98+
return fromEvent<E>(target, eventType).pipe(takeUntil(this.destroy$$));
9299
}
93100

94101
private setResizeHandleVisible(isVisible: boolean) {
95-
this.resizeHandle &&
102+
if (this.resizeHandle) {
96103
this.renderer2.setStyle(
97104
this.resizeHandle,
98105
'visibility',
99106
isVisible ? 'visible' : 'hidden',
100107
);
108+
}
101109
}
102110

103111
private createResizeHandle() {
104-
if (!this.resizeHandle) {
105-
this.resizeHandle = this.renderer2.createElement('div');
106-
this.renderer2.addClass(this.resizeHandle, 'resize-handle');
112+
if (this.resizeHandle) {
113+
return;
114+
}
107115

108-
this.binEvent(this.resizeHandle, 'click', (e: Event) =>
109-
e.stopPropagation(),
110-
);
111-
this.binEvent(
112-
this.resizeHandle,
113-
'mousedown',
114-
this.onMousedown.bind(this),
115-
);
116+
this.resizeHandle = this.renderer2.createElement('div');
117+
this.renderer2.addClass(this.resizeHandle, 'resize-handle');
116118

117-
this.renderer2.appendChild(this.element, this.resizeHandle);
118-
}
119+
this.binEvent<Event>(this.resizeHandle, 'click').subscribe(e =>
120+
e.stopPropagation(),
121+
);
122+
this.binEvent<MouseEvent>(this.resizeHandle, 'mousedown').subscribe(e => {
123+
this.onMousedown(e);
124+
});
125+
126+
this.renderer2.appendChild(this.element, this.resizeHandle);
119127
}
120128

121129
private createResizeOverlay() {
@@ -130,7 +138,7 @@ export class ResizableDirective implements AfterViewInit, OnDestroy {
130138
this.renderer2.setStyle(
131139
this.resizeBar,
132140
'left',
133-
this.element.clientWidth + this.getOffset() + 'px',
141+
this.element.clientWidth + this.getOffset() - this.BAR_WIDTH + 'px',
134142
);
135143
this.renderer2.appendChild(this.containerElement, this.resizeBar);
136144
}
@@ -143,42 +151,42 @@ export class ResizableDirective implements AfterViewInit, OnDestroy {
143151
this.setResizeHandleVisible(false);
144152

145153
this.initialWidth = this.element.clientWidth;
146-
this.resizeStartEvent.emit(this.initialWidth);
154+
this.resizeStart.emit(this.initialWidth);
147155
this.mouseDownScreenX = event.clientX;
148156
this.createResizeOverlay();
149157
this.createResizeBar();
150158

151-
this.mouseUpSubscription = this.binEvent<MouseEvent>(
159+
const mouseMoveSubscription = this.binEvent<MouseEvent>(
152160
document,
153-
'mouseup',
154-
ev => this.onMouseup(ev),
155-
);
161+
'mousemove',
162+
).subscribe(e => {
163+
this.move(e);
164+
});
156165

157-
this.binEvent(document, 'mousemove', this.move);
166+
const mouseUpSubscription = this.binEvent<MouseEvent>(
167+
document,
168+
'mouseup',
169+
).subscribe(ev => {
170+
this.onMouseup(ev);
171+
mouseUpSubscription.unsubscribe();
172+
mouseMoveSubscription.unsubscribe();
173+
});
158174
}
159175

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

163-
const movementX = event.clientX - this.mouseDownScreenX;
164-
const newWidth = this.initialWidth + movementX;
165-
const finalWidth = this.getFinalWidth(newWidth);
166-
167-
this.renderer2.removeChild(this.element, this.resizeOverlay);
179+
this.renderer2.removeChild(this.containerElement, this.resizeOverlay);
168180
this.resizeOverlay = null;
169181
this.renderer2.removeChild(this.containerElement, this.resizeBar);
170182
this.resizeBar = null;
171183

172-
this.resizeEndEvent.emit(finalWidth);
173-
174-
if (this.mouseUpSubscription && !this.mouseUpSubscription.closed) {
175-
this._destroySubscription();
176-
}
177-
178-
this.document.removeEventListener('mousemove', this.move);
184+
const movementX = event.clientX - this.mouseDownScreenX;
185+
const newWidth = this.initialWidth + movementX;
186+
this.resizeEnd.emit(this.getFinalWidth(newWidth));
179187
}
180188

181-
private readonly move = (event: MouseEvent) => {
189+
private move(event: MouseEvent) {
182190
if (!this.resizeBar) {
183191
return;
184192
}
@@ -189,14 +197,14 @@ export class ResizableDirective implements AfterViewInit, OnDestroy {
189197
this.renderer2.setStyle(
190198
this.resizeBar,
191199
'left',
192-
`${finalWidth + this.getOffset()}px`,
200+
`${finalWidth + this.getOffset() - this.BAR_WIDTH}px`,
193201
);
194-
this.resizingEvent.emit(finalWidth);
195-
};
202+
this.resizing.emit(finalWidth);
203+
}
196204

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

202210
const maxWidth = this.handleWidth(this.maxWidth || _max);
@@ -206,29 +214,21 @@ export class ResizableDirective implements AfterViewInit, OnDestroy {
206214
private getOffset() {
207215
return (
208216
this.element.getBoundingClientRect().left -
209-
this.containerElement.getBoundingClientRect().left -
210-
2 // 2 是resizeBar的宽度
217+
this.containerElement.getBoundingClientRect().left
211218
);
212219
}
213220

214221
private handleWidth(width: string | number) {
215-
if (!width) {
216-
return;
217-
}
218222
if (typeof width === 'number') {
219223
return width;
220224
}
225+
if (!width) {
226+
return;
227+
}
221228
if (width.includes('%')) {
222229
const tableWidth = this.containerElement.clientWidth;
223230
return (tableWidth * Number.parseFloat(width)) / 100;
224231
}
225232
return Number.parseFloat(width.replace(/\D+/, ''));
226233
}
227-
228-
private _destroySubscription() {
229-
if (this.mouseUpSubscription) {
230-
this.mouseUpSubscription.unsubscribe();
231-
this.mouseUpSubscription = null;
232-
}
233-
}
234234
}

src/resizable/resizable.scss

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
@import '../theme/var';
22

3+
.aui-resizable,
4+
.aui-container-for-resizable {
5+
position: relative;
6+
}
7+
38
.resize-handle {
49
display: inline-block;
510
position: absolute;

src/table/table-cell.directive.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import { CdkCell, CdkColumnDef } from '@angular/cdk/table';
22
import { Directive, ElementRef, Input } from '@angular/core';
33

4-
import { buildBem } from '../utils';
5-
6-
import { TABLE_PREFIX_CLASSNAME } from './table.component';
7-
8-
const bem = buildBem(TABLE_PREFIX_CLASSNAME);
4+
import { tableBem } from './table.component';
95

106
/** Cell template container that adds the right classes and role. */
117
@Directive({
@@ -25,7 +21,7 @@ export class TableCellDirective extends CdkCell {
2521
constructor(columnDef: CdkColumnDef, elementRef: ElementRef<HTMLElement>) {
2622
super(columnDef, elementRef);
2723
elementRef.nativeElement.classList.add(
28-
bem.element(`column-${columnDef.cssClassFriendlyName}`),
24+
tableBem.element(`column-${columnDef.cssClassFriendlyName}`),
2925
);
3026
}
3127
}

src/table/table-col-resizable.directive.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { AfterViewInit, Directive, inject, Input } from '@angular/core';
22
import { takeUntil } from 'rxjs';
33

44
import { ResizableDirective } from '../resizable';
5-
import { buildBem, handlePixel } from '../utils';
5+
import { handlePixel } from '../utils';
66

77
import {
8-
TABLE_PREFIX_CLASSNAME,
8+
tableBem,
99
TableColumnDefDirective,
1010
TableComponent,
1111
TableScrollWrapperDirective,
@@ -41,10 +41,8 @@ export class TableColResizableDirective
4141
this.tableScrollWrapperDirective || this.tableComponent
4242
).el.nativeElement;
4343

44-
const bem = buildBem(TABLE_PREFIX_CLASSNAME);
45-
46-
this.resizeEndEvent.pipe(takeUntil(this.destroy$$)).subscribe(width => {
47-
const className = bem.element(
44+
this.resizeEnd.pipe(takeUntil(this.destroy$$)).subscribe(width => {
45+
const className = tableBem.element(
4846
`column-${this.tableColumnDefDirective.cssClassFriendlyName}`,
4947
);
5048
if (!this.hostAttr) {

0 commit comments

Comments
 (0)