├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── package.json ├── src ├── index.ts ├── swing-card-component.ts ├── swing-stack-component.ts ├── swing.module.ts └── swing.ts ├── tsconfig.json ├── tslint.json └── typings.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | typings 4 | aot 5 | dist 6 | .idea/ 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src 3 | typings 4 | tsconfig.json 5 | tslint.json 6 | typings.json 7 | aot 8 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Kapil Sachdeva 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular2-swing 2 | 3 | # Build package 4 | 5 | `npm run build` 6 | 7 | dont forget `typings install` 8 | 9 | # Install 10 | npm install angular2-swing --save 11 | 12 | # Usage 13 | ```JavaScript 14 | import {Component, ViewChild, ViewChildren, QueryList} from '@angular/core'; 15 | 16 | import { 17 | Direction, 18 | StackConfig, 19 | Stack, 20 | Card, 21 | ThrowEvent, 22 | DragEvent, 23 | SwingStackComponent, 24 | SwingCardComponent} from 'angular2-swing'; 25 | 26 | @Component({ 27 | selector: 'app', 28 | template: ` 29 |
30 | 33 |
34 |
35 |

Drag the playing cards out of the stack and let go. Dragging them 36 | beyond the desk will throw them out of the stack. If you drag too 37 | little and let go, the cards will spring back into place. You can 38 | throw cards back into the stack after you have thrown them out.

39 |

Open the 40 | Console to view the associated events.

41 |

Demonstration of 42 | https://github.com/ksachdeva/angular2-swing implementation.

43 |
44 | 45 | ` 46 | }) 47 | export class AppComponent { 48 | 49 | @ViewChild('myswing1') swingStack: SwingStackComponent; 50 | @ViewChildren('mycards1') swingCards: QueryList; 51 | 52 | cards: Array; 53 | stackConfig: StackConfig; 54 | 55 | constructor() { 56 | 57 | this.stackConfig = { 58 | // Default setting only allows UP, LEFT and RIGHT so you can override this as below 59 | allowedDirections: [Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT], 60 | // Now need to send offsetX and offsetY with element instead of just offset 61 | throwOutConfidence: (offsetX, offsetY, element) => { 62 | return Math.min(Math.max(Math.abs(offsetX) / (element.offsetWidth / 1.7), Math.abs(offsetY) / (element.offsetHeight / 2)), 1); 63 | }, 64 | throwOutDistance: (d) => { 65 | return 800; 66 | } 67 | } 68 | 69 | this.cards = [ 70 | { name: 'clubs', symbol: '♣' }, 71 | { name: 'diamonds', symbol: '♦' }, 72 | { name: 'spades', symbol: '♠' } 73 | ]; 74 | } 75 | 76 | ngAfterViewInit() { 77 | // ViewChild & ViewChildren are only available 78 | // in this function 79 | 80 | console.log(this.swingStack); // this is the stack 81 | console.log(this.swingCards); // this is a list of cards 82 | 83 | // we can get the underlying stack 84 | // which has methods - createCard, destroyCard, getCard etc 85 | console.log(this.swingStack.stack); 86 | 87 | // and the cards 88 | // every card has methods - destroy, throwIn, throwOut etc 89 | this.swingCards.forEach((c) => console.log(c.getCard())); 90 | 91 | // this is how you can manually hook up to the 92 | // events instead of providing the event method in the template 93 | this.swingStack.throwoutleft.subscribe( 94 | (event: ThrowEvent) => console.log('Manual hook: ', event)); 95 | 96 | this.swingStack.dragstart.subscribe((event: DragEvent) => console.log(event)); 97 | 98 | this.swingStack.dragmove.subscribe((event: DragEvent) => console.log(event)); 99 | } 100 | 101 | // This method is called by hooking up the event 102 | // on the HTML element - see the template above 103 | onThrowOut(event: ThrowEvent) { 104 | console.log('Hook from the template', event.throwDirection); 105 | } 106 | } 107 | 108 | ``` 109 | 110 | See [angular2-swing-example](https://github.com/ksachdeva/angular2-swing-example) repository for the full example 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-swing", 3 | "version": "0.12.0", 4 | "description": "Angular 2 Component for Swing (Tinder style cards)", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "scripts": { 8 | "build-app": "./node_modules/.bin/ngc", 9 | "clean": "rm -rf ./dist && rm -rf ./aot", 10 | "build": "npm run clean && npm run build-app" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/ksachdeva/angular2-swing.git" 15 | }, 16 | "author": "Kapil Sachdeva ", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/ksachdeva/angular2-swing/issues" 20 | }, 21 | "homepage": "https://github.com/ksachdeva/angular2-swing#readme", 22 | "dependencies": { 23 | "swing": "^3.1.1" 24 | }, 25 | "devDependencies": { 26 | "@angular/common": "^4.0.0", 27 | "@angular/compiler": "^4.0.0", 28 | "@angular/core": "^4.0.0", 29 | "@angular/cli": "1.0.0", 30 | "@angular/compiler-cli": "^4.0.0", 31 | "core-js": "^2.4.1", 32 | "reflect-metadata": "^0.1.3", 33 | "rxjs": "5.0.0-beta.12", 34 | "zone.js": "^0.8.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { SwingStackComponent } from './swing-stack-component'; 2 | export { SwingCardComponent } from './swing-card-component'; 3 | export { 4 | ThrowEvent, 5 | DragEvent, 6 | ThrowEventName, 7 | DragEventName, 8 | Card, 9 | Stack, 10 | StackConfig, 11 | Direction } from './swing'; 12 | export { SwingModule } from './swing.module'; 13 | -------------------------------------------------------------------------------- /src/swing-card-component.ts: -------------------------------------------------------------------------------- 1 | import {Component, ElementRef, Host, Input} from '@angular/core'; 2 | import {SwingStackComponent} from './swing-stack-component'; 3 | import {Card} from './swing'; 4 | 5 | @Component({ 6 | selector: '[swing-card]', 7 | template: ` 8 | 9 | ` 10 | }) 11 | export class SwingCardComponent { 12 | @Input() prepend: boolean = false; 13 | 14 | card: Card; 15 | 16 | constructor( 17 | private elmentRef: ElementRef, 18 | private swingStack: SwingStackComponent) { 19 | } 20 | 21 | ngOnInit() { 22 | this.swingStack.addCard(this, this.prepend); 23 | } 24 | 25 | getElementRef() { 26 | return this.elmentRef; 27 | } 28 | 29 | getNativeElement() { 30 | return this.elmentRef.nativeElement; 31 | } 32 | 33 | getCard(): Card { 34 | return this.swingStack.stack.getCard(this.getNativeElement()); 35 | } 36 | 37 | destroyCard() { 38 | this.swingStack.cards = this.swingStack.cards.filter(swingCardComponent => swingCardComponent !== this); 39 | let card = this.swingStack.stack.getCard(this.getNativeElement()); 40 | this.swingStack.stack.destroyCard(card); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/swing-stack-component.ts: -------------------------------------------------------------------------------- 1 | declare var require: any; 2 | 3 | import {Component, Input, ContentChildren, QueryList, 4 | AfterContentInit, EventEmitter } from '@angular/core'; 5 | 6 | import { Direction, ThrowEvent, DragEvent, Stack, Card, StackConfig } from './swing'; 7 | import {SwingCardComponent} from './swing-card-component'; 8 | 9 | const Swing = require('swing'); 10 | 11 | @Component({ 12 | selector: '[swing-stack]', 13 | template: ` 14 | 15 | `, 16 | outputs: [ 17 | 'throwout', 18 | 'throwoutend', 19 | 'throwoutleft', 20 | 'throwoutright', 21 | 'throwoutup', 22 | 'throwoutdown', 23 | 'throwin', 24 | 'throwinend', 25 | 'dragstart', 26 | 'dragmove', 27 | 'dragend', 28 | ] 29 | }) 30 | export class SwingStackComponent implements AfterContentInit { 31 | 32 | @Input() stackConfig: StackConfig; 33 | 34 | throwout: EventEmitter = new EventEmitter(); 35 | throwoutend: EventEmitter = new EventEmitter(); 36 | throwoutleft: EventEmitter = new EventEmitter(); 37 | throwoutright: EventEmitter = new EventEmitter(); 38 | throwoutup: EventEmitter = new EventEmitter(); 39 | throwoutdown: EventEmitter = new EventEmitter(); 40 | throwin: EventEmitter = new EventEmitter(); 41 | throwinend: EventEmitter = new EventEmitter(); 42 | 43 | dragstart: EventEmitter = new EventEmitter(); 44 | dragmove: EventEmitter = new EventEmitter(); 45 | dragend: EventEmitter = new EventEmitter(); 46 | 47 | cards: SwingCardComponent[]; 48 | stack: Stack; 49 | 50 | constructor() { 51 | this.cards = []; 52 | } 53 | 54 | addCard(card: SwingCardComponent, prepend: boolean = false) { 55 | this.cards.push(card); 56 | if (this.stack) { 57 | this.stack.createCard(card.getNativeElement(), prepend); 58 | } 59 | } 60 | 61 | ngAfterContentInit() { 62 | this.stack = Swing.Stack(this.stackConfig || {}); 63 | this.cards.forEach((c) => this.stack.createCard(c.getNativeElement(), c.prepend)); 64 | 65 | // Hook various events 66 | this.stack.on('throwout', $event => this.throwout.emit($event)); 67 | this.stack.on('throwoutend', $event => this.throwoutend.emit($event)); 68 | this.stack.on('throwoutleft', $event => this.throwoutleft.emit($event)); 69 | this.stack.on('throwoutright', $event => this.throwoutright.emit($event)); 70 | this.stack.on('throwin', $event => this.throwin.emit($event)); 71 | this.stack.on('throwinend', $event => this.throwinend.emit($event)); 72 | this.stack.on('dragstart', $event => this.dragstart.emit($event)); 73 | this.stack.on('dragmove', $event => this.dragmove.emit($event)); 74 | this.stack.on('dragend', $event => this.dragend.emit($event)); 75 | this.stack.on('throwoutup', $event => this.throwoutup.emit($event)); 76 | this.stack.on('throwoutdown', $event => this.throwoutdown.emit($event)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/swing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { SwingStackComponent } from './swing-stack-component'; 5 | import { SwingCardComponent } from './swing-card-component'; 6 | 7 | @NgModule({ 8 | imports: [CommonModule], 9 | declarations: [SwingStackComponent, SwingCardComponent], 10 | exports: [SwingCardComponent, SwingStackComponent] 11 | }) 12 | export class SwingModule { } 13 | -------------------------------------------------------------------------------- /src/swing.ts: -------------------------------------------------------------------------------- 1 | // here the ambient definitions for the swing 2 | // module are specified. Normally they should be at DefinitelyTyped 3 | // or better with the repository 4 | declare var require: any; 5 | const Swing = require('swing'); 6 | 7 | export interface ThrowEvent { 8 | /** 9 | * The element being dragged. 10 | */ 11 | target: HTMLElement; 12 | 13 | /** 14 | * The direction in which the element is being dragged: Card.DIRECTION_LEFT 15 | * or Card.DIRECTION_RIGHT 16 | */ 17 | throwDirection: Direction; 18 | } 19 | 20 | export interface DragEvent { 21 | /** 22 | * The element being dragged. 23 | */ 24 | target: HTMLElement; 25 | 26 | /** 27 | * Only available when the event is dragmove 28 | */ 29 | throwOutConfidence?: number; 30 | /** 31 | * Only available when the event is dragmove 32 | */ 33 | throwDirection?: Direction; 34 | /** 35 | * Only available when the event is dragmove 36 | */ 37 | offset?: number; 38 | 39 | } 40 | 41 | export type ThrowEventName = 'throwin' | 'throwinend' | 42 | 'throwout' | 'throwoutend' | 'throwoutleft' | 'throwoutup' | 'throwoutdown' | 'throwoutright'; 43 | 44 | export type DragEventName = 'dragstart' | 'dragmove' | 'dragend'; 45 | 46 | export interface Card { 47 | /** 48 | * Unbinds all Hammer.Manager events. 49 | * Removes the listeners from the physics simulation. 50 | * 51 | * @return {undefined} 52 | */ 53 | destroy(): void; 54 | 55 | /** 56 | * Throws a card into the stack from an arbitrary position. 57 | * 58 | * @param {Number} fromX 59 | * @param {Number} fromY 60 | * @return {undefined} 61 | */ 62 | throwIn(x: number, y: number): void; 63 | 64 | /** 65 | * Throws a card out of the stack in the direction away from the original offset. 66 | * 67 | * @param {Number} fromX 68 | * @param {Number} fromY 69 | * @return {undefined} 70 | */ 71 | throwOut(x: number, y: number): void; 72 | 73 | on(eventName: ThrowEventName, callabck: (event: ThrowEvent) => void): void; 74 | on(eventName: DragEventName, callabck: (event: DragEvent) => void): void; 75 | } 76 | 77 | export interface Stack { 78 | 79 | /** 80 | * Creates an instance of Card and associates it with an element. 81 | * 82 | * @param {HTMLElement} element 83 | * @param {boolean} prepend 84 | * @return {Card} 85 | */ 86 | createCard(elment: HTMLElement, prepend?: boolean): void; 87 | 88 | /** 89 | * Returns an instance of Card associated with an element. 90 | * 91 | * @param {HTMLElement} element 92 | * @return {Card|null} 93 | */ 94 | getCard(element: HTMLElement): Card; 95 | 96 | /** 97 | * 98 | *@param {Card} card 99 | */ 100 | destroyCard(card: Card): void; 101 | 102 | on(eventName: ThrowEventName, callabck: (event: ThrowEvent) => void): void; 103 | on(eventName: DragEventName, callabck: (event: DragEvent) => void): void; 104 | } 105 | 106 | export interface StackConfig { 107 | 108 | minThrowOutDistance?: number; 109 | maxThrowOutDistance?: number; 110 | maxRotation?: number; 111 | allowedDirections?: Array; 112 | 113 | /** 114 | * Determines if element is being thrown out of the stack. 115 | * 116 | * Element is considered to be thrown out when throwOutConfidence is equal to 1. 117 | * 118 | * @param {Number} offset Distance from the dragStart. 119 | * @param {HTMLElement} element Element. 120 | * @param {Number} throwOutConfidence config.throwOutConfidence 121 | * @return {Boolean} 122 | */ 123 | isThrowOut?: (offset: number, element: HTMLElement, throwOutConfidence: number) => boolean; 124 | 125 | /** 126 | * Returns a value between 0 and 1 indicating the completeness of the throw out condition. 127 | * 128 | * Ration of the absolute distance from the original card position and element width. 129 | * 130 | * @param {Number} offsetX Distance from the dragStart. 131 | * @param {Number} offsetY Distance from the dragStart. 132 | * @param {HTMLElement} element Element. 133 | * @return {Number} 134 | */ 135 | throwOutConfidence?: (offsetX: number, offsetY: number, element: HTMLElement) => number; 136 | 137 | /** 138 | * Calculates a distances at which the card is thrown out of the stack. 139 | * 140 | * @param {Number} min 141 | * @param {Number} max 142 | * @return {Number} 143 | */ 144 | throwOutDistance?: (min: number, max: number) => number; 145 | 146 | /** 147 | * Calculates rotation based on the element x and y offset, element width and 148 | * maxRotation variables. 149 | * 150 | * @param {Number} x Horizontal offset from the startDrag. 151 | * @param {Number} y Vertical offset from the startDrag. 152 | * @param {HTMLElement} element Element. 153 | * @param {Number} maxRotation 154 | * @return {Number} Rotation angle expressed in degrees. 155 | */ 156 | rotation?: (x: number, y: number, element: HTMLElement, maxRotation: number) => number; 157 | 158 | /** 159 | * Uses CSS transform to translate element position and rotation. 160 | * 161 | * Invoked in the event of `dragmove` and every time the physics solver is triggered. 162 | * 163 | * @param {HTMLElement} element 164 | * @param {Number} x Horizontal offset from the startDrag. 165 | * @param {Number} y Vertical offset from the startDrag. 166 | * @param {Number} r 167 | * @return {undefined} 168 | */ 169 | transform?: (element: HTMLElement, x: number, y: number, r: number) => void; 170 | } 171 | 172 | export enum Direction { 173 | DOWN = Swing.Direction.DOWN, 174 | INVALID = Swing.Direction.INVALID, 175 | LEFT = Swing.Direction.LEFT, 176 | RIGHT = Swing.Direction.RIGHT, 177 | UP = Swing.Direction.UP 178 | } 179 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "es2015", 5 | "declaration": true, 6 | "module": "commonjs", 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "sourceMap": true, 10 | "outDir": "dist", 11 | "removeComments": false 12 | }, 13 | "angularCompilerOptions": { 14 | "genDir": "aot" 15 | }, 16 | "include": [ 17 | "src/**/*", 18 | "typings/**/*" 19 | ], 20 | "exclude": [ 21 | "node_modules", 22 | "typings/main.d.ts", 23 | "typings/main" 24 | ], 25 | "compileOnSave": false, 26 | "buildOnSave": false, 27 | "atom": { 28 | "rewriteTsconfig": false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "curly": false, 5 | "eofline": true, 6 | "indent": [ 7 | true, 8 | "spaces" 9 | ], 10 | "max-line-length": [ 11 | true, 12 | 100 13 | ], 14 | "member-ordering": [ 15 | true, 16 | "public-before-private", 17 | "static-before-instance", 18 | "variables-before-functions" 19 | ], 20 | "no-arg": true, 21 | "no-construct": true, 22 | "no-duplicate-key": true, 23 | "no-duplicate-variable": true, 24 | "no-empty": false, 25 | "no-eval": true, 26 | "trailing-comma": true, 27 | "no-trailing-whitespace": true, 28 | "no-unused-expression": true, 29 | "no-unused-variable": false, 30 | "no-unreachable": true, 31 | "no-use-before-declare": true, 32 | "one-line": [ 33 | true, 34 | "check-open-brace", 35 | "check-catch", 36 | "check-else", 37 | "check-whitespace" 38 | ], 39 | "quotemark": [ 40 | true, 41 | "single" 42 | ], 43 | "semicolon": true, 44 | "triple-equals": [ 45 | true, 46 | "allow-null-check" 47 | ], 48 | "variable-name": false, 49 | "whitespace": [ 50 | true, 51 | "check-branch", 52 | "check-decl", 53 | "check-operator", 54 | "check-separator", 55 | "check-type" 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "core-js": "registry:dt/core-js#0.0.0+20160725163759" 4 | } 5 | } 6 | --------------------------------------------------------------------------------