Skip to content

Commit

Permalink
feat(gestures): port use-gesture
Browse files Browse the repository at this point in the history
Closes #91

- [x] drag
- [ ] move
- [ ] hover
- [ ] scroll
- [ ] wheel
- [ ] pinch
- [ ] multiple
  • Loading branch information
nartc committed Oct 4, 2023
1 parent a162f29 commit d2c0719
Show file tree
Hide file tree
Showing 20 changed files with 281 additions and 90 deletions.
4 changes: 4 additions & 0 deletions apps/test-app/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import { RouterLink, RouterOutlet } from '@angular/router';
<li>
<a routerLink="/intl">Intl</a>
</li>
<li>
<a routerLink="/drag">Drag Gesture</a>
</li>
</ul>
<hr />
Expand Down
4 changes: 4 additions & 0 deletions apps/test-app/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export const appConfig: ApplicationConfig = {
path: 'intl',
loadComponent: () => import('./intl/intl.component'),
},
{
path: 'drag',
loadComponent: () => import('./drag/drag.component'),
},
]),
],
};
64 changes: 64 additions & 0 deletions apps/test-app/src/app/drag/drag.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Component, signal } from '@angular/core';
import type { Vector2 } from '@use-gesture/vanilla';
import {
NgxDrag,
injectDrag,
provideZonelessGesture,
type NgxDragState,
} from 'ngxtension/gestures';

@Component({
selector: 'app-box',
standalone: true,
template: `
<span style="position: absolute; top: -1rem; ">
{{ position() }}
</span>
`,
host: {
class: 'draggable-box pink-draggable-box',
},
})
export class Box {
position = signal<Vector2>([0, 0]);

constructor() {
injectDrag(
({ target, active, offset: [ox, oy], cdr }) => {
const el = target as HTMLElement;
this.position.set([ox, oy]);
el.style.transform = `translate(${ox}px, ${oy}px)`;
if (!active) {
cdr.detectChanges();
}
},
{ config: () => ({ from: this.position }) }
);
}
}

@Component({
standalone: true,
imports: [Box, NgxDrag],
template: `
<app-box />
<div
class="draggable-box blue-draggable-box"
(ngxDrag)="onDrag($event)"
[ngxDragConfig]="{ from: position }"
></div>
`,
providers: [provideZonelessGesture()],
})
export default class Drag {
position = signal<Vector2>([0, 0]);

onDrag({ target, active, offset: [ox, oy], cdr }: NgxDragState) {
const el = target as HTMLElement;
this.position.set([ox, oy]);
el.style.transform = `translate(${ox}px, ${oy}px)`;
if (!active) {
cdr.detectChanges();
}
}
}
17 changes: 17 additions & 0 deletions apps/test-app/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,20 @@ body {
margin: 0;
box-sizing: border-box;
}

.draggable-box {
display: block;
position: relative;
width: 100px;
height: 100px;
border-radius: 0.25rem;
touch-action: none;
}

.pink-draggable-box {
background: hotpink;
}

.blue-draggable-box {
background: dodgerblue;
}
3 changes: 3 additions & 0 deletions libs/ngxtension/gestures/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ngxtension/gestures

Secondary entry point of `ngxtension`. It can be used by importing from `ngxtension/gestures`.
5 changes: 5 additions & 0 deletions libs/ngxtension/gestures/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"lib": {
"entryFile": "src/index.ts"
}
}
33 changes: 33 additions & 0 deletions libs/ngxtension/gestures/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "ngxtension/gestures",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"sourceRoot": "libs/ngxtension/gestures/src",
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/ngxtension/jest.config.ts",
"testPathPattern": ["gestures"],
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": [
"libs/ngxtension/gestures/**/*.ts",
"libs/ngxtension/gestures/**/*.html"
]
}
}
}
}
98 changes: 98 additions & 0 deletions libs/ngxtension/gestures/src/drag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {
ChangeDetectorRef,
DestroyRef,
Directive,
ElementRef,
EventEmitter,
Injector,
Input,
NgZone,
Output,
effect,
inject,
runInInjectionContext,
signal,
type OnInit,
} from '@angular/core';
import {
DragGesture,
type DragConfig,
type DragState,
type EventTypes,
type Handler,
} from '@use-gesture/vanilla';
import { assertInjector } from 'ngxtension/assert-injector';
import { injectZonelessGesture } from './zoneless-gesture';

type DragHandler = Handler<'drag', EventTypes['drag']>;
export type NgxDragState = Parameters<DragHandler>[0] & {
cdr: ChangeDetectorRef;
};
type NgxDragHandler = (state: NgxDragState) => ReturnType<DragHandler>;

export function injectDrag(
handler: NgxDragHandler,
{
injector,
zoneless,
config = () => ({}),
}: { injector?: Injector; zoneless?: boolean; config?: () => DragConfig } = {}
) {
injector = assertInjector(injectDrag, injector);
return runInInjectionContext(injector, () => {
const zonelessGesture = injectZonelessGesture();
const host = inject(ElementRef) as ElementRef<HTMLElement>;
const zone = inject(NgZone);
const cdr = inject(ChangeDetectorRef);

zoneless ??= zonelessGesture;

const ngHandler = (state: DragState) => {
Object.assign(state, { cdr });
return handler(state as NgxDragState);
};

let dragGesture: DragGesture;

dragGesture = zoneless
? zone.runOutsideAngular(
() => new DragGesture(host.nativeElement, ngHandler)
)
: new DragGesture(host.nativeElement, ngHandler);

effect(() => {
if (zoneless) {
zone.runOutsideAngular(() => {
dragGesture.setConfig(config());
});
} else {
dragGesture.setConfig(config());
}
});

inject(DestroyRef).onDestroy(dragGesture.destroy.bind(dragGesture));
});
}

@Directive({
selector: '[ngxDrag]',
standalone: true,
})
export class NgxDrag implements OnInit {
private config = signal<DragConfig>({});
@Input('ngxDragConfig') set _config(config: DragConfig) {
this.config.set(config);
}
@Input('ngxDragZoneless') zoneless?: boolean;
@Output() ngxDrag = new EventEmitter<NgxDragState>();

private injector = inject(Injector);

ngOnInit(): void {
injectDrag(this.ngxDrag.emit.bind(this.ngxDrag), {
injector: this.injector,
zoneless: this.zoneless,
config: this.config,
});
}
}
2 changes: 2 additions & 0 deletions libs/ngxtension/gestures/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './drag';
export * from './zoneless-gesture';
9 changes: 9 additions & 0 deletions libs/ngxtension/gestures/src/zoneless-gesture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createInjectionToken } from 'ngxtension/create-injection-token';

const [injectZonelessGesture, provideFn] = createInjectionToken(() => false);

function provideZonelessGesture() {
return provideFn(true);
}

export { injectZonelessGesture, provideZonelessGesture };
4 changes: 2 additions & 2 deletions libs/ngxtension/if-validator/src/if-validator.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
AbstractControl,
AsyncValidatorFn,
FormControl,
ValidatorFn,
Validators,
type AsyncValidatorFn,
type ValidatorFn,
} from '@angular/forms';
import { of } from 'rxjs';

Expand Down
4 changes: 2 additions & 2 deletions libs/ngxtension/intl/src/display-names.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {
inject,
LOCALE_ID,
Pipe,
PipeTransform,
Provider,
type PipeTransform,
type Provider,
} from '@angular/core';
import { createInjectionToken } from 'ngxtension/create-injection-token';

Expand Down
4 changes: 2 additions & 2 deletions libs/ngxtension/intl/src/list-format.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {
inject,
LOCALE_ID,
Pipe,
PipeTransform,
Provider,
type PipeTransform,
type Provider,
} from '@angular/core';
import { createInjectionToken } from 'ngxtension/create-injection-token';

Expand Down
4 changes: 2 additions & 2 deletions libs/ngxtension/intl/src/plural-rules.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {
inject,
LOCALE_ID,
Pipe,
PipeTransform,
Provider,
type PipeTransform,
type Provider,
} from '@angular/core';
import { createInjectionToken } from 'ngxtension/create-injection-token';

Expand Down
4 changes: 2 additions & 2 deletions libs/ngxtension/intl/src/relative-time-format.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {
inject,
LOCALE_ID,
Pipe,
PipeTransform,
Provider,
type PipeTransform,
type Provider,
} from '@angular/core';
import { createInjectionToken } from 'ngxtension/create-injection-token';

Expand Down
2 changes: 1 addition & 1 deletion libs/ngxtension/intl/src/supportedValuesOf.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Pipe, PipeTransform } from '@angular/core';
import { Pipe, type PipeTransform } from '@angular/core';

/**
* This pipe is a wrapper around the [Intl.supportedValuesOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/supportedValuesOf) method.
Expand Down
1 change: 1 addition & 0 deletions libs/ngxtension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"peerDependencies": {
"@angular/common": "^16.0.0",
"@angular/core": "^16.0.0",
"@use-gesture/vanilla": "^10.0.0",
"rxjs": "^6.0.0 || ^7.0.0"
},
"dependencies": {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@angular/platform-browser-dynamic": "~16.2.7",
"@angular/router": "~16.2.7",
"@swc/helpers": "0.5.2",
"@use-gesture/vanilla": "^10.3.0",
"rxjs": "~7.8.1",
"tslib": "^2.6.2",
"zone.js": "~0.13.3"
Expand Down
Loading

0 comments on commit d2c0719

Please sign in to comment.