Skip to content

Commit 6913f01

Browse files
authoredNov 11, 2022
feat(abc:hotkey): add hotkey component (#1538)
1 parent a197bb1 commit 6913f01

16 files changed

+225
-72
lines changed
 

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@
9191
"@ng-util/monaco-editor": "^14.0.0",
9292
"@nguniversal/express-engine": "~14.1.0",
9393
"express": "^4.18.1",
94-
"isutf8": "^4.0.0"
94+
"isutf8": "^4.0.0",
95+
"@github/hotkey": "^2.0.1"
9596
},
9697
"devDependencies": {
9798
"@angular-devkit/build-angular": "^14.2.1",

‎packages/abc/hotkey/demo/basic.md

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
order: 1
3+
title:
4+
zh-CN: 基础
5+
en-US: Basic
6+
---
7+
8+
## zh-CN
9+
10+
最简单的用法。
11+
12+
## en-US
13+
14+
Simplest of usage.
15+
16+
```ts
17+
import { Component } from '@angular/core';
18+
19+
import { NzMessageService } from 'ng-zorro-antd/message';
20+
21+
@Component({
22+
selector: 'app-demo',
23+
template: `
24+
<button
25+
nz-button
26+
nzType="primary"
27+
(click)="show('clicked button 1!')"
28+
hotkey="Control+d,Meta+d"
29+
data-hotkey-scope="text-area-1"
30+
>
31+
press meta+d or ctrl+d in text area 1 to click button 1
32+
</button>
33+
<br />
34+
<textarea nz-input id="text-area-1" rows="4" cols="40" hotkey="q" #textArea1 (focus)="selectText(textArea1)">
35+
text area 1, press q to focus this textarea and select all text</textarea
36+
>
37+
<br />
38+
<button
39+
nz-button
40+
nzType="primary"
41+
(click)="show('clicked button 2!')"
42+
hotkey="Control+d,Meta+d"
43+
data-hotkey-scope="text-area-2"
44+
>
45+
press meta+d or ctrl+d in text area 2 to click button 2
46+
</button>
47+
<br />
48+
<textarea nz-input nzType="primary" id="text-area-2" rows="4" cols="40">text area 2</textarea><br />
49+
<label><input nz-input hotkey="t" type="text" />Press <kbd>t</kbd> to focus this field</label><br />
50+
<label><nz-switch hotkey="r"></nz-switch>Press <kbd>r</kbd> to check/uncheck this checkbox</label><br />
51+
<a (click)="show('click link')" nz-button nzType="link" hotkey="o k"> Press <kbd>o k</kbd> click this link </a>
52+
`,
53+
styles: [
54+
`
55+
:host kbd {
56+
color: #f50;
57+
padding: 0 8px;
58+
}
59+
`
60+
]
61+
})
62+
export class DemoComponent {
63+
constructor(private msg: NzMessageService) {}
64+
65+
show(msg: string): void {
66+
this.msg.info(msg);
67+
}
68+
69+
selectText(el: HTMLTextAreaElement): void {
70+
el.select();
71+
}
72+
}
73+
```
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Platform } from '@angular/cdk/platform';
2+
import { Directive, ElementRef, Input, NgZone, OnDestroy } from '@angular/core';
3+
4+
import { install, uninstall } from '@github/hotkey';
5+
6+
@Directive({ selector: '[hotkey]' })
7+
export class HotkeyDirective implements OnDestroy {
8+
/**
9+
* Specify [hotkey format](https://github.com/github/hotkey#hotkey-string-format)
10+
*
11+
* 指定[热键格式](https://github.com/github/hotkey#hotkey-string-format)
12+
*/
13+
@Input('hotkey')
14+
set hotkey(key: string) {
15+
if (!this.platform.isBrowser) return;
16+
17+
this.ngZone.runOutsideAngular(() => install(this.el.nativeElement, key));
18+
}
19+
20+
constructor(private el: ElementRef<HTMLElement>, private ngZone: NgZone, private platform: Platform) {}
21+
22+
ngOnDestroy(): void {
23+
if (!this.platform.isBrowser) return;
24+
25+
this.ngZone.runOutsideAngular(() => uninstall(this.el.nativeElement));
26+
}
27+
}

‎packages/abc/hotkey/hotkey.module.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { NgModule } from '@angular/core';
2+
3+
import { HotkeyDirective } from './hotkey.directive';
4+
5+
const DIRECTIVES = [HotkeyDirective];
6+
7+
@NgModule({
8+
declarations: DIRECTIVES,
9+
exports: DIRECTIVES
10+
})
11+
export class HotkeyModule {}

‎packages/abc/hotkey/hotkey.spec.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Platform } from '@angular/cdk/platform';
2+
import { Component, ViewChild } from '@angular/core';
3+
import { TestBed } from '@angular/core/testing';
4+
5+
import { createTestContext } from '../../testing';
6+
import { HotkeyDirective } from './hotkey.directive';
7+
import { HotkeyModule } from './hotkey.module';
8+
9+
describe('abc: hotkey', () => {
10+
let context: TestComponent;
11+
12+
function genModule(isBrowser: boolean): void {
13+
TestBed.configureTestingModule({
14+
imports: [HotkeyModule],
15+
providers: [{ provide: Platform, useValue: { isBrowser } }],
16+
declarations: [TestComponent]
17+
});
18+
({ context } = createTestContext(TestComponent));
19+
spyOn(context, 'focus');
20+
}
21+
22+
afterEach(() => {
23+
context.comp.ngOnDestroy();
24+
});
25+
26+
it('should be working', done => {
27+
genModule(true);
28+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'q' }));
29+
setTimeout(() => {
30+
expect(context.focus).toHaveBeenCalled();
31+
done();
32+
}, 60);
33+
});
34+
35+
it('when in ssr', done => {
36+
genModule(false);
37+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'q' }));
38+
setTimeout(() => {
39+
expect(context.focus).not.toHaveBeenCalled();
40+
done();
41+
}, 60);
42+
});
43+
});
44+
45+
@Component({ template: `<input #el hotkey="q" class="ipt" (focus)="focus()" />` })
46+
class TestComponent {
47+
@ViewChild(HotkeyDirective, { static: true }) readonly comp!: HotkeyDirective;
48+
hotkey = 'q';
49+
50+
focus(): void {}
51+
}

‎packages/abc/hotkey/index.en-US.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
type: Basic
3+
order: 1
4+
title: hotkey
5+
subtitle: Hotkey
6+
cols: 2
7+
module: import { HotkeyModule } from '@delon/abc/hotkey';
8+
---
9+
10+
Based on the [@github/hotke](https://github.com/github/hotkey) hotkey library.
11+
12+
> If you don't know the hotkey value, you can get it through [Hotkey Code](https://github.github.io/hotkey/examples/hotkey_mapper.html).
13+
14+
## API
15+
16+
### LoadingShowOptions
17+
18+
| Property | Description | Type | Default |
19+
|----------|-------------|------|---------|
20+
| `hotkey` | Specify [hotkey format](https://github.com/github/hotkey#hotkey-string-format) | `string` | - |

‎packages/abc/hotkey/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './public_api';

‎packages/abc/hotkey/index.zh-CN.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
type: Basic
3+
order: 1
4+
title: hotkey
5+
subtitle: 热键
6+
cols: 2
7+
module: import { HotkeyModule } from '@delon/abc/hotkey';
8+
---
9+
10+
基于 [@github/hotke](https://github.com/github/hotkey) 热键库。
11+
12+
> 如果不清楚热键值,可通过[热键代码](https://github.github.io/hotkey/examples/hotkey_mapper.html)来获取。
13+
14+
## API
15+
16+
### [hotkey]
17+
18+
| 成员 | 说明 | 类型 | 默认值 |
19+
|----|----|----|-----|
20+
| `hotkey` | 指定[热键格式](https://github.com/github/hotkey#hotkey-string-format) | `string` | - |

‎packages/abc/hotkey/ng-package.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"lib": {
3+
"flatModuleFile": "hotkey",
4+
"entryFile": "public_api.ts"
5+
}
6+
}

‎packages/abc/hotkey/public_api.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './hotkey.module';
2+
export * from './hotkey.directive';

‎packages/abc/ng-package.json

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"xlsx",
1515
"plyr",
1616
"pdfjs-dist",
17+
"@github/hotkey",
1718
"ngx-countdown",
1819
"@delon/theme",
1920
"@delon/util",

‎packages/abc/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"xlsx": "@LIB-PLACEHOLDER",
2828
"plyr": "@LIB-PLACEHOLDER",
2929
"pdfjs-dist": "@LIB-PLACEHOLDER",
30+
"@github/hotkey": "@LIB-PLACEHOLDER",
3031
"ngx-countdown": "@LIB-PLACEHOLDER",
3132
"@delon/theme": "PEER-0.0.0-PLACEHOLDER",
3233
"@delon/util": "PEER-0.0.0-PLACEHOLDER",

‎scripts/ci/utils.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ DEPENDENCIES=$(node -p "
5252
'husky',
5353
'lint-staged',
5454
'rxjs',
55-
'swagger-typescript-api'
55+
'swagger-typescript-api',
56+
'@github/hotkey'
5657
].map(key => key.replace(/\@/g, '\\\\@').replace(/\//g, '\\\\/').replace(/-/g, '\\\\-') + '|' + (vs[key] || dvs[key])).join('\n\t');
5758
")
5859
VERSION=$(node -p "require('./package.json').version")

‎src/app/core/code/files/delon-abc.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { FullContentModule } from '@delon/abc/full-content';
1313
import { GlobalFooterModule } from '@delon/abc/global-footer';
1414
import { ImageModule } from '@delon/abc/image';
1515
import { LoadingModule } from '@delon/abc/loading';
16+
import { HotkeyModule } from '@delon/abc/hotkey';
1617
import { LodopModule } from '@delon/abc/lodop';
1718
import { NoticeIconModule } from '@delon/abc/notice-icon';
1819
import { ObserversModule } from '@delon/abc/observers';
@@ -62,6 +63,7 @@ const MODULES = [
6263
SGModule,
6364
DatePickerModule,
6465
LoadingModule,
66+
HotkeyModule,
6567
MediaModule,
6668
OnboardingModule,
6769
LetModule,

‎src/app/shared/shared-delon.module.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ErrorCollectModule } from '@delon/abc/error-collect';
88
import { ExceptionModule } from '@delon/abc/exception';
99
import { FooterToolbarModule } from '@delon/abc/footer-toolbar';
1010
import { GlobalFooterModule } from '@delon/abc/global-footer';
11+
import { HotkeyModule } from '@delon/abc/hotkey';
1112
import { LetModule } from '@delon/abc/let';
1213
import { LoadingModule } from '@delon/abc/loading';
1314
import { MediaModule } from '@delon/abc/media';
@@ -94,5 +95,6 @@ export const SHARED_DELON_MODULES = [
9495
FormatPipeModule,
9596
FilterPipeModule,
9697
AutoFocusModule,
97-
LetModule
98+
LetModule,
99+
HotkeyModule
98100
];

‎src/dev/demo.component.ts

+3-69
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,11 @@
11
import { Component } from '@angular/core';
2-
import { FormControl, FormGroup, Validators } from '@angular/forms';
3-
4-
import { SEErrorRefresh } from '@delon/abc/se';
5-
import { NzMessageService } from 'ng-zorro-antd/message';
62

73
@Component({
84
selector: 'app-demo',
9-
template: `
10-
<h3>Operating</h3>
11-
<div class="mb-md">
12-
<button nz-button (click)="resetErrors()">Reset all errors</button>
13-
</div>
14-
<form nz-form #f="ngForm" se-container [errors]="ngModelErrors" gutter="32">
15-
<se label="App Key" [error]="{ required: '请填写', pattern: '只能包含a-z, 0-9之间' }">
16-
<input
17-
type="text"
18-
nz-input
19-
[(ngModel)]="i.ak"
20-
name="ak"
21-
required
22-
pattern="^[a-z0-9]*$"
23-
placeholder="必填项,且只能包含a-z, 0-9之间"
24-
/>
25-
</se>
26-
<ng-template #appSecretRequired> 请填写,密钥<a (click)="msg.success('success')">生成</a>地址。 </ng-template>
27-
<se label="App Secret" [error]="{ required: appSecretRequired, pattern: '只能包含0-9之间' }">
28-
<input
29-
type="text"
30-
nz-input
31-
[(ngModel)]="i.sk"
32-
name="sk"
33-
required
34-
maxlength="32"
35-
pattern="^[0-9]*$"
36-
placeholder="必填项,且只能包含0-9之间"
37-
/>
38-
</se>
39-
<se>
40-
<button nz-button nzType="primary" [disabled]="f.invalid">Save</button>
41-
</se>
42-
</form>
43-
<h3>Reactive</h3>
44-
<form nz-form [formGroup]="validateForm" se-container gutter="32" [errors]="reactiveErrors">
45-
<se label="App Key" [error]="{ required: 'Please input your username!', pattern: 'Incorrect format, muse be A' }">
46-
<input formControlName="userName" nz-input placeholder="Username" />
47-
</se>
48-
<se label="App Secret" error="Please input your Password!">
49-
<input formControlName="password" nz-input type="password" placeholder="Password" />
50-
</se>
51-
<se>
52-
<button nz-button nzType="primary" [disabled]="!validateForm.valid">Log in</button>
53-
</se>
54-
</form>
55-
`
5+
template: ``
566
})
577
export class DemoComponent {
58-
validateForm = new FormGroup({
59-
userName: new FormControl<string | null>(null, [Validators.required, Validators.pattern(/A/)]),
60-
password: new FormControl(null, [Validators.required]),
61-
remember: new FormControl(true)
62-
});
63-
64-
i: { ak?: string; sk?: string } = {};
65-
ngModelErrors: SEErrorRefresh[] = [];
66-
reactiveErrors: SEErrorRefresh[] = [];
67-
68-
constructor(public msg: NzMessageService) {}
69-
70-
resetErrors(): void {
71-
this.ngModelErrors = [{ name: 'ak', error: 'Required field, and can only contain a-z, 0-9' }];
72-
this.reactiveErrors = [
73-
{ name: 'userName', error: 'Required username' },
74-
{ name: 'password', error: 'Required password' }
75-
];
8+
_n(): void {
9+
console.log('n');
7610
}
7711
}

0 commit comments

Comments
 (0)
Please sign in to comment.