├── src ├── assets │ ├── .gitkeep │ ├── demo.pdf │ ├── fonts │ │ └── RobotoMono-Regular.ttf │ └── logo.svg ├── app │ ├── layout │ │ ├── app-menu │ │ │ ├── app-menu.component.scss │ │ │ ├── app-menu.component.ts │ │ │ └── app-menu.component.html │ │ └── layout.module.ts │ ├── pages │ │ ├── demo-resizable │ │ │ ├── resizable-grid │ │ │ │ ├── resizable-grid.component.scss │ │ │ │ ├── resizable-grid.component.ts │ │ │ │ └── resizable-grid.component.html │ │ │ ├── resizable-basic │ │ │ │ ├── resizable-basic.component.scss │ │ │ │ ├── resizable-basic.component.html │ │ │ │ └── resizable-basic.component.ts │ │ │ ├── resizable-events │ │ │ │ ├── resizable-events.component.scss │ │ │ │ ├── resizable-events.component.html │ │ │ │ └── resizable-events.component.ts │ │ │ ├── resizable-layout │ │ │ │ ├── resizable-layout.component.scss │ │ │ │ ├── resizable-layout.component.ts │ │ │ │ └── resizable-layout.component.html │ │ │ ├── resizable-min-max │ │ │ │ ├── resizable-min-max.component.scss │ │ │ │ ├── resizable-min-max.component.html │ │ │ │ └── resizable-min-max.component.ts │ │ │ ├── resizable-containment │ │ │ │ ├── resizable-containment.component.scss │ │ │ │ ├── resizable-containment.component.html │ │ │ │ └── resizable-containment.component.ts │ │ │ ├── demo-resizable-routing.module.ts │ │ │ ├── resizable-iframe │ │ │ │ ├── resizable-iframe.component.scss │ │ │ │ ├── resizable-iframe.component.html │ │ │ │ └── resizable-iframe.component.ts │ │ │ └── demo-resizable.module.ts │ │ ├── demo-draggable │ │ │ ├── draggable-basic │ │ │ │ ├── draggable-basic.component.scss │ │ │ │ ├── draggable-basic.component.html │ │ │ │ └── draggable-basic.component.ts │ │ │ ├── draggable-events │ │ │ │ ├── draggable-events.component.scss │ │ │ │ ├── draggable-events.component.html │ │ │ │ └── draggable-events.component.ts │ │ │ ├── draggable-layout │ │ │ │ ├── draggable-layout.component.scss │ │ │ │ ├── draggable-layout.component.ts │ │ │ │ └── draggable-layout.component.html │ │ │ ├── draggable-boundary │ │ │ │ ├── draggable-boundary.component.scss │ │ │ │ ├── draggable-boundary.component.ts │ │ │ │ └── draggable-boundary.component.html │ │ │ ├── draggable-methods │ │ │ │ ├── draggable-methods.component.scss │ │ │ │ ├── draggable-methods.component.html │ │ │ │ └── draggable-methods.component.ts │ │ │ ├── draggable-options │ │ │ │ ├── draggable-options.component.scss │ │ │ │ ├── draggable-options.component.ts │ │ │ │ └── draggable-options.component.html │ │ │ ├── draggable-swap │ │ │ │ ├── draggable-swap.component.scss │ │ │ │ ├── draggable-swap.component.html │ │ │ │ └── draggable-swap.component.ts │ │ │ ├── demo-draggable-routing.module.ts │ │ │ ├── draggable-grid │ │ │ │ ├── draggable-grid.component.scss │ │ │ │ ├── draggable-grid.component.ts │ │ │ │ └── draggable-grid.component.html │ │ │ └── demo-draggable.module.ts │ │ └── welcome │ │ │ ├── welcome.component.scss │ │ │ ├── welcome-routing.module.ts │ │ │ ├── welcome.module.ts │ │ │ ├── welcome.component.ts │ │ │ └── welcome.component.html │ ├── shared │ │ ├── code-block │ │ │ ├── code-block.component.scss │ │ │ ├── code-block.component.html │ │ │ └── code-block.component.ts │ │ ├── icons-provider.module.ts │ │ ├── shared.module.ts │ │ └── ng-zorro-custom.module.ts │ ├── app.component.ts │ ├── menus.ts │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.module.ts │ └── app.component.scss ├── styles.scss ├── main.ts ├── index.html ├── styles │ ├── _resizable.scss │ └── _custom.scss └── prism-material-dark.css ├── .vscode ├── extensions.json ├── settings.json ├── launch.json └── tasks.json ├── projects └── angular2-draggable │ ├── src │ ├── scss │ │ ├── _variables.scss │ │ └── resizable.scss │ ├── public-api.ts │ └── lib │ │ ├── models │ │ ├── resize-handle-type.ts │ │ ├── resize-event.ts │ │ ├── size.ts │ │ └── position.ts │ │ ├── angular-draggable.module.ts │ │ ├── widgets │ │ ├── helper-block.ts │ │ └── resize-handle.ts │ │ ├── angular-draggable.directive.ts │ │ └── angular-resizable.directive.ts │ ├── ng-package.json │ ├── tsconfig.lib.prod.json │ ├── tsconfig.spec.json │ ├── tsconfig.lib.json │ ├── package.json │ └── LICENSE ├── .prettierrc.json ├── tsconfig.app.json ├── tsconfig.spec.json ├── .editorconfig ├── .prettierignore ├── scripts └── build-scss.js ├── .gitignore ├── .github └── workflows │ └── ci.yaml ├── LICENSE ├── tsconfig.json ├── .eslintrc.json ├── package.json ├── angular.json ├── README.md └── CHANGELOG.md /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/layout/app-menu/app-menu.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-grid/resizable-grid.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-basic/draggable-basic.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-events/draggable-events.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-layout/draggable-layout.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-basic/resizable-basic.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-events/resizable-events.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-layout/resizable-layout.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-boundary/draggable-boundary.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-methods/draggable-methods.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-options/draggable-options.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-min-max/resizable-min-max.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-containment/resizable-containment.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shared/code-block/code-block.component.scss: -------------------------------------------------------------------------------- 1 | .code-block { 2 | margin-top: 16px; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/demo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieziyu/angular2-draggable/HEAD/src/assets/demo.pdf -------------------------------------------------------------------------------- /src/app/pages/welcome/welcome.component.scss: -------------------------------------------------------------------------------- 1 | section.jumbotron { 2 | background-color: #fff !important; 3 | } -------------------------------------------------------------------------------- /src/assets/fonts/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieziyu/angular2-draggable/HEAD/src/assets/fonts/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import './styles/custom'; 3 | @import './styles/resizable'; -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /projects/angular2-draggable/src/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | $handle-border-size: 7px; 2 | $handle-border-offset: -5px; 3 | 4 | $handle-corner-size: 12px; 5 | $handle-corner-offset: 1px; -------------------------------------------------------------------------------- /projects/angular2-draggable/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/angular2-draggable", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "singleQuote": true, 5 | "semi": true, 6 | "arrowParens": "avoid", 7 | "trailingComma": "es5", 8 | "bracketSameLine": true, 9 | "printWidth": 100 10 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app/app.module'; 4 | 5 | platformBrowserDynamic() 6 | .bootstrapModule(AppModule) 7 | .catch(err => console.error(err)); 8 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-basic/draggable-basic.component.html: -------------------------------------------------------------------------------- 1 | 2 |
Drag Me!
3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'], 7 | }) 8 | export class AppComponent { 9 | isCollapsed = false; 10 | } 11 | -------------------------------------------------------------------------------- /projects/angular2-draggable/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of angular2-draggable 3 | */ 4 | 5 | export * from './lib/angular-draggable.directive'; 6 | export * from './lib/angular-resizable.directive'; 7 | export * from './lib/angular-draggable.module'; 8 | export * from './lib/models/position'; 9 | -------------------------------------------------------------------------------- /projects/angular2-draggable/src/lib/models/resize-handle-type.ts: -------------------------------------------------------------------------------- 1 | export interface ResizeHandleStyle { 2 | n?: string; 3 | s?: string; 4 | e?: string; 5 | w?: string; 6 | ne?: string; 7 | nw?: string; 8 | se?: string; 9 | sw?: string; 10 | } 11 | 12 | export type ResizeHandleType = string | ResizeHandleStyle; 13 | -------------------------------------------------------------------------------- /projects/angular2-draggable/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-layout/draggable-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-draggable-layout', 5 | templateUrl: './draggable-layout.component.html', 6 | styleUrls: ['./draggable-layout.component.scss'], 7 | }) 8 | export class DraggableLayoutComponent {} 9 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-layout/resizable-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-resizable-layout', 5 | templateUrl: './resizable-layout.component.html', 6 | styleUrls: ['./resizable-layout.component.scss'], 7 | }) 8 | export class ResizableLayoutComponent {} 9 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-basic/resizable-basic.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

Resizable

5 |
6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /projects/angular2-draggable/src/lib/models/resize-event.ts: -------------------------------------------------------------------------------- 1 | import { ISize } from './size'; 2 | 3 | export interface IResizeEvent { 4 | host: any; 5 | handle: any; 6 | size: ISize; 7 | position: { 8 | top: number; 9 | left: number; 10 | }; 11 | direction: { 12 | n: boolean; 13 | s: boolean; 14 | w: boolean; 15 | e: boolean; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /projects/angular2-draggable/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "**/*.spec.ts", 12 | "**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular2DraggableDemo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/angular2-draggable/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/lib", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-min-max/resizable-min-max.component.html: -------------------------------------------------------------------------------- 1 | 2 |
9 |

Resizable

10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/app/pages/welcome/welcome-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { WelcomeComponent } from './welcome.component'; 4 | 5 | const routes: Routes = [{ path: '', component: WelcomeComponent }]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule], 10 | }) 11 | export class WelcomeRoutingModule {} 12 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/pages/welcome/welcome.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { SharedModule } from '../../shared/shared.module'; 3 | import { WelcomeRoutingModule } from './welcome-routing.module'; 4 | import { WelcomeComponent } from './welcome.component'; 5 | 6 | @NgModule({ 7 | imports: [WelcomeRoutingModule, SharedModule], 8 | declarations: [WelcomeComponent], 9 | exports: [WelcomeComponent], 10 | }) 11 | export class WelcomeModule {} 12 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-swap/draggable-swap.component.scss: -------------------------------------------------------------------------------- 1 | .box-container { 2 | position: relative; 3 | height: 150px; 4 | padding: 45px 0; 5 | } 6 | 7 | .box-swap { 8 | position: absolute; 9 | text-align: center; 10 | width: 150px; 11 | height: 60px; 12 | padding: 15px 35px; 13 | line-height: 30px; 14 | color: white; 15 | 16 | &.static-block { 17 | transition: all 0.3s ease-out; 18 | background-color: #ff4d4f; 19 | } 20 | } -------------------------------------------------------------------------------- /projects/angular2-draggable/src/lib/angular-draggable.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { AngularDraggableDirective } from './angular-draggable.directive'; 3 | import { AngularResizableDirective } from './angular-resizable.directive'; 4 | 5 | @NgModule({ 6 | imports: [], 7 | declarations: [AngularDraggableDirective, AngularResizableDirective], 8 | exports: [AngularDraggableDirective, AngularResizableDirective], 9 | }) 10 | export class AngularDraggableModule {} 11 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-methods/draggable-methods.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 | 8 |
Drag Me!
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/app/layout/layout.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { SharedModule } from '../shared/shared.module'; 5 | import { AppMenuComponent } from './app-menu/app-menu.component'; 6 | 7 | @NgModule({ 8 | declarations: [AppMenuComponent], 9 | imports: [CommonModule, SharedModule, RouterModule], 10 | exports: [AppMenuComponent], 11 | }) 12 | export class LayoutModule {} 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[html]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": "explicit" 6 | }, 7 | "editor.formatOnSave": false 8 | }, 9 | "[typescript]": { 10 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 11 | "editor.codeActionsOnSave": { 12 | "source.fixAll.eslint": "explicit" 13 | }, 14 | "editor.formatOnSave": false 15 | }, 16 | "eslint.format.enable": true 17 | } -------------------------------------------------------------------------------- /src/app/shared/icons-provider.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { NzIconModule } from 'ng-zorro-antd/icon'; 3 | 4 | import { 5 | MenuFoldOutline, 6 | MenuUnfoldOutline, 7 | FormOutline, 8 | DashboardOutline, 9 | } from '@ant-design/icons-angular/icons'; 10 | 11 | const icons = [MenuFoldOutline, MenuUnfoldOutline, DashboardOutline, FormOutline]; 12 | 13 | @NgModule({ 14 | imports: [NzIconModule.forRoot(icons)], 15 | exports: [NzIconModule], 16 | }) 17 | export class IconsProviderModule {} 18 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/demo-draggable-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { DraggableLayoutComponent } from './draggable-layout/draggable-layout.component'; 4 | 5 | const routes: Routes = [ 6 | { path: '', redirectTo: 'demo', pathMatch: 'full' }, 7 | { path: 'demo', component: DraggableLayoutComponent }, 8 | ]; 9 | 10 | @NgModule({ 11 | imports: [RouterModule.forChild(routes)], 12 | exports: [RouterModule], 13 | }) 14 | export class DemoDraggableRoutingModule {} 15 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/demo-resizable-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { ResizableLayoutComponent } from './resizable-layout/resizable-layout.component'; 4 | 5 | const routes: Routes = [ 6 | { path: '', redirectTo: 'demo', pathMatch: 'full' }, 7 | { path: 'demo', component: ResizableLayoutComponent }, 8 | ]; 9 | 10 | @NgModule({ 11 | imports: [RouterModule.forChild(routes)], 12 | exports: [RouterModule], 13 | }) 14 | export class DemoResizableRoutingModule {} 15 | -------------------------------------------------------------------------------- /src/styles/_resizable.scss: -------------------------------------------------------------------------------- 1 | .resizable-widget { 2 | width: 200px; 3 | height: 200px; 4 | background-color: $white; 5 | border: solid 1px $gray-400; 6 | padding: 0.5em; 7 | box-sizing: content-box; 8 | } 9 | 10 | .widget-header { 11 | text-align: center; 12 | border: 1px solid $gray-400; 13 | background: #e9e9e9; 14 | color: #333333; 15 | font-weight: bold; 16 | padding: 0.25em; 17 | } 18 | 19 | .widget-container { 20 | width: 300px; 21 | height: 300px; 22 | padding: 0.5em; 23 | background-color: $blue; 24 | border: solid 1px $gray-400; 25 | } -------------------------------------------------------------------------------- /src/app/menus.ts: -------------------------------------------------------------------------------- 1 | import type { ThemeType } from '@ant-design/icons-angular'; 2 | 3 | export interface AppMenu { 4 | path: string; 5 | text: string; 6 | icon?: string; 7 | iconTheme?: ThemeType; 8 | submenus?: AppMenu[]; 9 | } 10 | 11 | export const APP_MENUS: AppMenu[] = [ 12 | { 13 | path: '/welcome', 14 | icon: 'home', 15 | text: 'Home', 16 | }, 17 | { 18 | path: '/draggable', 19 | icon: 'drag', 20 | text: 'Draggable', 21 | }, 22 | { 23 | path: '/resizable', 24 | icon: 'expand', 25 | text: 'Resizable', 26 | }, 27 | ]; 28 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-grid/draggable-grid.component.scss: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | position: relative; 3 | height: 250px; 4 | width: 250px; 5 | } 6 | 7 | .drag-grid { 8 | position: absolute; 9 | width: 50px; 10 | height: 50px; 11 | background-color: #f8cb00; 12 | color: white; 13 | line-height: 50px; 14 | text-align: center; 15 | } 16 | 17 | .grid { 18 | position: absolute; 19 | width: 50px; 20 | height: 50px; 21 | background-color: #20c997; 22 | color: white; 23 | text-align: center; 24 | line-height: 50px; 25 | border: 1px dashed #067002; 26 | } -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-iframe/resizable-iframe.component.scss: -------------------------------------------------------------------------------- 1 | .iframe-drag-bounds { 2 | width: 100%; 3 | height: 400px; 4 | border: 1px dashed #A4B7C1; 5 | } 6 | 7 | .iframe-resize-container { 8 | position: relative; 9 | width: 300px; 10 | height: 300px; 11 | } 12 | 13 | .iframe-drag-handle { 14 | position: absolute; 15 | top: 0; 16 | right: 35px; 17 | height: 2em; 18 | background: #cccccc; 19 | z-index: 8; 20 | padding: 0.25em; 21 | text-align: right; 22 | } 23 | 24 | .iframe-content { 25 | position: absolute; 26 | height: 100%; 27 | top: 0; 28 | left: 0; 29 | } -------------------------------------------------------------------------------- /projects/angular2-draggable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-draggable", 3 | "version": "16.0.0", 4 | "author": "Xie, Ziyu", 5 | "license": "MIT", 6 | "keywords": [ 7 | "angular", 8 | "ng", 9 | "draggable", 10 | "resizable" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/xieziyu/angular2-draggable.git" 15 | }, 16 | "homepage": "https://xieziyu.github.io/angular2-draggable", 17 | "bugs": "https://github.com/xieziyu/angular2-draggable/issues", 18 | "dependencies": { 19 | "tslib": "^2.3.0" 20 | }, 21 | "sideEffects": false 22 | } 23 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Compiled output 2 | /dist 3 | /tmp 4 | /out-tsc 5 | /bazel-out 6 | 7 | # Node 8 | /node_modules 9 | npm-debug.log 10 | yarn-error.log 11 | 12 | # IDEs and editors 13 | .idea/ 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # Visual Studio Code 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | .history/* 28 | 29 | # Miscellaneous 30 | /.angular/cache 31 | .sass-cache/ 32 | /connect.lock 33 | /coverage 34 | /libpeerconnection.log 35 | testem.log 36 | /typings 37 | 38 | # System files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /scripts/build-scss.js: -------------------------------------------------------------------------------- 1 | const sass = require('node-sass'); 2 | const fs = require('fs'); 3 | 4 | const SCSS_FILE = './projects/angular2-draggable/src/scss/resizable.scss'; 5 | const OUTPUT_PATH = './dist/angular2-draggable/css'; 6 | 7 | const result = sass.renderSync({ 8 | file: SCSS_FILE, 9 | outputStyle: 'expanded' 10 | }); 11 | 12 | const minResult = sass.renderSync({ 13 | file: SCSS_FILE, 14 | outputStyle: 'compressed' 15 | }); 16 | 17 | try { 18 | fs.mkdirSync(OUTPUT_PATH); 19 | } catch(e) { 20 | // no need 21 | } 22 | fs.writeFileSync(OUTPUT_PATH + '/resizable.css', result.css); 23 | fs.writeFileSync(OUTPUT_PATH + '/resizable.min.css', minResult.css); 24 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-swap/draggable-swap.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
11 | A (Drag) 12 |
13 |
18 | B 19 |
20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-grid/resizable-grid.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // IGNORE START 3 | declare const require: any; 4 | // IGNORE END 5 | 6 | @Component({ 7 | selector: 'app-resizable-grid', 8 | templateUrl: './resizable-grid.component.html', 9 | styleUrls: ['./resizable-grid.component.scss'], 10 | }) 11 | export class ResizableGridComponent { 12 | // IGNORE START 13 | html = 14 | require('!!html-loader?{"minimize": {"removeComments":false,"caseSensitive":true}}!./resizable-grid.component.html') 15 | .default; 16 | component = require('!!raw-loader!./resizable-grid.component.ts').default; 17 | // IGNORE END 18 | } 19 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-basic/draggable-basic.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // IGNORE START 3 | declare const require: any; 4 | // IGNORE END 5 | 6 | @Component({ 7 | selector: 'app-draggable-basic', 8 | templateUrl: './draggable-basic.component.html', 9 | styleUrls: ['./draggable-basic.component.scss'], 10 | }) 11 | export class DraggableBasicComponent { 12 | // IGNORE START 13 | html = 14 | require('!!html-loader?{"minimize": {"removeComments":false,"caseSensitive":true}}!./draggable-basic.component.html') 15 | .default; 16 | component = require('!!raw-loader!./draggable-basic.component.ts').default; 17 | // IGNORE END 18 | } 19 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-basic/resizable-basic.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // IGNORE START 3 | declare const require: any; 4 | // IGNORE END 5 | 6 | @Component({ 7 | selector: 'app-resizable-basic', 8 | templateUrl: './resizable-basic.component.html', 9 | styleUrls: ['./resizable-basic.component.scss'], 10 | }) 11 | export class ResizableBasicComponent { 12 | // IGNORE START 13 | html = 14 | require('!!html-loader?{"minimize": {"removeComments":false,"caseSensitive":true}}!./resizable-basic.component.html') 15 | .default; 16 | component = require('!!raw-loader!./resizable-basic.component.ts').default; 17 | // IGNORE END 18 | } 19 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-grid/resizable-grid.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

Resizable

6 |
[rzGrid]=50
7 |
8 |
9 | 10 |
11 |

Resizable

12 |
[rzGrid]=[50, 100]
13 |
14 |
15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-methods/draggable-methods.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // IGNORE START 3 | declare const require: any; 4 | // IGNORE END 5 | 6 | @Component({ 7 | selector: 'app-draggable-methods', 8 | templateUrl: './draggable-methods.component.html', 9 | styleUrls: ['./draggable-methods.component.scss'], 10 | }) 11 | export class DraggableMethodsComponent { 12 | // IGNORE START 13 | html = 14 | require('!!html-loader?{"minimize": {"removeComments":false,"caseSensitive":true}}!./draggable-methods.component.html') 15 | .default; 16 | component = require('!!raw-loader!./draggable-methods.component.ts').default; 17 | // IGNORE END 18 | } 19 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-min-max/resizable-min-max.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // IGNORE START 3 | declare const require: any; 4 | // IGNORE END 5 | 6 | @Component({ 7 | selector: 'app-resizable-min-max', 8 | templateUrl: './resizable-min-max.component.html', 9 | styleUrls: ['./resizable-min-max.component.scss'], 10 | }) 11 | export class ResizableMinMaxComponent { 12 | // IGNORE START 13 | html = 14 | require('!!html-loader?{"minimize": {"removeComments":false,"caseSensitive":true}}!./resizable-min-max.component.html') 15 | .default; 16 | component = require('!!raw-loader!./resizable-min-max.component.ts').default; 17 | // IGNORE END 18 | } 19 | -------------------------------------------------------------------------------- /src/app/shared/code-block/code-block.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-containment/resizable-containment.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

Containment

6 | 7 |
13 |

Resizable

14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-containment/resizable-containment.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // IGNORE START 3 | declare const require: any; 4 | // IGNORE END 5 | 6 | @Component({ 7 | selector: 'app-resizable-containment', 8 | templateUrl: './resizable-containment.component.html', 9 | styleUrls: ['./resizable-containment.component.scss'], 10 | }) 11 | export class ResizableContainmentComponent { 12 | // IGNORE START 13 | html = 14 | require('!!html-loader?{"minimize": {"removeComments":false,"caseSensitive":true}}!./resizable-containment.component.html') 15 | .default; 16 | component = require('!!raw-loader!./resizable-containment.component.ts').default; 17 | // IGNORE END 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-grid/draggable-grid.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // IGNORE START 3 | declare const require: any; 4 | // IGNORE END 5 | 6 | @Component({ 7 | selector: 'app-draggable-grid', 8 | templateUrl: './draggable-grid.component.html', 9 | styleUrls: ['./draggable-grid.component.scss'], 10 | }) 11 | export class DraggableGridComponent { 12 | // IGNORE START 13 | html = 14 | require('!!html-loader?{"minimize": {"removeComments":false,"caseSensitive":true}}!./draggable-grid.component.html') 15 | .default; 16 | component = require('!!raw-loader!./draggable-grid.component.ts').default; 17 | // IGNORE END 18 | gridSize = 50; 19 | grids = [0, 50, 100, 150, 200]; 20 | } 21 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-iframe/resizable-iframe.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
12 |
13 |
Draggable & Resizable
14 |
15 | 16 |
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-iframe/resizable-iframe.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // IGNORE START 3 | declare const require: any; 4 | // IGNORE END 5 | 6 | @Component({ 7 | selector: 'app-resizable-iframe', 8 | templateUrl: './resizable-iframe.component.html', 9 | styleUrls: ['./resizable-iframe.component.scss'], 10 | }) 11 | export class ResizableIframeComponent { 12 | // IGNORE START 13 | html = 14 | require('!!html-loader?{"minimize": {"removeComments":false,"caseSensitive":true}}!./resizable-iframe.component.html') 15 | .default; 16 | component = require('!!raw-loader!./resizable-iframe.component.ts').default; 17 | scss = require('!!raw-loader!./resizable-iframe.component.scss').default; 18 | // IGNORE END 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - fix/action 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | - name: Setup Node 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: 18 19 | cache: 'yarn' 20 | - name: Install 21 | run: yarn 22 | - name: Build 23 | run: yarn release 24 | - name: Deploy to GitHub Pages 25 | if: success() 26 | uses: crazy-max/ghaction-github-pages@v3 27 | with: 28 | target_branch: gh-pages 29 | build_dir: dist/docs 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-grid/draggable-grid.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
12 |

Drag

13 |
14 | 15 |
16 |
17 |
22 | grid 23 |
24 |
25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | const routes: Routes = [ 5 | { path: '', pathMatch: 'full', redirectTo: '/welcome' }, 6 | { 7 | path: 'welcome', 8 | loadChildren: () => import('./pages/welcome/welcome.module').then(m => m.WelcomeModule), 9 | }, 10 | { 11 | path: 'draggable', 12 | loadChildren: () => 13 | import('./pages/demo-draggable/demo-draggable.module').then(m => m.DemoDraggableModule), 14 | }, 15 | { 16 | path: 'resizable', 17 | loadChildren: () => 18 | import('./pages/demo-resizable/demo-resizable.module').then(m => m.DemoResizableModule), 19 | }, 20 | ]; 21 | 22 | @NgModule({ 23 | imports: [RouterModule.forRoot(routes)], 24 | exports: [RouterModule], 25 | }) 26 | export class AppRoutingModule {} 27 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-options/draggable-options.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // IGNORE START 3 | declare const require: any; 4 | // IGNORE END 5 | 6 | @Component({ 7 | selector: 'app-draggable-options', 8 | templateUrl: './draggable-options.component.html', 9 | styleUrls: ['./draggable-options.component.scss'], 10 | }) 11 | export class DraggableOptionsComponent { 12 | // IGNORE START 13 | html = 14 | require('!!html-loader?{"minimize": {"removeComments":false,"caseSensitive":true}}!./draggable-options.component.html') 15 | .default; 16 | component = require('!!raw-loader!./draggable-options.component.ts').default; 17 | // IGNORE END 18 | draggable = true; 19 | useHandle = false; 20 | zIndex; 21 | zIndexMoving; 22 | preventDefaultEvent = false; 23 | trackPosition = true; 24 | position; 25 | lockAxis; 26 | } 27 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NgZorroCustomModule } from './ng-zorro-custom.module'; 4 | import { MarkdownModule } from 'ngx-markdown'; 5 | import { AngularDraggableModule } from 'angular2-draggable'; 6 | 7 | import { IconsProviderModule } from './icons-provider.module'; 8 | import { CodeBlockComponent } from './code-block/code-block.component'; 9 | 10 | @NgModule({ 11 | declarations: [CodeBlockComponent], 12 | imports: [ 13 | CommonModule, 14 | IconsProviderModule, 15 | NgZorroCustomModule, 16 | MarkdownModule.forChild(), 17 | AngularDraggableModule, 18 | ], 19 | exports: [ 20 | IconsProviderModule, 21 | NgZorroCustomModule, 22 | MarkdownModule, 23 | CodeBlockComponent, 24 | AngularDraggableModule, 25 | ], 26 | }) 27 | export class SharedModule {} 28 | -------------------------------------------------------------------------------- /projects/angular2-draggable/src/lib/models/size.ts: -------------------------------------------------------------------------------- 1 | export interface ISize { 2 | width: number; 3 | height: number; 4 | } 5 | 6 | export class Size implements ISize { 7 | constructor(public width: number, public height: number) {} 8 | 9 | static getCurrent(el: Element) { 10 | let size = new Size(0, 0); 11 | 12 | if (window) { 13 | const computed = window.getComputedStyle(el); 14 | if (computed) { 15 | size.width = parseInt(computed.getPropertyValue('width'), 10); 16 | size.height = parseInt(computed.getPropertyValue('height'), 10); 17 | } 18 | return size; 19 | } else { 20 | console.error('Not Supported!'); 21 | return null; 22 | } 23 | } 24 | 25 | static copy(s: Size) { 26 | return new Size(0, 0).set(s); 27 | } 28 | 29 | set(s: ISize) { 30 | this.width = s.width; 31 | this.height = s.height; 32 | return this; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-events/draggable-events.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 |
9 | 12 |
13 | 14 |
22 |

Drag me

23 |

check your console

24 |
25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/app/layout/app-menu/app-menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { Router, NavigationEnd } from '@angular/router'; 3 | import { filter } from 'rxjs/operators'; 4 | import { AppMenu, APP_MENUS } from '../../menus'; 5 | 6 | interface AppMenuEx extends AppMenu { 7 | pathRegex: RegExp; 8 | } 9 | 10 | @Component({ 11 | selector: 'app-menu', 12 | templateUrl: './app-menu.component.html', 13 | styleUrls: ['./app-menu.component.scss'], 14 | }) 15 | export class AppMenuComponent { 16 | @Input() isCollapsed: boolean; 17 | menus: AppMenuEx[]; 18 | currentUrl: string; 19 | 20 | constructor(private router: Router) { 21 | router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe((e: NavigationEnd) => { 22 | this.currentUrl = e.url; 23 | }); 24 | this.menus = APP_MENUS.map(m => ({ 25 | path: m.path, 26 | pathRegex: new RegExp(m.path), 27 | text: m.text, 28 | icon: m.icon, 29 | iconTheme: m.iconTheme, 30 | submenus: m.submenus ? m.submenus.concat() : undefined, 31 | })); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Xie, Ziyu 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 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-events/resizable-events.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 |
18 |

19 | Resizable 20 | 21 | 22 |

23 |
24 |

State: {{ state }}

25 |

Size: {{ size | json }}

26 |

Position: {{ position | json }}

27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "paths": { 6 | "angular2-draggable": [ 7 | "dist/angular2-draggable" 8 | ] 9 | }, 10 | "baseUrl": "./", 11 | "outDir": "./dist/out-tsc", 12 | "forceConsistentCasingInFileNames": true, 13 | "strict": false, 14 | "noImplicitOverride": true, 15 | "noPropertyAccessFromIndexSignature": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "sourceMap": true, 19 | "declaration": false, 20 | "downlevelIteration": true, 21 | "experimentalDecorators": true, 22 | "moduleResolution": "node", 23 | "importHelpers": true, 24 | "target": "ES2022", 25 | "module": "ES2022", 26 | "useDefineForClassFields": false, 27 | "lib": [ 28 | "ES2022", 29 | "dom" 30 | ] 31 | }, 32 | "angularCompilerOptions": { 33 | "enableI18nLegacyMessageIdFormat": false, 34 | "strictInjectionParameters": true, 35 | "strictInputAccessModifiers": true, 36 | "strictTemplates": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /projects/angular2-draggable/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Xie, Ziyu 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 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-events/draggable-events.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // IGNORE START 3 | declare const require: any; 4 | // IGNORE END 5 | 6 | @Component({ 7 | selector: 'app-draggable-events', 8 | templateUrl: './draggable-events.component.html', 9 | styleUrls: ['./draggable-events.component.scss'], 10 | }) 11 | export class DraggableEventsComponent { 12 | // IGNORE START 13 | html = 14 | require('!!html-loader?{"minimize": {"removeComments":false,"caseSensitive":true}}!./draggable-events.component.html') 15 | .default; 16 | component = require('!!raw-loader!./draggable-events.component.ts').default; 17 | // IGNORE END 18 | movingOffset = { x: 0, y: 0 }; 19 | endOffset = { x: 0, y: 0 }; 20 | 21 | onStart(event) { 22 | console.log('started output:', event); 23 | } 24 | 25 | onStop(event) { 26 | console.log('stopped output:', event); 27 | } 28 | 29 | onMoving(event) { 30 | this.movingOffset.x = event.x; 31 | this.movingOffset.y = event.y; 32 | } 33 | 34 | onMoveEnd(event) { 35 | this.endOffset.x = event.x; 36 | this.endOffset.y = event.y; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "overrides": [ 4 | { 5 | "files": ["*.ts"], 6 | "extends": [ 7 | "plugin:@angular-eslint/recommended", 8 | "plugin:@angular-eslint/template/process-inline-templates", 9 | "plugin:prettier/recommended" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": "off", 13 | "@angular-eslint/component-selector": "off" 14 | } 15 | }, 16 | // NOTE: WE ARE NOT APPLYING PRETTIER IN THIS OVERRIDE, ONLY @ANGULAR-ESLINT/TEMPLATE 17 | { 18 | "files": ["*.html"], 19 | "extends": ["plugin:@angular-eslint/template/recommended"], 20 | "rules": {} 21 | }, 22 | // NOTE: WE ARE NOT APPLYING @ANGULAR-ESLINT/TEMPLATE IN THIS OVERRIDE, ONLY PRETTIER 23 | { 24 | "files": ["*.html"], 25 | "excludedFiles": ["*inline-template-*.component.html"], 26 | "extends": ["plugin:prettier/recommended"], 27 | "rules": { 28 | // NOTE: WE ARE OVERRIDING THE DEFAULT CONFIG TO ALWAYS SET THE PARSER TO ANGULAR (SEE BELOW) 29 | "prettier/prettier": ["error", { "parser": "angular" }] 30 | } 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /src/app/shared/code-block/code-block.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-code-block', 5 | templateUrl: './code-block.component.html', 6 | styleUrls: ['./code-block.component.scss'], 7 | }) 8 | export class CodeBlockComponent implements OnInit { 9 | @Input() html: string; 10 | @Input() component: string; 11 | @Input() scss: string; 12 | @Input() data: string; 13 | htmlCode: string; 14 | componentCode: string; 15 | scssCode: string; 16 | dataCode: string; 17 | 18 | constructor() {} 19 | 20 | ngOnInit() { 21 | if (this.html) { 22 | this.htmlCode = this.html.match(/DEMO START -->\n((.*\n)*) 32 |
Drag Me!
33 | 34 | 35 |
Disabled
36 | \`\`\``; 37 | resizableHTML = `## Resizable HTML 38 | \`\`\`html 39 | 40 |
I'm resizable
41 | 42 | 43 |
Disabled
44 | \`\`\``; 45 | resizableCSS = `## Resizable CSS 46 | \`\`\`diff 47 | // angular.json 48 | "styles": [ 49 | ... 50 | + "node_modules/angular2-draggable/css/resizable.min.css" 51 | ] 52 | \`\`\``; 53 | } 54 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-layout/resizable-layout.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Home 5 | 6 | Resizable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/demo-draggable.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { SharedModule } from '../../shared/shared.module'; 4 | import { DemoDraggableRoutingModule } from './demo-draggable-routing.module'; 5 | import { DraggableBasicComponent } from './draggable-basic/draggable-basic.component'; 6 | import { DraggableLayoutComponent } from './draggable-layout/draggable-layout.component'; 7 | import { DraggableOptionsComponent } from './draggable-options/draggable-options.component'; 8 | import { DraggableEventsComponent } from './draggable-events/draggable-events.component'; 9 | import { DraggableBoundaryComponent } from './draggable-boundary/draggable-boundary.component'; 10 | import { DraggableMethodsComponent } from './draggable-methods/draggable-methods.component'; 11 | import { DraggableGridComponent } from './draggable-grid/draggable-grid.component'; 12 | import { DraggableSwapComponent } from './draggable-swap/draggable-swap.component'; 13 | 14 | @NgModule({ 15 | declarations: [ 16 | DraggableBasicComponent, 17 | DraggableLayoutComponent, 18 | DraggableOptionsComponent, 19 | DraggableEventsComponent, 20 | DraggableBoundaryComponent, 21 | DraggableMethodsComponent, 22 | DraggableGridComponent, 23 | DraggableSwapComponent, 24 | ], 25 | imports: [CommonModule, SharedModule, DemoDraggableRoutingModule], 26 | }) 27 | export class DemoDraggableModule {} 28 | -------------------------------------------------------------------------------- /src/app/layout/app-menu/app-menu.component.html: -------------------------------------------------------------------------------- 1 | 40 | -------------------------------------------------------------------------------- /src/app/pages/demo-resizable/resizable-events/resizable-events.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { AngularResizableDirective } from 'angular2-draggable'; 3 | // IGNORE START 4 | declare const require: any; 5 | // IGNORE END 6 | 7 | @Component({ 8 | selector: 'app-resizable-events', 9 | templateUrl: './resizable-events.component.html', 10 | styleUrls: ['./resizable-events.component.scss'], 11 | }) 12 | export class ResizableEventsComponent { 13 | // IGNORE START 14 | html = 15 | require('!!html-loader?{"minimize": {"removeComments":false,"caseSensitive":true}}!./resizable-events.component.html') 16 | .default; 17 | component = require('!!raw-loader!./resizable-events.component.ts').default; 18 | // IGNORE END 19 | state = ''; 20 | size: any = null; 21 | position: any = null; 22 | aspectRatio = false; 23 | 24 | onResizeStart(event) { 25 | this.state = 'Resize Started'; 26 | this.size = event.size; 27 | this.position = event.position; 28 | } 29 | 30 | onResizing(event) { 31 | this.state = 'Resizing'; 32 | this.size = event.size; 33 | this.position = event.position; 34 | } 35 | 36 | onResizeStop(event) { 37 | this.state = 'Resize Stopped'; 38 | this.size = event.size; 39 | this.position = event.position; 40 | } 41 | 42 | onReset(block: AngularResizableDirective) { 43 | block.resetSize(); 44 | let { size, position } = block.getStatus(); 45 | this.size = size; 46 | this.position = position; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/app/shared/ng-zorro-custom.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { NZ_I18N, en_US } from 'ng-zorro-antd/i18n'; 3 | import { NzIconModule } from 'ng-zorro-antd/icon'; 4 | import { NzMenuModule } from 'ng-zorro-antd/menu'; 5 | import { NzGridModule } from 'ng-zorro-antd/grid'; 6 | import { NzBreadCrumbModule } from 'ng-zorro-antd/breadcrumb'; 7 | import { NzLayoutModule } from 'ng-zorro-antd/layout'; 8 | import { NzButtonModule } from 'ng-zorro-antd/button'; 9 | import { NzMessageModule } from 'ng-zorro-antd/message'; 10 | import { NzTabsModule } from 'ng-zorro-antd/tabs'; 11 | import { NzCardModule } from 'ng-zorro-antd/card'; 12 | import { NzDividerModule } from 'ng-zorro-antd/divider'; 13 | import { NzPageHeaderModule } from 'ng-zorro-antd/page-header'; 14 | import { NzAlertModule } from 'ng-zorro-antd/alert'; 15 | import { NzTypographyModule } from 'ng-zorro-antd/typography'; 16 | import { NzSpaceModule } from 'ng-zorro-antd/space'; 17 | 18 | const NG_ZORRO_MODULES = [ 19 | NzIconModule, 20 | NzMenuModule, 21 | NzGridModule, 22 | NzBreadCrumbModule, 23 | NzLayoutModule, 24 | NzButtonModule, 25 | NzMessageModule, 26 | NzTabsModule, 27 | NzCardModule, 28 | NzDividerModule, 29 | NzPageHeaderModule, 30 | NzAlertModule, 31 | NzTypographyModule, 32 | NzSpaceModule, 33 | ]; 34 | 35 | @NgModule({ 36 | imports: [...NG_ZORRO_MODULES], 37 | exports: [...NG_ZORRO_MODULES], 38 | providers: [{ provide: NZ_I18N, useValue: en_US }], 39 | }) 40 | export class NgZorroCustomModule {} 41 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-layout/draggable-layout.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Home 5 | 6 | Draggable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /projects/angular2-draggable/src/lib/widgets/resize-handle.ts: -------------------------------------------------------------------------------- 1 | import { Renderer2 } from '@angular/core'; 2 | 3 | export class ResizeHandle { 4 | protected _handle: Element; 5 | private _onResize; 6 | 7 | constructor( 8 | protected parent: Element, 9 | protected renderer: Renderer2, 10 | public type: string, 11 | public css: string, 12 | private onMouseDown: any, 13 | private existHandle?: Element 14 | ) { 15 | // generate handle div or using exist handle 16 | let handle = this.existHandle || renderer.createElement('div'); 17 | renderer.addClass(handle, 'ng-resizable-handle'); 18 | renderer.addClass(handle, css); 19 | 20 | // add default diagonal for se handle 21 | if (type === 'se') { 22 | renderer.addClass(handle, 'ng-resizable-diagonal'); 23 | } 24 | 25 | // append div to parent 26 | if (this.parent && !this.existHandle) { 27 | parent.appendChild(handle); 28 | } 29 | 30 | // create and register event listener 31 | this._onResize = event => { 32 | onMouseDown(event, this); 33 | }; 34 | handle.addEventListener('mousedown', this._onResize, { passive: false }); 35 | handle.addEventListener('touchstart', this._onResize, { passive: false }); 36 | 37 | // done 38 | this._handle = handle; 39 | } 40 | 41 | dispose() { 42 | this._handle.removeEventListener('mousedown', this._onResize); 43 | this._handle.removeEventListener('touchstart', this._onResize); 44 | 45 | if (this.parent && !this.existHandle) { 46 | this.parent.removeChild(this._handle); 47 | } 48 | this._handle = null; 49 | this._onResize = null; 50 | } 51 | 52 | get el() { 53 | return this._handle; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-swap/draggable-swap.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // IGNORE START 3 | declare const require: any; 4 | // IGNORE END 5 | 6 | @Component({ 7 | selector: 'app-draggable-swap', 8 | templateUrl: './draggable-swap.component.html', 9 | styleUrls: ['./draggable-swap.component.scss'], 10 | }) 11 | export class DraggableSwapComponent { 12 | // IGNORE START 13 | html = 14 | require('!!html-loader?{"minimize": {"removeComments":false,"caseSensitive":true}}!./draggable-swap.component.html') 15 | .default; 16 | component = require('!!raw-loader!./draggable-swap.component.ts').default; 17 | scss = require('!!raw-loader!./draggable-swap.component.scss').default; 18 | // IGNORE END 19 | positionA = { x: 0, y: 0 }; 20 | positionB = { x: 160, y: 0 }; 21 | 22 | onMoving(event) { 23 | const boxWidth = 150; 24 | const boxHeight = 60; 25 | 26 | if ( 27 | this.positionA.x < this.positionB.x && 28 | event.x + boxWidth >= this.positionB.x + boxWidth / 2 && 29 | event.x <= this.positionB.x + boxWidth && 30 | event.y + boxHeight >= this.positionA.y && 31 | event.y <= this.positionA.y + boxHeight 32 | ) { 33 | let tmp = this.positionB; 34 | this.positionB = this.positionA; 35 | this.positionA = tmp; 36 | } else if ( 37 | this.positionA.x >= this.positionB.x && 38 | event.x <= this.positionB.x + boxWidth / 2 && 39 | event.x + boxWidth >= this.positionB.x && 40 | event.y + boxHeight >= this.positionA.y && 41 | event.y <= this.positionA.y + boxHeight 42 | ) { 43 | let tmp = this.positionB; 44 | this.positionB = this.positionA; 45 | this.positionA = tmp; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | text-rendering: optimizeLegibility; 4 | -webkit-font-smoothing: antialiased; 5 | -moz-osx-font-smoothing: grayscale; 6 | } 7 | 8 | .app-layout { 9 | height: 100vh; 10 | } 11 | 12 | .menu-sidebar { 13 | position: relative; 14 | z-index: 10; 15 | min-height: 100vh; 16 | box-shadow: 2px 0 6px rgba(0,21,41,.35); 17 | } 18 | 19 | .header-trigger { 20 | padding: 20px 24px; 21 | font-size: 20px; 22 | cursor: pointer; 23 | transition: all .3s,padding 0s; 24 | } 25 | 26 | .trigger:hover { 27 | color: #1890ff; 28 | } 29 | 30 | .sidebar-logo { 31 | position: relative; 32 | height: 64px; 33 | padding-left: 24px; 34 | overflow: hidden; 35 | line-height: 64px; 36 | background: #001529; 37 | transition: all .3s; 38 | } 39 | 40 | .sidebar-logo img { 41 | display: inline-block; 42 | height: 32px; 43 | width: 32px; 44 | vertical-align: middle; 45 | } 46 | 47 | .sidebar-logo h1 { 48 | display: inline-block; 49 | margin: 0 0 0 20px; 50 | color: #fff; 51 | font-weight: 600; 52 | font-size: 25px; 53 | font-family: Avenir,Helvetica Neue,Arial,Helvetica,sans-serif; 54 | vertical-align: middle; 55 | } 56 | 57 | nz-header { 58 | padding: 0; 59 | width: 100%; 60 | z-index: 2; 61 | } 62 | 63 | .app-header { 64 | position: relative; 65 | height: 64px; 66 | padding: 0; 67 | background: #fff; 68 | box-shadow: 0 1px 4px rgba(0,21,41,.08); 69 | display: flex; 70 | justify-content: space-between; 71 | align-items: center; 72 | } 73 | 74 | .github-logo { 75 | padding: 0px 24px; 76 | font-size: 25px; 77 | cursor: pointer; 78 | transition: all .3s,padding 0s; 79 | } 80 | 81 | a.github-logo { 82 | color: rgba(0,0,0,.65) 83 | } 84 | 85 | nz-content { 86 | margin: 24px; 87 | } 88 | 89 | .inner-content { 90 | padding: 24px; 91 | background: #fff; 92 | // height: 100%; 93 | } 94 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-boundary/draggable-boundary.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

5 | 6 |

7 |

8 | 11 |

12 |

13 | 16 |

17 |

18 | 21 |

22 |

23 | 26 |

27 |
28 |
29 |
38 |

#myBounds

39 |
46 |

Drag me {{ inBounds ? 'in #myBounds' : '' }}

47 |

check your console

48 |
49 |
50 |
51 |
52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/app/pages/welcome/welcome.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | version 4 | Angular2-Draggable 5 |

6 |

7 | version 8 | downloads 9 |

10 |

11 | Angular(ver >= 4.x) directives that make the DOM element 12 | draggable and 13 | resizable 14 |

15 |

16 | GitHub 24 | Documents 31 |

32 |
33 | 34 |
35 |
36 |
37 | 38 |
39 |
40 | 41 |
42 |
43 | 44 |
45 |
46 | 47 |
48 |
49 | 50 |
51 |
52 |
53 | -------------------------------------------------------------------------------- /projects/angular2-draggable/src/scss/resizable.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | 3 | .ng-resizable { 4 | position: relative; 5 | } 6 | 7 | .ng-resizable-handle { 8 | position: absolute; 9 | font-size: 0.1px; 10 | display: block; 11 | -ms-touch-action: none; 12 | touch-action: none; 13 | 14 | &.ng-resizable-e { 15 | cursor: e-resize; 16 | width: $handle-border-size; 17 | right: $handle-border-offset; 18 | height: 100%; 19 | top: 0; 20 | } 21 | 22 | &.ng-resizable-w { 23 | cursor: w-resize; 24 | width: $handle-border-size; 25 | left: $handle-border-offset; 26 | height: 100%; 27 | top: 0; 28 | } 29 | 30 | &.ng-resizable-s { 31 | cursor: s-resize; 32 | height: $handle-border-size; 33 | bottom: $handle-border-offset; 34 | width: 100%; 35 | left: 0; 36 | } 37 | 38 | &.ng-resizable-n { 39 | cursor: n-resize; 40 | height: $handle-border-size; 41 | top: $handle-border-offset; 42 | width: 100%; 43 | left: 0; 44 | } 45 | 46 | &.ng-resizable-se { 47 | cursor: se-resize; 48 | width: $handle-corner-size; 49 | height: $handle-corner-size; 50 | right: $handle-corner-offset; 51 | bottom: $handle-corner-offset; 52 | } 53 | 54 | &.ng-resizable-sw { 55 | cursor: sw-resize; 56 | width: $handle-corner-size; 57 | height: $handle-corner-size; 58 | left: $handle-corner-offset; 59 | bottom: $handle-corner-offset; 60 | } 61 | 62 | &.ng-resizable-ne { 63 | cursor: ne-resize; 64 | width: $handle-corner-size; 65 | height: $handle-corner-size; 66 | right: $handle-corner-offset; 67 | top: $handle-corner-offset; 68 | } 69 | 70 | &.ng-resizable-nw { 71 | cursor: nw-resize; 72 | width: $handle-corner-size; 73 | height: $handle-corner-size; 74 | left: $handle-corner-offset; 75 | top: $handle-corner-offset; 76 | } 77 | } 78 | 79 | .ng-resizable-diagonal { 80 | box-sizing: border-box; 81 | width: 0; 82 | height: 0; 83 | border-bottom: $handle-corner-size solid #aaa; 84 | border-left: $handle-corner-size solid transparent; 85 | } 86 | 87 | -------------------------------------------------------------------------------- /src/styles/_custom.scss: -------------------------------------------------------------------------------- 1 | $white: #fff; 2 | $gray-100: #f0f3f5; 3 | $gray-200: #c2cfd6; 4 | $gray-300: #a4b7c1; 5 | $gray-400: #869fac; 6 | $gray-500: #678898; 7 | $gray-600: #536c79; 8 | $gray-700: #3e515b; 9 | $gray-800: #29363d; 10 | $gray-900: #151b1e; 11 | $black: #000 !default; 12 | 13 | $blue: #1890ff; 14 | $red: #ff4d4f; 15 | $green: #49aa19; 16 | 17 | @font-face { 18 | font-family: 'Roboto Mono'; 19 | src: url('../assets/fonts/RobotoMono-Regular.ttf'); 20 | } 21 | 22 | markdown table { 23 | margin: auto !important; 24 | } 25 | 26 | .text-center { 27 | text-align: center !important; 28 | } 29 | 30 | .mr-2 { 31 | margin-right: 0.5em; 32 | } 33 | 34 | .ant-page-header { 35 | padding: 0; 36 | } 37 | 38 | .code-block { 39 | pre { 40 | max-height: 400px; 41 | } 42 | } 43 | 44 | .code-block-sm { 45 | pre { 46 | height: 200px; 47 | overflow-y: auto; 48 | } 49 | } 50 | 51 | .button-groups { 52 | button { 53 | margin-right: 16px; 54 | } 55 | padding: 16px 0; 56 | } 57 | 58 | .drag-block-sm { 59 | width: 100px; 60 | height: 100px; 61 | color: white; 62 | line-height: 100px; 63 | text-align: center; 64 | } 65 | 66 | .drag-block { 67 | width: 200px; 68 | height: 200px; 69 | background-color: $blue; 70 | color: white; 71 | padding: 75px 0px; 72 | line-height: 50px; 73 | text-align: center; 74 | } 75 | 76 | .drag-block-lg { 77 | width: 300px; 78 | height: 300px; 79 | background-color: $red; 80 | padding-top: 20px; 81 | color: white; 82 | text-align: center; 83 | overflow-y: auto; 84 | } 85 | 86 | .drag-block-handle { 87 | width: 100px; 88 | height: 43px; 89 | margin-top: -20px; 90 | padding-top: 10px; 91 | margin-bottom: 10px; 92 | } 93 | 94 | .ng-draggable { 95 | cursor: grab; 96 | background-color: $blue; 97 | } 98 | 99 | .ng-dragging { 100 | cursor: grabbing; 101 | } 102 | 103 | .drag-boundary { 104 | width: 400px; 105 | height: 400px; 106 | overflow: hidden; 107 | border: solid 1px $green; 108 | position: relative; 109 | 110 | .label { 111 | color: white; 112 | background-color: $green; 113 | width: 100px; 114 | text-align: center; 115 | right: 0px; 116 | position: absolute; 117 | } 118 | } 119 | 120 | .top-b { 121 | border-top-color: $red !important; 122 | } 123 | 124 | .bottom-b { 125 | border-bottom-color: $red !important; 126 | } 127 | 128 | .left-b { 129 | border-left-color: $red !important; 130 | } 131 | 132 | .right-b { 133 | border-right-color: $red !important; 134 | } 135 | -------------------------------------------------------------------------------- /projects/angular2-draggable/src/lib/models/position.ts: -------------------------------------------------------------------------------- 1 | export interface IPosition { 2 | x: number; 3 | y: number; 4 | } 5 | 6 | export class Position implements IPosition { 7 | constructor(public x: number, public y: number) {} 8 | 9 | static fromEvent(e: MouseEvent | TouchEvent, el: any = null) { 10 | /** 11 | * Fix issue: Resize doesn't work on Windows10 IE11 (and on some windows 7 IE11) 12 | * https://github.com/xieziyu/angular2-draggable/issues/164 13 | * e instanceof MouseEvent check returns false on IE11 14 | */ 15 | if (this.isMouseEvent(e)) { 16 | return new Position(e.clientX, e.clientY); 17 | } else { 18 | if (el === null || e.changedTouches.length === 1) { 19 | return new Position(e.changedTouches[0].clientX, e.changedTouches[0].clientY); 20 | } 21 | 22 | /** 23 | * Fix issue: Multiple phone draggables at the same time 24 | * https://github.com/xieziyu/angular2-draggable/issues/128 25 | */ 26 | for (let i = 0; i < e.changedTouches.length; i++) { 27 | if (e.changedTouches[i].target === el) { 28 | return new Position(e.changedTouches[i].clientX, e.changedTouches[i].clientY); 29 | } 30 | } 31 | } 32 | return null; 33 | } 34 | 35 | static isMouseEvent(e: MouseEvent | TouchEvent): e is MouseEvent { 36 | return Object.prototype.toString.apply(e).indexOf('MouseEvent') === 8; 37 | } 38 | 39 | static isIPosition(obj): obj is IPosition { 40 | return !!obj && 'x' in obj && 'y' in obj; 41 | } 42 | 43 | static getCurrent(el: Element) { 44 | let pos = new Position(0, 0); 45 | 46 | if (window) { 47 | const computed = window.getComputedStyle(el); 48 | if (computed) { 49 | let x = parseInt(computed.getPropertyValue('left'), 10); 50 | let y = parseInt(computed.getPropertyValue('top'), 10); 51 | pos.x = isNaN(x) ? 0 : x; 52 | pos.y = isNaN(y) ? 0 : y; 53 | } 54 | return pos; 55 | } else { 56 | console.error('Not Supported!'); 57 | return null; 58 | } 59 | } 60 | 61 | static copy(p: IPosition) { 62 | return new Position(0, 0).set(p); 63 | } 64 | 65 | get value(): IPosition { 66 | return { x: this.x, y: this.y }; 67 | } 68 | 69 | add(p: IPosition) { 70 | this.x += p.x; 71 | this.y += p.y; 72 | return this; 73 | } 74 | 75 | subtract(p: IPosition) { 76 | this.x -= p.x; 77 | this.y -= p.y; 78 | return this; 79 | } 80 | 81 | multiply(n: number) { 82 | this.x *= n; 83 | this.y *= n; 84 | } 85 | 86 | divide(n: number) { 87 | this.x /= n; 88 | this.y /= n; 89 | } 90 | 91 | reset() { 92 | this.x = 0; 93 | this.y = 0; 94 | return this; 95 | } 96 | 97 | set(p: IPosition) { 98 | this.x = p.x; 99 | this.y = p.y; 100 | return this; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-draggable-demo", 3 | "version": "16.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve --port=4202", 8 | "build": "ng build", 9 | "build:prod": "ng build --configuration production --base-href ./", 10 | "build:lib": "ng build angular2-draggable", 11 | "build:lib:prod": "ng build angular2-draggable --configuration production", 12 | "build:css": "node scripts/build-scss.js", 13 | "test": "ng test", 14 | "test:lib": "ng test angular2-draggable", 15 | "lint": "ng lint", 16 | "fix": "ng lint --fix", 17 | "e2e": "ng e2e", 18 | "clean": "rimraf -rf dist", 19 | "compodoc": "compodoc -p projects/angular2-draggable/tsconfig.lib.json -d dist/docs/api-doc --theme stripe", 20 | "serve:doc": "compodoc -p projects/angular2-draggable/tsconfig.lib.json -s", 21 | "build:doc": "run-s build:prod compodoc", 22 | "update:file": "cpr README.md dist/angular2-draggable/README.md -o && cpr CHANGELOG.md dist/angular2-draggable/CHANGELOG.md -o", 23 | "release": "run-s clean dist build:doc", 24 | "demo": "run-s build:lib build:css start", 25 | "dist": "run-s clean build:lib:prod build:css update:file" 26 | }, 27 | "private": true, 28 | "dependencies": { 29 | "@angular/animations": "^16.0.0", 30 | "@angular/common": "^16.0.0", 31 | "@angular/compiler": "^16.0.0", 32 | "@angular/core": "^16.0.0", 33 | "@angular/forms": "^16.0.0", 34 | "@angular/platform-browser": "^16.0.0", 35 | "@angular/platform-browser-dynamic": "^16.0.0", 36 | "@angular/router": "^16.0.0", 37 | "@ant-design/icons-angular": "^15.0.0", 38 | "ng-zorro-antd": "15.1.0", 39 | "ngx-markdown": "^16.0.0", 40 | "prismjs": "^1.29.0", 41 | "rxjs": "~7.8.0", 42 | "tslib": "^2.3.0", 43 | "zone.js": "~0.13.0" 44 | }, 45 | "devDependencies": { 46 | "@angular-devkit/build-angular": "^16.0.2", 47 | "@angular-eslint/builder": "^16.0.2", 48 | "@angular-eslint/eslint-plugin": "^16.0.2", 49 | "@angular-eslint/eslint-plugin-template": "^16.0.2", 50 | "@angular-eslint/schematics": "^16.0.2", 51 | "@angular-eslint/template-parser": "^16.0.2", 52 | "@angular/cli": "~16.0.2", 53 | "@angular/compiler-cli": "^16.0.0", 54 | "@compodoc/compodoc": "^1.1.19", 55 | "@types/jasmine": "~4.3.0", 56 | "@typescript-eslint/eslint-plugin": "^5.59.6", 57 | "@typescript-eslint/parser": "^5.59.6", 58 | "cpr": "^3.0.1", 59 | "eslint": "^8.40.0", 60 | "eslint-config-prettier": "^8.8.0", 61 | "eslint-plugin-prettier": "^4.2.1", 62 | "html-loader": "^4.2.0", 63 | "jasmine-core": "~4.6.0", 64 | "karma": "~6.4.0", 65 | "karma-chrome-launcher": "~3.2.0", 66 | "karma-coverage": "~2.2.0", 67 | "karma-jasmine": "~5.1.0", 68 | "karma-jasmine-html-reporter": "~2.0.0", 69 | "ng-packagr": "^16.0.0", 70 | "node-sass": "^8.0.0", 71 | "npm-run-all": "^4.1.5", 72 | "prettier": "^2.8.8", 73 | "prettier-eslint": "^15.0.1", 74 | "raw-loader": "^4.0.2", 75 | "rimraf": "^5.0.1", 76 | "ts-node": "^10.9.1", 77 | "typescript": "~5.0.2" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/prism-material-dark.css: -------------------------------------------------------------------------------- 1 | code[class*="language-"], 2 | pre[class*="language-"] { 3 | text-align: left; 4 | white-space: pre; 5 | word-spacing: normal; 6 | word-break: normal; 7 | word-wrap: normal; 8 | color: #eee; 9 | background: #2f2f2f; 10 | font-family: Roboto Mono, monospace; 11 | font-size: 1em; 12 | line-height: 1.5em; 13 | 14 | -moz-tab-size: 4; 15 | -o-tab-size: 4; 16 | tab-size: 4; 17 | 18 | -webkit-hyphens: none; 19 | -moz-hyphens: none; 20 | -ms-hyphens: none; 21 | hyphens: none; 22 | } 23 | 24 | code[class*="language-"]::-moz-selection, 25 | pre[class*="language-"]::-moz-selection, 26 | code[class*="language-"] ::-moz-selection, 27 | pre[class*="language-"] ::-moz-selection { 28 | background: #363636; 29 | } 30 | 31 | code[class*="language-"]::selection, 32 | pre[class*="language-"]::selection, 33 | code[class*="language-"] ::selection, 34 | pre[class*="language-"] ::selection { 35 | background: #363636; 36 | } 37 | 38 | :not(pre) > code[class*="language-"] { 39 | white-space: normal; 40 | border-radius: 0.2em; 41 | padding: 0.1em; 42 | } 43 | 44 | pre[class*="language-"] { 45 | overflow: auto; 46 | position: relative; 47 | margin: 0.5em 0; 48 | padding: 1.25em 1em; 49 | } 50 | 51 | .language-css > code, 52 | .language-sass > code, 53 | .language-scss > code { 54 | color: #fd9170; 55 | } 56 | 57 | [class*="language-"] .namespace { 58 | opacity: 0.7; 59 | } 60 | 61 | .token.atrule { 62 | color: #c792ea; 63 | } 64 | 65 | .token.attr-name { 66 | color: #ffcb6b; 67 | } 68 | 69 | .token.attr-value { 70 | color: #a5e844; 71 | } 72 | 73 | .token.attribute { 74 | color: #a5e844; 75 | } 76 | 77 | .token.boolean { 78 | color: #c792ea; 79 | } 80 | 81 | .token.builtin { 82 | color: #ffcb6b; 83 | } 84 | 85 | .token.cdata { 86 | color: #80cbc4; 87 | } 88 | 89 | .token.char { 90 | color: #80cbc4; 91 | } 92 | 93 | .token.class { 94 | color: #ffcb6b; 95 | } 96 | 97 | .token.class-name { 98 | color: #f2ff00; 99 | } 100 | 101 | .token.comment { 102 | color: #616161; 103 | } 104 | 105 | .token.constant { 106 | color: #c792ea; 107 | } 108 | 109 | .token.deleted { 110 | color: #ff6666; 111 | } 112 | 113 | .token.doctype { 114 | color: #616161; 115 | } 116 | 117 | .token.entity { 118 | color: #ff6666; 119 | } 120 | 121 | .token.function { 122 | color: #c792ea; 123 | } 124 | 125 | .token.hexcode { 126 | color: #f2ff00; 127 | } 128 | 129 | .token.id { 130 | color: #c792ea; 131 | font-weight: bold; 132 | } 133 | 134 | .token.important { 135 | color: #c792ea; 136 | font-weight: bold; 137 | } 138 | 139 | .token.inserted { 140 | color: #80cbc4; 141 | } 142 | 143 | .token.keyword { 144 | color: #c792ea; 145 | } 146 | 147 | .token.number { 148 | color: #fd9170; 149 | } 150 | 151 | .token.operator { 152 | color: #89ddff; 153 | } 154 | 155 | .token.prolog { 156 | color: #616161; 157 | } 158 | 159 | .token.property { 160 | color: #80cbc4; 161 | } 162 | 163 | .token.pseudo-class { 164 | color: #a5e844; 165 | } 166 | 167 | .token.pseudo-element { 168 | color: #a5e844; 169 | } 170 | 171 | .token.punctuation { 172 | color: #89ddff; 173 | } 174 | 175 | .token.regex { 176 | color: #f2ff00; 177 | } 178 | 179 | .token.selector { 180 | color: #ff6666; 181 | } 182 | 183 | .token.string { 184 | color: #a5e844; 185 | } 186 | 187 | .token.symbol { 188 | color: #c792ea; 189 | } 190 | 191 | .token.tag { 192 | color: #ff6666; 193 | } 194 | 195 | .token.unit { 196 | color: #fd9170; 197 | } 198 | 199 | .token.url { 200 | color: #ff6666; 201 | } 202 | 203 | .token.variable { 204 | color: #ff6666; 205 | } 206 | -------------------------------------------------------------------------------- /src/app/pages/demo-draggable/draggable-options/draggable-options.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 | 8 |

9 |

10 | 16 |

17 |

18 | 19 |

20 |

21 | 22 |

23 |

24 | 27 |

28 |

29 | 32 |

33 |

34 | 37 |

38 |

39 | 42 |

43 |
44 | 45 |
46 |
57 |
#myHandle
58 |

[handle]="myHandle"

59 |

[ngDraggable] = {{ draggable }}

60 |

[position] = {{ position === undefined ? 'undefined' : (position | json) }}

61 |

[zIndex] = {{ zIndex === undefined ? 'undefined' : zIndex }}

62 |

[zIndexMoving] = {{ zIndexMoving === undefined ? 'undefined' : zIndexMoving }}

63 |

[preventDefaultEvent] = {{ preventDefaultEvent }}

64 |

[trackPosition] = {{ trackPosition }}

65 |

[lockAxis] = {{ lockAxis === undefined ? 'undefined' : lockAxis }}

66 | Drag Me! 67 |
68 |
78 |

[ngDraggable] = {{ draggable }}

79 |

[position] = {{ position === undefined ? 'undefined' : (position | json) }}

80 |

[zIndex] = {{ zIndex === undefined ? 'undefined' : zIndex }}

81 |

[zIndexMoving] = {{ zIndexMoving === undefined ? 'undefined' : zIndexMoving }}

82 |

[preventDefaultEvent] = {{ preventDefaultEvent }}

83 |

[trackPosition] = {{ trackPosition }}

84 |

[lockAxis] = {{ lockAxis === undefined ? 'undefined' : lockAxis }}

85 | Drag Me! 86 |
87 |
88 |
89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "cli": { 6 | "packageManager": "yarn", 7 | "schematicCollections": [ 8 | "@angular-eslint/schematics" 9 | ] 10 | }, 11 | "projects": { 12 | "angular2-draggable-demo": { 13 | "projectType": "application", 14 | "schematics": { 15 | "@schematics/angular:component": { 16 | "style": "scss", 17 | "skipTests": true 18 | }, 19 | "@schematics/angular:service": { 20 | "skipTests": true 21 | } 22 | }, 23 | "root": "", 24 | "sourceRoot": "src", 25 | "prefix": "app", 26 | "architect": { 27 | "build": { 28 | "builder": "@angular-devkit/build-angular:browser", 29 | "options": { 30 | "outputPath": "dist/docs", 31 | "index": "src/index.html", 32 | "main": "src/main.ts", 33 | "polyfills": [ 34 | "zone.js" 35 | ], 36 | "tsConfig": "tsconfig.app.json", 37 | "inlineStyleLanguage": "scss", 38 | "assets": [ 39 | "src/favicon.ico", 40 | "src/assets", 41 | { 42 | "glob": "**/*", 43 | "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/", 44 | "output": "/assets/" 45 | } 46 | ], 47 | "styles": [ 48 | "./node_modules/ng-zorro-antd/ng-zorro-antd.min.css", 49 | "src/styles.scss", 50 | "src/prism-material-dark.css", 51 | "dist/angular2-draggable/css/resizable.min.css" 52 | ], 53 | "scripts": [ 54 | "node_modules/marked/marked.min.js", 55 | "node_modules/prismjs/prism.js", 56 | "node_modules/prismjs/components/prism-markup.min.js", 57 | "node_modules/prismjs/components/prism-css.min.js", 58 | "node_modules/prismjs/components/prism-scss.min.js", 59 | "node_modules/prismjs/components/prism-typescript.min.js", 60 | "node_modules/prismjs/components/prism-javascript.min.js", 61 | "node_modules/prismjs/components/prism-bash.min.js", 62 | "node_modules/prismjs/components/prism-diff.min.js" 63 | ] 64 | }, 65 | "configurations": { 66 | "production": { 67 | "budgets": [ 68 | { 69 | "type": "initial", 70 | "maximumWarning": "2mb", 71 | "maximumError": "5mb" 72 | }, 73 | { 74 | "type": "anyComponentStyle", 75 | "maximumWarning": "6kb", 76 | "maximumError": "10kb" 77 | } 78 | ], 79 | "outputHashing": "all" 80 | }, 81 | "development": { 82 | "buildOptimizer": false, 83 | "optimization": false, 84 | "vendorChunk": true, 85 | "extractLicenses": false, 86 | "sourceMap": true, 87 | "namedChunks": true 88 | } 89 | }, 90 | "defaultConfiguration": "production" 91 | }, 92 | "serve": { 93 | "builder": "@angular-devkit/build-angular:dev-server", 94 | "configurations": { 95 | "production": { 96 | "browserTarget": "angular2-draggable-demo:build:production" 97 | }, 98 | "development": { 99 | "browserTarget": "angular2-draggable-demo:build:development" 100 | } 101 | }, 102 | "defaultConfiguration": "development" 103 | }, 104 | "extract-i18n": { 105 | "builder": "@angular-devkit/build-angular:extract-i18n", 106 | "options": { 107 | "browserTarget": "angular2-draggable-demo:build" 108 | } 109 | }, 110 | "test": { 111 | "builder": "@angular-devkit/build-angular:karma", 112 | "options": { 113 | "polyfills": [ 114 | "zone.js", 115 | "zone.js/testing" 116 | ], 117 | "tsConfig": "tsconfig.spec.json", 118 | "inlineStyleLanguage": "scss", 119 | "assets": [ 120 | "src/favicon.ico", 121 | "src/assets" 122 | ], 123 | "styles": [ 124 | "./node_modules/ng-zorro-antd/ng-zorro-antd.min.css", 125 | "src/styles.scss" 126 | ], 127 | "scripts": [] 128 | } 129 | }, 130 | "lint": { 131 | "builder": "@angular-eslint/builder:lint", 132 | "options": { 133 | "lintFilePatterns": [ 134 | "src/**/*.ts", 135 | "src/**/*.html" 136 | ] 137 | } 138 | } 139 | } 140 | }, 141 | "angular2-draggable": { 142 | "projectType": "library", 143 | "schematics": { 144 | "@schematics/angular:component": { 145 | "style": "scss", 146 | "skipTests": true 147 | }, 148 | "@schematics/angular:service": { 149 | "skipTests": true 150 | }, 151 | "@schematics/angular:directive": { 152 | "skipTests": true 153 | } 154 | }, 155 | "root": "projects/angular2-draggable", 156 | "sourceRoot": "projects/angular2-draggable/src", 157 | "prefix": "lib", 158 | "architect": { 159 | "build": { 160 | "builder": "@angular-devkit/build-angular:ng-packagr", 161 | "options": { 162 | "project": "projects/angular2-draggable/ng-package.json" 163 | }, 164 | "configurations": { 165 | "production": { 166 | "tsConfig": "projects/angular2-draggable/tsconfig.lib.prod.json" 167 | }, 168 | "development": { 169 | "tsConfig": "projects/angular2-draggable/tsconfig.lib.json" 170 | } 171 | }, 172 | "defaultConfiguration": "production" 173 | }, 174 | "test": { 175 | "builder": "@angular-devkit/build-angular:karma", 176 | "options": { 177 | "tsConfig": "projects/angular2-draggable/tsconfig.spec.json", 178 | "polyfills": [ 179 | "zone.js", 180 | "zone.js/testing" 181 | ] 182 | } 183 | }, 184 | "lint": { 185 | "builder": "@angular-eslint/builder:lint", 186 | "options": { 187 | "lintFilePatterns": [ 188 | "projects/angular2-draggable/src/**/*.ts", 189 | "projects/angular2-draggable/src/**/*.html" 190 | ] 191 | } 192 | } 193 | } 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular2-draggable 2 | 3 | 4 | [![npm](https://img.shields.io/npm/v/angular2-draggable.svg)][npm-badge-url] 5 | [![npm](https://img.shields.io/npm/dm/angular2-draggable.svg)][npm-badge-url] 6 | [![Build Status](https://github.com/xieziyu/angular2-draggable/actions/workflows/ci.yaml/badge.svg)][ci-url] 7 | 8 | + [Online Demo](https://xieziyu.github.io/angular2-draggable) 9 | + [Online Docs](https://xieziyu.github.io/angular2-draggable/api-doc) 10 | 11 | ## Table of contents 12 | - [angular2-draggable](#angular2-draggable) 13 | - [Table of contents](#table-of-contents) 14 | - [Getting Started](#getting-started) 15 | - [Installation](#installation) 16 | - [Draggable](#draggable) 17 | - [Resizable](#resizable) 18 | - [API](#api) 19 | - [Directive:](#directive) 20 | - [CSS:](#css) 21 | - [Events](#events) 22 | - [Demo](#demo) 23 | 24 | # Getting Started 25 | angular2-draggable has angular directives that make the DOM element draggable and resizable. 26 | + `ngDraggable` 27 | + v16.x requires Angular >= 16 28 | + v2.x requires Angular >= 6 29 | + v1.5.0 requires Angular >= 4 && < 6 30 | 31 | + `ngResizable` 32 | + provided since v2.0, requires Angular >= 6 33 | 34 | [CHANGELOG](./CHANGELOG.md) 35 | 36 | # Installation 37 | ``` 38 | npm install angular2-draggable --save 39 | ``` 40 | 41 | # Draggable 42 | Please refer to the [demo](https://xieziyu.github.io/angular2-draggable) page. 43 | 44 | 1. Firstly, import `AngularDraggableModule` in your app module (or any other proper angular module): 45 | ```typescript 46 | import { AngularDraggableModule } from 'angular2-draggable'; 47 | 48 | @NgModule({ 49 | imports: [ 50 | ..., 51 | AngularDraggableModule 52 | ], 53 | ... 54 | }) 55 | export class AppModule { } 56 | ``` 57 | 58 | 2. Then: use `ngDraggable` directive to make the DOM element draggable. 59 | + Simple example: 60 | 61 | + html: 62 | ```html 63 |
Drag me!
64 | ``` 65 | 66 | + Use `[handle]` to move parent element: 67 | 68 | + html: 69 | ```html 70 |
71 |
I'm handle. Drag me!
72 |
You can't drag this block now!
73 |
74 | ``` 75 | 76 | # Resizable 77 | Please refer to the [demo](https://xieziyu.github.io/angular2-draggable/#/resizable/default) page. 78 | 79 | Besides of importing `AngularDraggableModule`, you need to import `resizable.min.css` in your project. If you use `angular-cli`, you can add this in `angular.json`: 80 | 81 | ```diff 82 | "styles": [ 83 | ... 84 | + "node_modules/angular2-draggable/css/resizable.min.css" 85 | ] 86 | ``` 87 | 88 | Then you can use `ngResizable` directive to make the element resizable: 89 | ```html 90 |
I'm now resizable
91 | 92 |
Resizable is disabled now
93 | 94 |
Each side is resizable
95 | ``` 96 | 97 | Well you can use both directives concurrently if you wish: 98 | ```html 99 |
I'm now draggable and resizable
100 | ``` 101 | 102 | # API 103 | 104 | ## Directive: 105 | + `ngDraggable` directive support following input porperties: 106 | 107 | | Input | Type | Default | Description | 108 | | ----- | ---- | ------- | ----------- | 109 | | ngDraggable | boolean | `true` | You can toggle the draggable capability by setting `true` or `false` | 110 | | handle | HTMLElement | null | Use template variable to refer to the handle element. Then only the handle element is draggable | 111 | | zIndex | string | null | Use it to set z-index property when element is not moving | 112 | | zIndexMoving | string | null | Use it to set z-index property when element is moving | 113 | | bounds | HTMLElemnt | null | Use it to set the boundary | 114 | | inBounds | boolean | `false` | Use it make element stay in the bounds | 115 | | outOfBounds | `{ top: boolean; bottom: boolean; right: boolean; left: boolean }` | `false` | Set it to allow element get out of bounds from the direction. Refer to [demo](https://xieziyu.github.io/angular2-draggable/#/usage/boundary) | 116 | | position | `{ x: number, y: number }` | `{ x:0, y:0 }` | Use it to set position offset | 117 | | gridSize | number | 1 | Use it for snapping to grid. Refer to [demo](https://xieziyu.github.io/angular2-draggable/#/advance/snap-grid) | 118 | | preventDefaultEvent | boolean | `false` | Whether to prevent default mouse event | 119 | | scale | number | 1 | Set it when parent element has CSS transform scale | 120 | | lockAxis | `'x' \| 'y'` | null | Restrict dragging to a specific axis by locking another one | 121 | 122 | + `ngResizable` directive support following input porperties: 123 | 124 | | Input | Type | Default | Description | 125 | | ----- | ---- | ------- | ----------- | 126 | | ngResizable | boolean | `true` | You can toggle the resizable capability by setting `true` or `false` | 127 | | rzHandles | string | `"e,s,se"` | Which handles can be used for resizing. Optional types in `"n,e,s,w,se,sw,ne,nw"` or `"all"` | 128 | | rzAspectRatio | boolean \| number | `false` | `boolean`: Whether the element should be constrained to a specific aspect ratio. `number`: Force the element to maintain a specific aspect ratio during resizing (width/height) | 129 | | rzContainment | Selector \| string \| Element | null | Constrains resizing to within the bounds of the specified element or region. It accepts an HTMLElement, `'parent'` or a valid CSS selector string such as '#id' | 130 | | rzGrid | number \| number[] | 1 | Snaps the resizing element to a grid, every x and y pixels. Array values: `[x, y]`| 131 | | rzMinWidth | number | 1 | The minimum width the resizable should be allowed to resize to. | 132 | | rzMaxWidth | number | 1 | The maximum width the resizable should be allowed to resize to. | 133 | | rzMinHeight | number | 1 | The minimum height the resizable should be allowed to resize to. | 134 | | rzMaxHeight | number | 1 | The maximum height the resizable should be allowed to resize to. | 135 | | preventDefaultEvent | boolean | `false` | Whether to prevent default mouse event. | 136 | | rzScale | number | 1 | Set it when parent element has CSS transform scale | 137 | 138 | ## CSS: 139 | + When `ngDraggable` is enabled on some element, `ng-draggable` and `ng-dragging` class is automatically toggled on it. You can use it to customize the pointer style. For example: 140 | 141 | ```css 142 | .ng-draggable { 143 | cursor: grab; 144 | } 145 | 146 | .ng-dragging { 147 | cursor: grabbing; 148 | } 149 | ``` 150 | 151 | + When `ngResizable` is enabled on some element, `ng-resizable` class is automatically assigned to it. And handle elements will be created with `ng-resizable-handle`. You can customize the handle style. 152 | 153 | # Events 154 | + `ngDraggable` directive: 155 | 156 | | Output | $event | Description | 157 | | ------ | ------ | ----------- | 158 | | started | `nativeElement` of host | emitted when start dragging | 159 | | stopped | `nativeElement` of host | emitted when stop dragging | 160 | | edge | { top: boolean, right: boolean, bottom: boolean, left: boolean } | emitted after `[bounds]` is set | 161 | | movingOffset | { x: number, y: number } | emit position offset when moving | 162 | | endOffset | { x: number, y: number } | emit position offset when stop moving | 163 | 164 | Simple example: 165 | ```html 166 |
171 | Drag me! 172 |
173 | ``` 174 | 175 | + `ngResizable` directive: 176 | 177 | | Output | $event | description | 178 | | ------ | ------ | ----------- | 179 | | rzStart | `IResizeEvent` | emitted when start resizing | 180 | | rzResizing | `IResizeEvent` | emitted when resizing | 181 | | rzStop | `IResizeEvent` | emitted when stop resizing | 182 | 183 | ```typescript 184 | export interface IResizeEvent { 185 | host: any; 186 | handle: any; 187 | size: { 188 | width: number; 189 | height: number; 190 | }; 191 | position: { 192 | top: number; 193 | left: number; 194 | }; 195 | direction: { 196 | n: boolean; 197 | s: boolean; 198 | w: boolean; 199 | e: boolean; 200 | }; 201 | } 202 | ``` 203 | 204 | Simple example: 205 | ```html 206 |
210 | Resizable 211 |
212 | ``` 213 | 214 | # Demo 215 | You can clone this repo to your working copy and then launch the demo page in your local machine: 216 | ```bash 217 | npm install 218 | npm run demo 219 | 220 | # or 221 | yarn install 222 | yarn demo 223 | ``` 224 | The demo page server is listening to: http://localhost:4203 225 | 226 | 227 | [npm-badge-url]: https://www.npmjs.com/package/angular2-draggable 228 | [ci-url]: https://github.com/xieziyu/angular2-draggable/actions/workflows/ci.yaml 229 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 16.0.0 (2023-05-20) 2 | 3 | #### New 4 | 5 | + Support Angular 16 6 | 7 | ## 2.3.2 (2019-06-10) 8 | + **ngResizable**: Fix [issue #164](https://github.com/xieziyu/angular2-draggable/issues/164): Resize doesn't work on Windows10 IE11 ([PR #171](https://github.com/xieziyu/angular2-draggable/pull/171) by [shumih](https://github.com/shumih]), [PR #174](https://github.com/xieziyu/angular2-draggable/pull/174) by [LiorSaadon](https://github.com/LiorSaadon])) 9 | 10 | ## 2.3.0 (2019-05-14) 11 | 12 | #### New 13 | + **ngDraggable**: Add CSS class `ng-dragging` when dragging. 14 | + **ngResizable**: Add `direction` property in `IResizeEvent`. 15 | 16 | #### Bugfix 17 | + **ngResizable**: Fix [issue #157](https://github.com/xieziyu/angular2-draggable/issues/159): Problem resizing with containment 18 | 19 | ## 2.2.4 (2019-04-19) 20 | 21 | #### Bugfix 22 | + **ngResizable**: Fix [issue #157](https://github.com/xieziyu/angular2-draggable/issues/157): calling resetSize() method cause exception 23 | 24 | ## 2.2.3 (2019-04-18) 25 | 26 | #### Bugfix 27 | + **ngDraggable**: 28 | + Fix draggable position bouncing when draggable is scaled and position is set ([by agnitos](https://github.com/agnitos)) - [PR #150](https://github.com/xieziyu/angular2-draggable/pull/150) 29 | + Fix translate in draggable.directive ([by Volker505](https://github.com/Volker505)) - [PR #151](https://github.com/xieziyu/angular2-draggable/pull/151) 30 | + Fix issue with dragging window inside iframe for IE ([by fdabrowski](https://github.com/fdabrowski)) - [PR #154](https://github.com/xieziyu/angular2-draggable/pull/154) 31 | + Fix Element move when resizing using the NW and NE handles and aspect ratio is enabled ([by dioseltorre](https://github.com/dioseltorre)) - [PR #156](https://github.com/xieziyu/angular2-draggable/pull/156) 32 | 33 | ## 2.2.2 (2019-03-01) 34 | 35 | #### Bugfix 36 | + **ngDraggable**: Fixed ngDraggable toggle bug. ([by agnitos](https://github.com/agnitos)) - [PR #145](https://github.com/xieziyu/angular2-draggable/pull/145) 37 | 38 | ## 2.2.1 (2018-12-25) 39 | 40 | #### Bugfix 41 | + **ngDraggable**: Fixed flickering of the component on initial drag while scale is applied to the parent. ([by rathodsanjay](https://github.com/rathodsanjay) - [PR #134](https://github.com/xieziyu/angular2-draggable/pull/123)) 42 | 43 | --- 44 | 45 | ## 2.2.0 (2018-12-22) 46 | 47 | #### New 48 | + **ngDraggable**: add [lockAxis] input to restrict dragging to a specific axis by locking another one. 49 | 50 | #### Bugfix 51 | + **ngDraggable**: 52 | + fix [issue #112](https://github.com/xieziyu/angular2-draggable/issues/112): Control change detection with HostListener events. Performance updated. 53 | + fix [issue #128](https://github.com/xieziyu/angular2-draggable/issues/128): Multiple phone draggables at the same time. 54 | + **ngResizable**: 55 | + fix [issue #132](https://github.com/xieziyu/angular2-draggable/issues/132): Aspect ratio feature exits Y-Axis boundary on resize. 56 | + Performance updated. 57 | 58 | --- 59 | 60 | ## 2.1.9 (2018-11-29) 61 | 62 | #### Bugfix 63 | + **ngDraggable**: [#31](https://github.com/xieziyu/angular2-draggable/issues/31) Problems when scale transform applied to parent. ([by rathodsanjay](https://github.com/rathodsanjay) - [PR #123](https://github.com/xieziyu/angular2-draggable/pull/123)) 64 | 65 | --- 66 | 67 | ## 2.1.8 (2018-11-11) 68 | 69 | #### New 70 | + **ngResizable**: add [preventDefaultEvent] flag to ngResizable mousedown ([by mecp](https://github.com/mecp) - [PR #119](https://github.com/xieziyu/angular2-draggable/pull/119)) 71 | 72 | --- 73 | 74 | ## 2.1.7 (2018-10-31) 75 | 76 | #### Bugfix 77 | + **ngResizable**: [#116](https://github.com/xieziyu/angular2-draggable/issues/116) ngResizable Locks Height When rzHandles Includes Only e, w. (Thanks to [Yamazaki93](https://github.com/Yamazaki93)) 78 | 79 | --- 80 | 81 | ## 2.1.6 (2018-10-26) 82 | 83 | #### Bugfix 84 | + **ngResizable**: rzResizing IE event issue. [#115](https://github.com/xieziyu/angular2-draggable/issues/115) 85 | 86 | --- 87 | 88 | ## 2.1.5 (2018-10-15) 89 | 90 | #### Bugfix 91 | + **ngDraggable**: EndOffset event not working properly with SnapToGrid. [#114](https://github.com/xieziyu/angular2-draggable/issues/114) 92 | 93 | --- 94 | 95 | ## 2.1.4 (2018-09-17) 96 | 97 | #### Bugfix 98 | + Fix a build issue 99 | + **ngResizable**: Resize bounds on a draggable element inside a containment is off. [#100](https://github.com/xieziyu/angular2-draggable/issues/100) 100 | 101 | --- 102 | 103 | ## 2.1.2 (2018-08-20) 104 | 105 | #### Bugfix 106 | + **ngDraggable**: Item is produced with div partially out of bounds. [#97](https://github.com/xieziyu/angular2-draggable/issues/97) 107 | 108 | --- 109 | 110 | ## 2.1.1 (2018-08-14) 111 | 112 | #### New 113 | + **ngResizable**: Provide `[rzGrid]`. Snaps the resizing element to a grid. 114 | + **ngResizable**: Provide `[rzMinWidth]`, `[rzMaxWidth]`, `[rzMinHeight]`, `[rzMaxHeight]`. The minimum/maximum width/height the resizable should be allowed to resize to. 115 | 116 | #### Bugfix 117 | + **ngResizable**: resizing from w, nw or n with a min/max size moves the window if it goes below/above the min/max size. [#94](https://github.com/xieziyu/angular2-draggable/issues/94) 118 | 119 | --- 120 | 121 | ## 2.0.1 (2018-08-08) 122 | 123 | #### Bugfix 124 | + click events are blocked. [#87](https://github.com/xieziyu/angular2-draggable/issues/87), [#84](https://github.com/xieziyu/angular2-draggable/issues/84) 125 | 126 | --- 127 | 128 | ## 2.0.0 (2018-08-03) 129 | 130 | #### Bugfix 131 | + Fix [issue #84](https://github.com/xieziyu/angular2-draggable/issues/84): iFrames, and context unrelated elements block all events, and are unusable 132 | 133 | --- 134 | 135 | ## 2.0.0-beta.2 (2018-07-02) 136 | 137 | #### New 138 | + **ngResizable**: Provide `[rzAspectRatio]`, whether the element should be constrained to a specific aspect ratio. 139 | + **ngResizable**: Provide `[rzContainment]`, constrains resizing to within the bounds of the specified element or region. 140 | 141 | --- 142 | 143 | ## 2.0.0-beta.1 (2018-06-26) 144 | 145 | #### New 146 | + **ngResizable**: Provide `(rzStart)`, `(rzResizing)`, `(rzStop)` event emitters 147 | + **ngResizable**: Provide `resetSize()`, `getStatus()` methods 148 | 149 | --- 150 | 151 | ## 2.0.0-beta.0 (2018-06-25) 152 | 153 | #### New 154 | + `ngResizable` directive which you can use to make the element resizable. 155 | + Provide `[rzHandles]` option for which handles can be used for resizing. 156 | 157 | ## 1.4.2 (2018-05-23) 158 | 159 | #### Changes 160 | + Expose boundsCheck() method. 161 | 162 | --- 163 | 164 | ## 1.4.1 (2018-05-11) 165 | 166 | #### Bugfix 167 | + Handle Drag is not working in Firefox [#68](https://github.com/xieziyu/angular2-draggable/issues/68). 168 | 169 | --- 170 | 171 | ## 1.4.0 (2018-05-04) 172 | 173 | #### New 174 | + Provide `[gridSize]` option for snapping to grid. Refer to [demo](https://xieziyu.github.io/angular2-draggable/#/advance/snap-grid). (PR [#64](https://github.com/xieziyu/angular2-draggable/pull/64) by [PAHADIx](https://github.com/PAHADIx)) 175 | 176 | #### Changes 177 | + Code optimized. (PR [#60](https://github.com/xieziyu/angular2-draggable/pull/60) by [korn3l](https://github.com/korn3l)) 178 | 179 | --- 180 | 181 | ## 1.3.2 (2018-04-10) 182 | 183 | #### New 184 | + Provide `[outOfBounds]` option. Set it to allow element get out of bounds from the direction. (PR [#57](https://github.com/xieziyu/angular2-draggable/issues/58) by [waldo2188](https://github.com/waldo2188)) 185 | 186 | --- 187 | 188 | ## 1.3.1 (2018-03-15) 189 | 190 | #### New 191 | + Provide `(movingOffset)` event emitter: emit position offset when moving 192 | + Provide `(endOffset)` event emitter: emit position offset when stop moving 193 | 194 | --- 195 | 196 | ## 1.3.0 (2018-03-09) 197 | 198 | #### New 199 | + Provide `[position]` option: to set initial position offset. 200 | 201 | --- 202 | 203 | ## 1.2.1 (2018-02-08) 204 | 205 | #### Bugfix 206 | + `[preventDefaultEvent]` should not prevent events of elements outside the handle. 207 | 208 | --- 209 | 210 | ## 1.2.0 (2018-02-07) 211 | 212 | #### New 213 | + Provide `resetPosition()` method to reset position. 214 | 215 | #### Breaking Changes 216 | + Use `Renderer2` of angular-core. So we don't support angular version < 4.0. 217 | 218 | #### Bugfix 219 | + `[trackPosition]` was not working as expected. 220 | 221 | #### Changes 222 | + The directive now `exportAs: 'ngDraggable'`. 223 | + `[preventDefaultEvent]` set default to false. 224 | 225 | --- 226 | 227 | ## 1.1.0 (2018-02-01) 228 | 229 | #### New 230 | + Provide `[trackPosition]` option: whether to track the element's movement. (PR by [Blackbaud-MikitaYankouski](https://github.com/Blackbaud-MikitaYankouski)) 231 | + Provide `[scale]` option: to fix scaling issue [#31](https://github.com/xieziyu/angular2-draggable/issues/31) 232 | + Provide `[preventDefaultEvent]` option: whether to prevent default mouse or touch event. (default: true) 233 | 234 | --- 235 | 236 | ## 1.1.0-beta.0 (2017-12-20) 237 | 238 | #### New 239 | + Provide `[zIndex]` and `[zIndexMoving]` to control z-index property. 240 | + Provide `[bounds]`, `(edge)` and `[inBounds]` to do boundary check and limit element staying in the bounds. 241 | 242 | --- 243 | 244 | 245 | ## [1.0.7](https://github.com/xieziyu/angular2-draggable/compare/v1.0.6...v1.0.7) (2017-09-19) 246 | 247 | ### Bugfix 248 | + Fix an issue when dragging with touch. 249 | 250 | --- 251 | 252 | 253 | ## [1.0.6](https://github.com/xieziyu/angular2-draggable/compare/v1.0.5...v1.0.6) (2017-08-26) 254 | 255 | ### Bugfix 256 | + Fix an issue: clicking before dragging leading to unexpected offset ([PR #12](https://github.com/xieziyu/angular2-draggable/pull/12) by [bmartinson13](https://github.com/bmartinson13)) 257 | 258 | --- 259 | 260 | 261 | ## [1.0.5](https://github.com/xieziyu/angular2-draggable/compare/v1.0.4...v1.0.5) (2017-07-24) 262 | 263 | ### New 264 | + Fix cross-browser compatibility issues. 265 | 266 | --- 267 | 268 | 269 | ## [1.0.4](https://github.com/xieziyu/angular2-draggable/compare/v1.0.3...v1.0.4) (2017-07-05) 270 | 271 | ### New 272 | + Publish `UMD` bundle 273 | 274 | --- 275 | 276 | 277 | ## [1.0.3](https://github.com/xieziyu/angular2-draggable/compare/v1.0.2...v1.0.3) (2017-06-13) 278 | 279 | ### New 280 | + Support `started` and `stopped` dragging event. 281 | 282 | --- 283 | 284 | 285 | ## [1.0.2](https://github.com/xieziyu/angular2-draggable/compare/v1.0.1...v1.0.2) (2017-05-05) 286 | 287 | ### BugFix 288 | + It now saves and restores the `position` and `z-index` properties. 289 | + It now calculates the correct `left` and `top` properties from CSS value. 290 | 291 | --- 292 | 293 | 294 | ## [1.0.1](https://github.com/xieziyu/angular2-draggable/compare/v1.0.0...v1.0.1) (2017-05-05) 295 | 296 | ### BugFix 297 | + Giving all draggable elements the position relative (PR: [#1](https://github.com/xieziyu/angular2-draggable/pull/1), thanks to tylerlindell) 298 | 299 | ### Changes 300 | + Bring moving element to the top by setting z-index (PR: [#1](https://github.com/xieziyu/angular2-draggable/pull/1), thanks to tylerlindell) 301 | -------------------------------------------------------------------------------- /projects/angular2-draggable/src/lib/angular-draggable.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, 4 | Renderer2, 5 | Input, 6 | Output, 7 | OnInit, 8 | HostListener, 9 | EventEmitter, 10 | OnChanges, 11 | SimpleChanges, 12 | OnDestroy, 13 | AfterViewInit, 14 | } from '@angular/core'; 15 | 16 | import { Subscription, fromEvent } from 'rxjs'; 17 | import { IPosition, Position } from './models/position'; 18 | import { HelperBlock } from './widgets/helper-block'; 19 | 20 | @Directive({ 21 | selector: '[ngDraggable]', 22 | exportAs: 'ngDraggable', 23 | }) 24 | export class AngularDraggableDirective implements OnInit, OnDestroy, OnChanges, AfterViewInit { 25 | private allowDrag = true; 26 | private moving = false; 27 | private orignal: Position = null; 28 | private oldTrans = new Position(0, 0); 29 | private tempTrans = new Position(0, 0); 30 | private currTrans = new Position(0, 0); 31 | private oldZIndex = ''; 32 | private _zIndex = ''; 33 | private needTransform = false; 34 | 35 | private draggingSub: Subscription = null; 36 | 37 | /** 38 | * Bugfix: iFrames, and context unrelated elements block all events, and are unusable 39 | * https://github.com/xieziyu/angular2-draggable/issues/84 40 | */ 41 | private _helperBlock: HelperBlock = null; 42 | 43 | @Output() started = new EventEmitter(); 44 | @Output() stopped = new EventEmitter(); 45 | @Output() edge = new EventEmitter(); 46 | 47 | /** Make the handle HTMLElement draggable */ 48 | @Input() handle: HTMLElement; 49 | 50 | /** Set the bounds HTMLElement */ 51 | @Input() bounds: HTMLElement; 52 | 53 | /** List of allowed out of bounds edges **/ 54 | @Input() outOfBounds = { 55 | top: false, 56 | right: false, 57 | bottom: false, 58 | left: false, 59 | }; 60 | 61 | /** Round the position to nearest grid */ 62 | @Input() gridSize = 1; 63 | 64 | /** Set z-index when dragging */ 65 | @Input() zIndexMoving: string; 66 | 67 | /** Set z-index when not dragging */ 68 | @Input() set zIndex(setting: string) { 69 | this.renderer.setStyle(this.el.nativeElement, 'z-index', setting); 70 | this._zIndex = setting; 71 | } 72 | /** Whether to limit the element stay in the bounds */ 73 | @Input() inBounds = false; 74 | 75 | /** Whether the element should use it's previous drag position on a new drag event. */ 76 | @Input() trackPosition = true; 77 | 78 | /** Input css scale transform of element so translations are correct */ 79 | @Input() scale = 1; 80 | 81 | /** Whether to prevent default event */ 82 | @Input() preventDefaultEvent = false; 83 | 84 | /** Set initial position by offsets */ 85 | @Input() position: IPosition = { x: 0, y: 0 }; 86 | 87 | /** Lock axis: 'x' or 'y' */ 88 | @Input() lockAxis: string = null; 89 | 90 | /** Emit position offsets when moving */ 91 | @Output() movingOffset = new EventEmitter(); 92 | 93 | /** Emit position offsets when put back */ 94 | @Output() endOffset = new EventEmitter(); 95 | 96 | @Input() 97 | set ngDraggable(setting: any) { 98 | if (setting !== undefined && setting !== null && setting !== '') { 99 | this.allowDrag = !!setting; 100 | 101 | let element = this.getDragEl(); 102 | 103 | if (this.allowDrag) { 104 | this.renderer.addClass(element, 'ng-draggable'); 105 | } else { 106 | this.putBack(); 107 | this.renderer.removeClass(element, 'ng-draggable'); 108 | } 109 | } 110 | } 111 | 112 | constructor(private el: ElementRef, private renderer: Renderer2) { 113 | this._helperBlock = new HelperBlock(el.nativeElement, renderer); 114 | } 115 | 116 | ngOnInit() { 117 | if (this.allowDrag) { 118 | let element = this.getDragEl(); 119 | this.renderer.addClass(element, 'ng-draggable'); 120 | } 121 | this.resetPosition(); 122 | } 123 | 124 | ngOnDestroy() { 125 | this.bounds = null; 126 | this.handle = null; 127 | this.orignal = null; 128 | this.oldTrans = null; 129 | this.tempTrans = null; 130 | this.currTrans = null; 131 | this._helperBlock.dispose(); 132 | this._helperBlock = null; 133 | 134 | if (this.draggingSub) { 135 | this.draggingSub.unsubscribe(); 136 | } 137 | } 138 | 139 | ngOnChanges(changes: SimpleChanges) { 140 | if (changes['position'] && !changes['position'].isFirstChange()) { 141 | let p = changes['position'].currentValue; 142 | 143 | if (!this.moving) { 144 | if (Position.isIPosition(p)) { 145 | this.oldTrans.set(p); 146 | } else { 147 | this.oldTrans.reset(); 148 | } 149 | 150 | this.transform(); 151 | } else { 152 | this.needTransform = true; 153 | } 154 | } 155 | } 156 | 157 | ngAfterViewInit() { 158 | if (this.inBounds) { 159 | this.boundsCheck(); 160 | this.oldTrans.add(this.tempTrans); 161 | this.tempTrans.reset(); 162 | } 163 | } 164 | 165 | private getDragEl() { 166 | return this.handle ? this.handle : this.el.nativeElement; 167 | } 168 | 169 | resetPosition() { 170 | if (Position.isIPosition(this.position)) { 171 | this.oldTrans.set(this.position); 172 | } else { 173 | this.oldTrans.reset(); 174 | } 175 | this.tempTrans.reset(); 176 | this.transform(); 177 | } 178 | 179 | private moveTo(p: Position) { 180 | if (this.orignal) { 181 | p.subtract(this.orignal); 182 | this.tempTrans.set(p); 183 | this.tempTrans.divide(this.scale); 184 | this.transform(); 185 | 186 | if (this.bounds) { 187 | let edgeEv = this.boundsCheck(); 188 | if (edgeEv) { 189 | this.edge.emit(edgeEv); 190 | } 191 | } 192 | 193 | this.movingOffset.emit(this.currTrans.value); 194 | } 195 | } 196 | 197 | private transform() { 198 | let translateX = this.tempTrans.x + this.oldTrans.x; 199 | let translateY = this.tempTrans.y + this.oldTrans.y; 200 | 201 | if (this.lockAxis === 'x') { 202 | translateX = this.oldTrans.x; 203 | this.tempTrans.x = 0; 204 | } else if (this.lockAxis === 'y') { 205 | translateY = this.oldTrans.y; 206 | this.tempTrans.y = 0; 207 | } 208 | 209 | // Snap to grid: by grid size 210 | if (this.gridSize > 1) { 211 | translateX = Math.round(translateX / this.gridSize) * this.gridSize; 212 | translateY = Math.round(translateY / this.gridSize) * this.gridSize; 213 | } 214 | 215 | let value = `translate(${Math.round(translateX)}px, ${Math.round(translateY)}px)`; 216 | 217 | this.renderer.setStyle(this.el.nativeElement, 'transform', value); 218 | this.renderer.setStyle(this.el.nativeElement, '-webkit-transform', value); 219 | this.renderer.setStyle(this.el.nativeElement, '-ms-transform', value); 220 | this.renderer.setStyle(this.el.nativeElement, '-moz-transform', value); 221 | this.renderer.setStyle(this.el.nativeElement, '-o-transform', value); 222 | 223 | // save current position 224 | this.currTrans.x = translateX; 225 | this.currTrans.y = translateY; 226 | } 227 | 228 | private pickUp() { 229 | // get old z-index: 230 | this.oldZIndex = this.el.nativeElement.style.zIndex ? this.el.nativeElement.style.zIndex : ''; 231 | 232 | if (window) { 233 | this.oldZIndex = window 234 | .getComputedStyle(this.el.nativeElement, null) 235 | .getPropertyValue('z-index'); 236 | } 237 | 238 | if (this.zIndexMoving) { 239 | this.renderer.setStyle(this.el.nativeElement, 'z-index', this.zIndexMoving); 240 | } 241 | 242 | if (!this.moving) { 243 | this.started.emit(this.el.nativeElement); 244 | this.moving = true; 245 | 246 | const element = this.getDragEl(); 247 | this.renderer.addClass(element, 'ng-dragging'); 248 | 249 | /** 250 | * Fix performance issue: 251 | * https://github.com/xieziyu/angular2-draggable/issues/112 252 | */ 253 | this.subscribeEvents(); 254 | } 255 | } 256 | 257 | private subscribeEvents() { 258 | this.draggingSub = fromEvent(document, 'mousemove', { passive: false }).subscribe(event => 259 | this.onMouseMove(event as MouseEvent) 260 | ); 261 | this.draggingSub.add( 262 | fromEvent(document, 'touchmove', { passive: false }).subscribe(event => 263 | this.onMouseMove(event as TouchEvent) 264 | ) 265 | ); 266 | this.draggingSub.add( 267 | fromEvent(document, 'mouseup', { passive: false }).subscribe(() => this.putBack()) 268 | ); 269 | // checking if browser is IE or Edge - https://github.com/xieziyu/angular2-draggable/issues/153 270 | let isIEOrEdge = /msie\s|trident\//i.test(window.navigator.userAgent); 271 | if (!isIEOrEdge) { 272 | this.draggingSub.add( 273 | fromEvent(document, 'mouseleave', { passive: false }).subscribe(() => this.putBack()) 274 | ); 275 | } 276 | this.draggingSub.add( 277 | fromEvent(document, 'touchend', { passive: false }).subscribe(() => this.putBack()) 278 | ); 279 | this.draggingSub.add( 280 | fromEvent(document, 'touchcancel', { passive: false }).subscribe(() => this.putBack()) 281 | ); 282 | } 283 | 284 | private unsubscribeEvents() { 285 | this.draggingSub.unsubscribe(); 286 | this.draggingSub = null; 287 | } 288 | 289 | boundsCheck() { 290 | if (this.bounds) { 291 | let boundary = this.bounds.getBoundingClientRect(); 292 | let elem = this.el.nativeElement.getBoundingClientRect(); 293 | let result = { 294 | top: this.outOfBounds.top ? true : boundary.top < elem.top, 295 | right: this.outOfBounds.right ? true : boundary.right > elem.right, 296 | bottom: this.outOfBounds.bottom ? true : boundary.bottom > elem.bottom, 297 | left: this.outOfBounds.left ? true : boundary.left < elem.left, 298 | }; 299 | 300 | if (this.inBounds) { 301 | if (!result.top) { 302 | this.tempTrans.y -= (elem.top - boundary.top) / this.scale; 303 | } 304 | 305 | if (!result.bottom) { 306 | this.tempTrans.y -= (elem.bottom - boundary.bottom) / this.scale; 307 | } 308 | 309 | if (!result.right) { 310 | this.tempTrans.x -= (elem.right - boundary.right) / this.scale; 311 | } 312 | 313 | if (!result.left) { 314 | this.tempTrans.x -= (elem.left - boundary.left) / this.scale; 315 | } 316 | 317 | this.transform(); 318 | } 319 | 320 | return result; 321 | } 322 | return null; 323 | } 324 | 325 | /** Get current offset */ 326 | getCurrentOffset() { 327 | return this.currTrans.value; 328 | } 329 | 330 | private putBack() { 331 | if (this._zIndex) { 332 | this.renderer.setStyle(this.el.nativeElement, 'z-index', this._zIndex); 333 | } else if (this.zIndexMoving) { 334 | if (this.oldZIndex) { 335 | this.renderer.setStyle(this.el.nativeElement, 'z-index', this.oldZIndex); 336 | } else { 337 | this.el.nativeElement.style.removeProperty('z-index'); 338 | } 339 | } 340 | 341 | if (this.moving) { 342 | this.stopped.emit(this.el.nativeElement); 343 | 344 | // Remove the helper div: 345 | this._helperBlock.remove(); 346 | 347 | if (this.needTransform) { 348 | if (Position.isIPosition(this.position)) { 349 | this.oldTrans.set(this.position); 350 | } else { 351 | this.oldTrans.reset(); 352 | } 353 | 354 | this.transform(); 355 | this.needTransform = false; 356 | } 357 | 358 | if (this.bounds) { 359 | let edgeEv = this.boundsCheck(); 360 | if (edgeEv) { 361 | this.edge.emit(edgeEv); 362 | } 363 | } 364 | 365 | this.moving = false; 366 | this.endOffset.emit(this.currTrans.value); 367 | 368 | if (this.trackPosition) { 369 | this.oldTrans.add(this.tempTrans); 370 | } 371 | 372 | this.tempTrans.reset(); 373 | 374 | if (!this.trackPosition) { 375 | this.transform(); 376 | } 377 | 378 | const element = this.getDragEl(); 379 | this.renderer.removeClass(element, 'ng-dragging'); 380 | 381 | /** 382 | * Fix performance issue: 383 | * https://github.com/xieziyu/angular2-draggable/issues/112 384 | */ 385 | this.unsubscribeEvents(); 386 | } 387 | } 388 | 389 | checkHandleTarget(target: EventTarget, element: Element) { 390 | // Checks if the target is the element clicked, then checks each child element of element as well 391 | // Ignores button clicks 392 | 393 | // Ignore elements of type button 394 | if (element.tagName === 'BUTTON') { 395 | return false; 396 | } 397 | 398 | // If the target was found, return true (handle was found) 399 | if (element === target) { 400 | return true; 401 | } 402 | 403 | // Recursively iterate this elements children 404 | for (let child in element.children) { 405 | if (element.children.hasOwnProperty(child)) { 406 | if (this.checkHandleTarget(target, element.children[child])) { 407 | return true; 408 | } 409 | } 410 | } 411 | 412 | // Handle was not found in this lineage 413 | // Note: return false is ignore unless it is the parent element 414 | return false; 415 | } 416 | 417 | @HostListener('mousedown', ['$event']) 418 | @HostListener('touchstart', ['$event']) 419 | onMouseDown(event: MouseEvent | TouchEvent) { 420 | // 1. skip right click; 421 | if (event instanceof MouseEvent && event.button === 2) { 422 | return; 423 | } 424 | // 2. if handle is set, the element can only be moved by handle 425 | let target = event.target || event.srcElement; 426 | if (this.handle !== undefined && !this.checkHandleTarget(target, this.handle)) { 427 | return; 428 | } 429 | 430 | // 3. if allow drag is set to false, ignore the mousedown 431 | if (this.allowDrag === false) { 432 | return; 433 | } 434 | 435 | if (this.preventDefaultEvent) { 436 | event.stopPropagation(); 437 | event.preventDefault(); 438 | } 439 | 440 | this.orignal = Position.fromEvent(event, this.getDragEl()); 441 | this.pickUp(); 442 | } 443 | 444 | onMouseMove(event: MouseEvent | TouchEvent) { 445 | if (this.moving && this.allowDrag) { 446 | if (this.preventDefaultEvent) { 447 | event.stopPropagation(); 448 | event.preventDefault(); 449 | } 450 | 451 | // Add a transparent helper div: 452 | this._helperBlock.add(); 453 | this.moveTo(Position.fromEvent(event, this.getDragEl())); 454 | } 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /projects/angular2-draggable/src/lib/angular-resizable.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, 4 | Renderer2, 5 | Input, 6 | Output, 7 | OnInit, 8 | EventEmitter, 9 | OnChanges, 10 | SimpleChanges, 11 | OnDestroy, 12 | AfterViewInit, 13 | } from '@angular/core'; 14 | 15 | import { Subscription, fromEvent } from 'rxjs'; 16 | import { HelperBlock } from './widgets/helper-block'; 17 | import { ResizeHandle } from './widgets/resize-handle'; 18 | import { ResizeHandleType } from './models/resize-handle-type'; 19 | import { Position, IPosition } from './models/position'; 20 | import { Size } from './models/size'; 21 | import { IResizeEvent } from './models/resize-event'; 22 | 23 | @Directive({ 24 | selector: '[ngResizable]', 25 | exportAs: 'ngResizable', 26 | }) 27 | export class AngularResizableDirective implements OnInit, OnChanges, OnDestroy, AfterViewInit { 28 | private _resizable = true; 29 | private _handles: { [key: string]: ResizeHandle } = {}; 30 | private _handleType: string[] = []; 31 | private _handleResizing: ResizeHandle = null; 32 | private _direction: { n: boolean; s: boolean; w: boolean; e: boolean } = null; 33 | private _directionChanged: { n: boolean; s: boolean; w: boolean; e: boolean } = null; 34 | private _aspectRatio = 0; 35 | private _containment: HTMLElement = null; 36 | private _origMousePos: Position = null; 37 | 38 | /** Original Size and Position */ 39 | private _origSize: Size = null; 40 | private _origPos: Position = null; 41 | 42 | /** Current Size and Position */ 43 | private _currSize: Size = null; 44 | private _currPos: Position = null; 45 | 46 | /** Initial Size and Position */ 47 | private _initSize: Size = null; 48 | private _initPos: Position = null; 49 | 50 | /** Snap to gird */ 51 | private _gridSize: IPosition = null; 52 | 53 | private _bounding: any = null; 54 | 55 | /** 56 | * Bugfix: iFrames, and context unrelated elements block all events, and are unusable 57 | * https://github.com/xieziyu/angular2-draggable/issues/84 58 | */ 59 | private _helperBlock: HelperBlock = null; 60 | 61 | private draggingSub: Subscription = null; 62 | private _adjusted = false; 63 | 64 | /** Disables the resizable if set to false. */ 65 | @Input() set ngResizable(v: any) { 66 | if (v !== undefined && v !== null && v !== '') { 67 | this._resizable = !!v; 68 | this.updateResizable(); 69 | } 70 | } 71 | 72 | /** 73 | * Which handles can be used for resizing. 74 | * @example 75 | * [rzHandles] = "'n,e,s,w,se,ne,sw,nw'" 76 | * equals to: [rzHandles] = "'all'" 77 | * 78 | * */ 79 | @Input() rzHandles: ResizeHandleType = 'e,s,se'; 80 | 81 | /** 82 | * Using exist handles for resizing instead of generate them. 83 | * @example 84 | * [rzHandleDoms] = { 85 | * e: handelE, 86 | * s: handelS, 87 | * se: handelSE 88 | * }; 89 | * */ 90 | @Input() rzHandleDoms: { 91 | se?: ElementRef; 92 | sw?: ElementRef; 93 | ne?: ElementRef; 94 | nw?: ElementRef; 95 | n?: ElementRef; 96 | e?: ElementRef; 97 | s?: ElementRef; 98 | w?: ElementRef; 99 | } = {}; 100 | 101 | /** 102 | * Whether the element should be constrained to a specific aspect ratio. 103 | * Multiple types supported: 104 | * boolean: When set to true, the element will maintain its original aspect ratio. 105 | * number: Force the element to maintain a specific aspect ratio during resizing. 106 | */ 107 | @Input() rzAspectRatio: boolean | number = false; 108 | 109 | /** 110 | * Constrains resizing to within the bounds of the specified element or region. 111 | * Multiple types supported: 112 | * Selector: The resizable element will be contained to the bounding box of the first element found by the selector. 113 | * If no element is found, no containment will be set. 114 | * Element: The resizable element will be contained to the bounding box of this element. 115 | * String: Possible values: "parent". 116 | */ 117 | @Input() rzContainment: string | HTMLElement = null; 118 | 119 | /** 120 | * Snaps the resizing element to a grid, every x and y pixels. 121 | * A number for both width and height or an array values like [ x, y ] 122 | */ 123 | @Input() rzGrid: number | number[] = null; 124 | 125 | /** The minimum width the resizable should be allowed to resize to. */ 126 | @Input() rzMinWidth: number = null; 127 | 128 | /** The minimum height the resizable should be allowed to resize to. */ 129 | @Input() rzMinHeight: number = null; 130 | 131 | /** The maximum width the resizable should be allowed to resize to. */ 132 | @Input() rzMaxWidth: number = null; 133 | 134 | /** The maximum height the resizable should be allowed to resize to. */ 135 | @Input() rzMaxHeight: number = null; 136 | 137 | /** Input css scale transform of element so translations are correct */ 138 | @Input() rzScale = 1; 139 | 140 | /** Whether to prevent default event */ 141 | @Input() preventDefaultEvent = true; 142 | 143 | /** emitted when start resizing */ 144 | @Output() rzStart = new EventEmitter(); 145 | 146 | /** emitted when start resizing */ 147 | @Output() rzResizing = new EventEmitter(); 148 | 149 | /** emitted when stop resizing */ 150 | @Output() rzStop = new EventEmitter(); 151 | 152 | constructor(private el: ElementRef, private renderer: Renderer2) { 153 | this._helperBlock = new HelperBlock(el.nativeElement, renderer); 154 | } 155 | 156 | ngOnChanges(changes: SimpleChanges) { 157 | if (changes['rzHandles'] && !changes['rzHandles'].isFirstChange()) { 158 | this.updateResizable(); 159 | } 160 | 161 | if (changes['rzAspectRatio'] && !changes['rzAspectRatio'].isFirstChange()) { 162 | this.updateAspectRatio(); 163 | } 164 | 165 | if (changes['rzContainment'] && !changes['rzContainment'].isFirstChange()) { 166 | this.updateContainment(); 167 | } 168 | } 169 | 170 | ngOnInit() { 171 | this.updateResizable(); 172 | } 173 | 174 | ngOnDestroy() { 175 | this.removeHandles(); 176 | this._containment = null; 177 | this._helperBlock.dispose(); 178 | this._helperBlock = null; 179 | } 180 | 181 | ngAfterViewInit() { 182 | const elm = this.el.nativeElement; 183 | this._initSize = Size.getCurrent(elm); 184 | this._initPos = Position.getCurrent(elm); 185 | this._currSize = Size.copy(this._initSize); 186 | this._currPos = Position.copy(this._initPos); 187 | this.updateAspectRatio(); 188 | this.updateContainment(); 189 | } 190 | 191 | /** A method to reset size */ 192 | public resetSize() { 193 | this._currSize = Size.copy(this._initSize); 194 | this._currPos = Position.copy(this._initPos); 195 | this.doResize(); 196 | } 197 | 198 | /** A method to get current status */ 199 | public getStatus() { 200 | if (!this._currPos || !this._currSize) { 201 | return null; 202 | } 203 | 204 | return { 205 | size: { 206 | width: this._currSize.width, 207 | height: this._currSize.height, 208 | }, 209 | position: { 210 | top: this._currPos.y, 211 | left: this._currPos.x, 212 | }, 213 | }; 214 | } 215 | 216 | private updateResizable() { 217 | const element = this.el.nativeElement; 218 | 219 | // clear handles: 220 | this.renderer.removeClass(element, 'ng-resizable'); 221 | this.removeHandles(); 222 | 223 | // create new ones: 224 | if (this._resizable) { 225 | this.renderer.addClass(element, 'ng-resizable'); 226 | this.createHandles(); 227 | } 228 | } 229 | 230 | /** Use it to update aspect */ 231 | private updateAspectRatio() { 232 | if (typeof this.rzAspectRatio === 'boolean') { 233 | if (this.rzAspectRatio && this._currSize.height) { 234 | this._aspectRatio = this._currSize.width / this._currSize.height; 235 | } else { 236 | this._aspectRatio = 0; 237 | } 238 | } else { 239 | let r = Number(this.rzAspectRatio); 240 | this._aspectRatio = isNaN(r) ? 0 : r; 241 | } 242 | } 243 | 244 | /** Use it to update containment */ 245 | private updateContainment() { 246 | if (!this.rzContainment) { 247 | this._containment = null; 248 | return; 249 | } 250 | 251 | if (typeof this.rzContainment === 'string') { 252 | if (this.rzContainment === 'parent') { 253 | this._containment = this.el.nativeElement.parentElement; 254 | } else { 255 | this._containment = document.querySelector(this.rzContainment); 256 | } 257 | } else { 258 | this._containment = this.rzContainment; 259 | } 260 | } 261 | 262 | /** Use it to create handle divs */ 263 | private createHandles() { 264 | if (!this.rzHandles) { 265 | return; 266 | } 267 | 268 | let tmpHandleTypes: string[]; 269 | if (typeof this.rzHandles === 'string') { 270 | if (this.rzHandles === 'all') { 271 | tmpHandleTypes = ['n', 'e', 's', 'w', 'ne', 'se', 'nw', 'sw']; 272 | } else { 273 | tmpHandleTypes = this.rzHandles.replace(/ /g, '').toLowerCase().split(','); 274 | } 275 | 276 | for (let type of tmpHandleTypes) { 277 | // default handle theme: ng-resizable-$type. 278 | let handle = this.createHandleByType(type, `ng-resizable-${type}`); 279 | if (handle) { 280 | this._handleType.push(type); 281 | this._handles[type] = handle; 282 | } 283 | } 284 | } else { 285 | tmpHandleTypes = Object.keys(this.rzHandles); 286 | for (let type of tmpHandleTypes) { 287 | // custom handle theme. 288 | let handle = this.createHandleByType(type, this.rzHandles[type]); 289 | if (handle) { 290 | this._handleType.push(type); 291 | this._handles[type] = handle; 292 | } 293 | } 294 | } 295 | } 296 | 297 | /** Use it to create a handle */ 298 | private createHandleByType(type: string, css: string): ResizeHandle { 299 | const _el = this.el.nativeElement; 300 | const _h = this.rzHandleDoms[type] ? this.rzHandleDoms[type].nativeElement : null; 301 | 302 | if (!type.match(/^(se|sw|ne|nw|n|e|s|w)$/)) { 303 | console.error('Invalid handle type:', type); 304 | return null; 305 | } 306 | 307 | return new ResizeHandle(_el, this.renderer, type, css, this.onMouseDown.bind(this), _h); 308 | } 309 | 310 | private removeHandles() { 311 | for (let type of this._handleType) { 312 | this._handles[type].dispose(); 313 | } 314 | 315 | this._handleType = []; 316 | this._handles = {}; 317 | } 318 | 319 | onMouseDown(event: MouseEvent | TouchEvent, handle: ResizeHandle) { 320 | // skip right click; 321 | if (event instanceof MouseEvent && event.button === 2) { 322 | return; 323 | } 324 | 325 | if (this.preventDefaultEvent) { 326 | // prevent default events 327 | event.stopPropagation(); 328 | event.preventDefault(); 329 | } 330 | 331 | if (!this._handleResizing) { 332 | this._origMousePos = Position.fromEvent(event); 333 | this.startResize(handle); 334 | 335 | this.subscribeEvents(); 336 | } 337 | } 338 | 339 | private subscribeEvents() { 340 | this.draggingSub = fromEvent(document, 'mousemove', { passive: false }).subscribe(event => 341 | this.onMouseMove(event as MouseEvent) 342 | ); 343 | this.draggingSub.add( 344 | fromEvent(document, 'touchmove', { passive: false }).subscribe(event => 345 | this.onMouseMove(event as TouchEvent) 346 | ) 347 | ); 348 | this.draggingSub.add( 349 | fromEvent(document, 'mouseup', { passive: false }).subscribe(() => this.onMouseLeave()) 350 | ); 351 | // fix for issue #164 352 | let isIEOrEdge = /msie\s|trident\//i.test(window.navigator.userAgent); 353 | if (!isIEOrEdge) { 354 | this.draggingSub.add( 355 | fromEvent(document, 'mouseleave', { passive: false }).subscribe(() => this.onMouseLeave()) 356 | ); 357 | } 358 | this.draggingSub.add( 359 | fromEvent(document, 'touchend', { passive: false }).subscribe(() => this.onMouseLeave()) 360 | ); 361 | this.draggingSub.add( 362 | fromEvent(document, 'touchcancel', { passive: false }).subscribe(() => this.onMouseLeave()) 363 | ); 364 | } 365 | 366 | private unsubscribeEvents() { 367 | this.draggingSub.unsubscribe(); 368 | this.draggingSub = null; 369 | } 370 | 371 | onMouseLeave() { 372 | if (this._handleResizing) { 373 | this.stopResize(); 374 | this._origMousePos = null; 375 | this.unsubscribeEvents(); 376 | } 377 | } 378 | 379 | onMouseMove(event: MouseEvent | TouchEvent) { 380 | if ( 381 | this._handleResizing && 382 | this._resizable && 383 | this._origMousePos && 384 | this._origPos && 385 | this._origSize 386 | ) { 387 | this.resizeTo(Position.fromEvent(event)); 388 | this.onResizing(); 389 | } 390 | } 391 | 392 | private startResize(handle: ResizeHandle) { 393 | const elm = this.el.nativeElement; 394 | this._origSize = Size.getCurrent(elm); 395 | this._origPos = Position.getCurrent(elm); // x: left, y: top 396 | this._currSize = Size.copy(this._origSize); 397 | this._currPos = Position.copy(this._origPos); 398 | if (this._containment) { 399 | this.getBounding(); 400 | } 401 | this.getGridSize(); 402 | 403 | // Add a transparent helper div: 404 | this._helperBlock.add(); 405 | this._handleResizing = handle; 406 | this.updateDirection(); 407 | this.rzStart.emit(this.getResizingEvent()); 408 | } 409 | 410 | private stopResize() { 411 | // Remove the helper div: 412 | this._helperBlock.remove(); 413 | this.rzStop.emit(this.getResizingEvent()); 414 | this._handleResizing = null; 415 | this._direction = null; 416 | this._origSize = null; 417 | this._origPos = null; 418 | if (this._containment) { 419 | this.resetBounding(); 420 | } 421 | } 422 | 423 | private onResizing() { 424 | this.rzResizing.emit(this.getResizingEvent()); 425 | } 426 | 427 | private getResizingEvent(): IResizeEvent { 428 | return { 429 | host: this.el.nativeElement, 430 | handle: this._handleResizing ? this._handleResizing.el : null, 431 | size: { 432 | width: this._currSize.width, 433 | height: this._currSize.height, 434 | }, 435 | position: { 436 | top: this._currPos.y, 437 | left: this._currPos.x, 438 | }, 439 | direction: { ...this._directionChanged }, 440 | }; 441 | } 442 | 443 | private updateDirection() { 444 | this._direction = { 445 | n: !!this._handleResizing.type.match(/n/), 446 | s: !!this._handleResizing.type.match(/s/), 447 | w: !!this._handleResizing.type.match(/w/), 448 | e: !!this._handleResizing.type.match(/e/), 449 | }; 450 | 451 | this._directionChanged = { ...this._direction }; 452 | 453 | // if aspect ration should be preserved: 454 | if (this.rzAspectRatio) { 455 | // if north then west (unless ne) 456 | if (this._directionChanged.n && !this._directionChanged.e) { 457 | this._directionChanged.w = true; 458 | } 459 | 460 | // if south then east (unless sw) 461 | if (this._directionChanged.s && !this._directionChanged.w) { 462 | this._directionChanged.e = true; 463 | } 464 | 465 | // if east then south (unless ne) 466 | if (this._directionChanged.e && !this._directionChanged.n) { 467 | this._directionChanged.s = true; 468 | } 469 | 470 | // if west then south (unless nw) 471 | if (this._directionChanged.w && !this._directionChanged.n) { 472 | this._directionChanged.s = true; 473 | } 474 | } 475 | } 476 | 477 | private resizeTo(p: Position) { 478 | p.subtract(this._origMousePos).divide(this.rzScale); 479 | 480 | const tmpX = Math.round(p.x / this._gridSize.x) * this._gridSize.x; 481 | const tmpY = Math.round(p.y / this._gridSize.y) * this._gridSize.y; 482 | 483 | if (this._direction.n) { 484 | // n, ne, nw 485 | this._currPos.y = this._origPos.y + tmpY; 486 | this._currSize.height = this._origSize.height - tmpY; 487 | } else if (this._direction.s) { 488 | // s, se, sw 489 | this._currSize.height = this._origSize.height + tmpY; 490 | } 491 | 492 | if (this._direction.e) { 493 | // e, ne, se 494 | this._currSize.width = this._origSize.width + tmpX; 495 | } else if (this._direction.w) { 496 | // w, nw, sw 497 | this._currSize.width = this._origSize.width - tmpX; 498 | this._currPos.x = this._origPos.x + tmpX; 499 | } 500 | 501 | this.checkBounds(); 502 | this.checkSize(); 503 | this.adjustByRatio(); 504 | this.doResize(); 505 | } 506 | 507 | private doResize() { 508 | const container = this.el.nativeElement; 509 | if (!this._direction || this._direction.n || this._direction.s || this._aspectRatio) { 510 | this.renderer.setStyle(container, 'height', this._currSize.height + 'px'); 511 | } 512 | if (!this._direction || this._direction.w || this._direction.e || this._aspectRatio) { 513 | this.renderer.setStyle(container, 'width', this._currSize.width + 'px'); 514 | } 515 | this.renderer.setStyle(container, 'left', this._currPos.x + 'px'); 516 | this.renderer.setStyle(container, 'top', this._currPos.y + 'px'); 517 | } 518 | 519 | private adjustByRatio() { 520 | if (this._aspectRatio && !this._adjusted) { 521 | if (this._direction.e || this._direction.w) { 522 | const newHeight = Math.floor(this._currSize.width / this._aspectRatio); 523 | 524 | if (this._direction.n) { 525 | this._currPos.y += this._currSize.height - newHeight; 526 | } 527 | 528 | this._currSize.height = newHeight; 529 | } else { 530 | const newWidth = Math.floor(this._aspectRatio * this._currSize.height); 531 | 532 | if (this._direction.n) { 533 | this._currPos.x += this._currSize.width - newWidth; 534 | } 535 | 536 | this._currSize.width = newWidth; 537 | } 538 | } 539 | } 540 | 541 | private checkBounds() { 542 | if (this._containment) { 543 | const maxWidth = 544 | this._bounding.width - 545 | this._bounding.pr - 546 | this._bounding.deltaL - 547 | this._bounding.translateX - 548 | this._currPos.x; 549 | const maxHeight = 550 | this._bounding.height - 551 | this._bounding.pb - 552 | this._bounding.deltaT - 553 | this._bounding.translateY - 554 | this._currPos.y; 555 | 556 | if (this._direction.n && this._currPos.y + this._bounding.translateY < 0) { 557 | this._currPos.y = -this._bounding.translateY; 558 | this._currSize.height = this._origSize.height + this._origPos.y + this._bounding.translateY; 559 | } 560 | 561 | if (this._direction.w && this._currPos.x + this._bounding.translateX < 0) { 562 | this._currPos.x = -this._bounding.translateX; 563 | this._currSize.width = this._origSize.width + this._origPos.x + this._bounding.translateX; 564 | } 565 | 566 | if (this._currSize.width > maxWidth) { 567 | this._currSize.width = maxWidth; 568 | } 569 | 570 | if (this._currSize.height > maxHeight) { 571 | this._currSize.height = maxHeight; 572 | } 573 | 574 | /** 575 | * Fix Issue: Additional check for aspect ratio 576 | * https://github.com/xieziyu/angular2-draggable/issues/132 577 | */ 578 | if (this._aspectRatio) { 579 | this._adjusted = false; 580 | 581 | if ( 582 | (this._direction.w || this._direction.e) && 583 | this._currSize.width / this._aspectRatio >= maxHeight 584 | ) { 585 | const newWidth = Math.floor(maxHeight * this._aspectRatio); 586 | 587 | if (this._direction.w) { 588 | this._currPos.x += this._currSize.width - newWidth; 589 | } 590 | 591 | this._currSize.width = newWidth; 592 | this._currSize.height = maxHeight; 593 | this._adjusted = true; 594 | } 595 | 596 | if ( 597 | (this._direction.n || this._direction.s) && 598 | this._currSize.height * this._aspectRatio >= maxWidth 599 | ) { 600 | const newHeight = Math.floor(maxWidth / this._aspectRatio); 601 | 602 | if (this._direction.n) { 603 | this._currPos.y += this._currSize.height - newHeight; 604 | } 605 | 606 | this._currSize.width = maxWidth; 607 | this._currSize.height = newHeight; 608 | this._adjusted = true; 609 | } 610 | } 611 | } 612 | } 613 | 614 | private checkSize() { 615 | const minHeight = !this.rzMinHeight ? 1 : this.rzMinHeight; 616 | const minWidth = !this.rzMinWidth ? 1 : this.rzMinWidth; 617 | 618 | if (this._currSize.height < minHeight) { 619 | this._currSize.height = minHeight; 620 | 621 | if (this._direction.n) { 622 | this._currPos.y = this._origPos.y + (this._origSize.height - minHeight); 623 | } 624 | } 625 | 626 | if (this._currSize.width < minWidth) { 627 | this._currSize.width = minWidth; 628 | 629 | if (this._direction.w) { 630 | this._currPos.x = this._origPos.x + (this._origSize.width - minWidth); 631 | } 632 | } 633 | 634 | if (this.rzMaxHeight && this._currSize.height > this.rzMaxHeight) { 635 | this._currSize.height = this.rzMaxHeight; 636 | 637 | if (this._direction.n) { 638 | this._currPos.y = this._origPos.y + (this._origSize.height - this.rzMaxHeight); 639 | } 640 | } 641 | 642 | if (this.rzMaxWidth && this._currSize.width > this.rzMaxWidth) { 643 | this._currSize.width = this.rzMaxWidth; 644 | 645 | if (this._direction.w) { 646 | this._currPos.x = this._origPos.x + (this._origSize.width - this.rzMaxWidth); 647 | } 648 | } 649 | } 650 | 651 | private getBounding() { 652 | const el = this._containment; 653 | const computed = window.getComputedStyle(el); 654 | if (computed) { 655 | let p = computed.getPropertyValue('position'); 656 | 657 | const nativeEl = window.getComputedStyle(this.el.nativeElement); 658 | let transforms = nativeEl 659 | .getPropertyValue('transform') 660 | .replace(/[^-\d,]/g, '') 661 | .split(','); 662 | 663 | this._bounding = {}; 664 | this._bounding.width = el.clientWidth; 665 | this._bounding.height = el.clientHeight; 666 | this._bounding.pr = parseInt(computed.getPropertyValue('padding-right'), 10); 667 | this._bounding.pb = parseInt(computed.getPropertyValue('padding-bottom'), 10); 668 | this._bounding.deltaL = this.el.nativeElement.offsetLeft - this._currPos.x; 669 | this._bounding.deltaT = this.el.nativeElement.offsetTop - this._currPos.y; 670 | 671 | if (transforms.length >= 6) { 672 | this._bounding.translateX = parseInt(transforms[4], 10); 673 | this._bounding.translateY = parseInt(transforms[5], 10); 674 | } else { 675 | this._bounding.translateX = 0; 676 | this._bounding.translateY = 0; 677 | } 678 | 679 | this._bounding.position = computed.getPropertyValue('position'); 680 | 681 | if (p === 'static') { 682 | this.renderer.setStyle(el, 'position', 'relative'); 683 | } 684 | } 685 | } 686 | 687 | private resetBounding() { 688 | if (this._bounding && this._bounding.position === 'static') { 689 | this.renderer.setStyle(this._containment, 'position', 'relative'); 690 | } 691 | this._bounding = null; 692 | } 693 | 694 | private getGridSize() { 695 | // set default value: 696 | this._gridSize = { x: 1, y: 1 }; 697 | 698 | if (this.rzGrid) { 699 | if (typeof this.rzGrid === 'number') { 700 | this._gridSize = { x: this.rzGrid, y: this.rzGrid }; 701 | } else if (Array.isArray(this.rzGrid)) { 702 | this._gridSize = { x: this.rzGrid[0], y: this.rzGrid[1] }; 703 | } 704 | } 705 | } 706 | } 707 | --------------------------------------------------------------------------------