mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(schematics): create drag-drop schematic (#13368)
* Creates a CDK schematic that can be used to generate a component that uses the CDK Drag & Drop module. The code is based on the `sorting` example of the drag-drop (just a bit trimmed to keep the code simple)
This commit is contained in:
committed by
vivian-hu
parent
3254992a8e
commit
72ccd8b831
@@ -9,13 +9,13 @@ exports_files(["#bazel_workaround.txt"])
|
||||
|
||||
filegroup(
|
||||
name = "schematics_assets",
|
||||
srcs = glob(["**/*.json"]) + ["README.md"],
|
||||
srcs = glob(["**/files/**/*", "**/*.json"]) + ["README.md"],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "schematics",
|
||||
module_name = "@angular/cdk/schematics",
|
||||
srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]),
|
||||
srcs = glob(["**/*.ts"], exclude=["**/files/**/*.ts", "**/*.spec.ts"]),
|
||||
tsconfig = ":tsconfig.json",
|
||||
deps = [
|
||||
"@npm//:@schematics/angular",
|
||||
@@ -51,7 +51,7 @@ jasmine_node_test(
|
||||
|
||||
ts_library(
|
||||
name = "schematics_test_sources",
|
||||
srcs = glob(["**/*.spec.ts"]),
|
||||
srcs = glob(["**/*.spec.ts"], exclude = ["**/files/**/*.spec.ts"]),
|
||||
deps = [
|
||||
":schematics",
|
||||
"@npm//:@schematics/angular",
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
"factory": "./ng-add/index",
|
||||
"schema": "./ng-add/schema.json",
|
||||
"aliases": ["install"]
|
||||
},
|
||||
"drag-drop": {
|
||||
"description": "Generates a component using the Drag and Drop module",
|
||||
"factory": "./ng-generate/drag-drop/index",
|
||||
"schema": "./ng-generate/drag-drop/schema.json",
|
||||
"aliases": ["dragdrop", "drag-and-drop"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
.container {
|
||||
width: 400px;
|
||||
max-width: 100%;
|
||||
margin: 0 25px 25px 0;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.list {
|
||||
border: solid 1px #ccc;
|
||||
min-height: 60px;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
padding: 20px 10px;
|
||||
border-bottom: solid 1px #ccc;
|
||||
box-sizing: border-box;
|
||||
cursor: move;
|
||||
background: white;
|
||||
color: black;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.list-item:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Highlight the list item that is being dragged. */
|
||||
.cdk-drag-preview {
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
|
||||
0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||
0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
/* Animate items as they're being sorted. */
|
||||
.cdk-drop-dragging .cdk-drag {
|
||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Animate an item that has been dropped. */
|
||||
.cdk-drag-animating {
|
||||
transition: transform 300ms cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.cdk-drag-placeholder {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<div class="container">
|
||||
<h2>To do</h2>
|
||||
|
||||
<cdk-drop #todoList [data]="todo" [connectedTo]="doneList" class="list" (dropped)="drop($event)">
|
||||
<div class="list-item" *ngFor="let item of todo" cdkDrag>{{item}}</div>
|
||||
</cdk-drop>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h2>Done</h2>
|
||||
|
||||
<cdk-drop #doneList [data]="done" [connectedTo]="todoList" class="list" (dropped)="drop($event)">
|
||||
<div class="list-item" *ngFor="let item of done" cdkDrag>{{item}}</div>
|
||||
</cdk-drop>
|
||||
</div>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component';
|
||||
|
||||
describe('<%= classify(name) %>Component', () => {
|
||||
let component: <%= classify(name) %>Component;
|
||||
let fixture: ComponentFixture<<%= classify(name) %>Component>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ <%= classify(name) %>Component ],
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
DragDropModule,
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(<%= classify(name) %>Component);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should compile', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { Component<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core';
|
||||
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
|
||||
|
||||
@Component({
|
||||
selector: '<%= selector %>',<% if(inlineTemplate) { %>
|
||||
template: `
|
||||
<%= indentTextContent(resolvedFiles.template, 4) %>
|
||||
`,<% } else { %>
|
||||
templateUrl: './<%= dasherize(name) %>.component.html',<% } if(inlineStyle) { %>
|
||||
styles: [`
|
||||
<%= indentTextContent(resolvedFiles.stylesheet, 4) %>
|
||||
`],<% } else { %>
|
||||
styleUrls: ['./<%= dasherize(name) %>.component.<%= styleext %>'],<% } %><% if(!!viewEncapsulation) { %>
|
||||
encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>,
|
||||
changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
|
||||
})
|
||||
export class <%= classify(name) %>Component {
|
||||
todo = [
|
||||
'Get to work',
|
||||
'Pick up groceries',
|
||||
'Go home',
|
||||
'Fall asleep'
|
||||
];
|
||||
|
||||
done = [
|
||||
'Get up',
|
||||
'Brush teeth',
|
||||
'Take a shower',
|
||||
'Check e-mail',
|
||||
'Walk dog'
|
||||
];
|
||||
|
||||
drop(event: CdkDragDrop<string[]>) {
|
||||
if (event.previousContainer === event.container) {
|
||||
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
|
||||
} else {
|
||||
transferArrayItem(event.previousContainer.data,
|
||||
event.container.data,
|
||||
event.previousIndex,
|
||||
event.currentIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
|
||||
import {createTestApp} from '../../testing';
|
||||
import {getFileContent} from '@schematics/angular/utility/test';
|
||||
import {Schema} from './schema';
|
||||
|
||||
describe('CDK drag-drop schematic', () => {
|
||||
let runner: SchematicTestRunner;
|
||||
|
||||
const baseOptions: Schema = {
|
||||
name: 'foo',
|
||||
// TODO(devversion): rename project to something that is not tied to Material. This involves
|
||||
// updating the other tests as well because `createTestApp` is responsible for creating
|
||||
// the project.
|
||||
project: 'material',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
runner = new SchematicTestRunner('schematics', require.resolve('../../collection.json'));
|
||||
});
|
||||
|
||||
it('should create drag-drop files and add them to module', () => {
|
||||
const tree = runner.runSchematic('drag-drop', baseOptions, createTestApp(runner));
|
||||
const moduleContent = getFileContent(tree, '/projects/material/src/app/app.module.ts');
|
||||
const files = tree.files;
|
||||
|
||||
expect(files).toContain('/projects/material/src/app/foo/foo.component.css');
|
||||
expect(files).toContain('/projects/material/src/app/foo/foo.component.html');
|
||||
expect(files).toContain('/projects/material/src/app/foo/foo.component.spec.ts');
|
||||
expect(files).toContain('/projects/material/src/app/foo/foo.component.ts');
|
||||
|
||||
expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo\/foo.component'/);
|
||||
expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooComponent\r?\n/m);
|
||||
});
|
||||
|
||||
it('should add drag-drop module', () => {
|
||||
const tree = runner.runSchematic('drag-drop', baseOptions, createTestApp(runner));
|
||||
const moduleContent = getFileContent(tree, '/projects/material/src/app/app.module.ts');
|
||||
|
||||
expect(moduleContent).toContain('DragDropModule');
|
||||
});
|
||||
|
||||
describe('styleext option', () => {
|
||||
it('should respect the option value', () => {
|
||||
const tree = runner.runSchematic(
|
||||
'drag-drop', {styleext: 'scss', ...baseOptions}, createTestApp(runner));
|
||||
|
||||
expect(tree.files).toContain('/projects/material/src/app/foo/foo.component.scss');
|
||||
});
|
||||
|
||||
it('should fall back to the @schematics/angular:component option value', () => {
|
||||
const tree = runner.runSchematic(
|
||||
'drag-drop', baseOptions, createTestApp(runner, {style: 'less'}));
|
||||
|
||||
expect(tree.files).toContain('/projects/material/src/app/foo/foo.component.less');
|
||||
});
|
||||
});
|
||||
|
||||
describe('inlineStyle option', () => {
|
||||
it('should respect the option value', () => {
|
||||
const tree = runner.runSchematic(
|
||||
'drag-drop', {inlineStyle: true, ...baseOptions}, createTestApp(runner));
|
||||
|
||||
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.css');
|
||||
});
|
||||
|
||||
it('should fall back to the @schematics/angular:component option value', () => {
|
||||
const tree = runner.runSchematic(
|
||||
'drag-drop', baseOptions, createTestApp(runner, {inlineStyle: true}));
|
||||
|
||||
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.css');
|
||||
});
|
||||
});
|
||||
|
||||
describe('inlineTemplate option', () => {
|
||||
it('should respect the option value', () => {
|
||||
const tree = runner.runSchematic(
|
||||
'drag-drop', {inlineTemplate: true, ...baseOptions}, createTestApp(runner));
|
||||
|
||||
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.html');
|
||||
});
|
||||
|
||||
it('should fall back to the @schematics/angular:component option value', () => {
|
||||
const tree = runner.runSchematic(
|
||||
'drag-drop', baseOptions, createTestApp(runner, {inlineTemplate: true}));
|
||||
|
||||
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.html');
|
||||
});
|
||||
});
|
||||
|
||||
describe('spec option', () => {
|
||||
it('should respect the option value', () => {
|
||||
const tree = runner.runSchematic(
|
||||
'drag-drop', {spec: false, ...baseOptions}, createTestApp(runner));
|
||||
|
||||
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.spec.ts');
|
||||
});
|
||||
|
||||
it('should fall back to the @schematics/angular:component option value', () => {
|
||||
const tree = runner.runSchematic(
|
||||
'drag-drop', baseOptions, createTestApp(runner, {skipTests: true}));
|
||||
|
||||
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.spec.ts');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {chain, noop, Rule, Tree} from '@angular-devkit/schematics';
|
||||
import {addModuleImportToModule, buildComponent, findModuleFromOptions} from '../../utils';
|
||||
import {Schema} from './schema';
|
||||
|
||||
/** Scaffolds a new Angular component that uses the Drag and Drop module. */
|
||||
export default function(options: Schema): Rule {
|
||||
return chain([
|
||||
buildComponent({...options}, {
|
||||
template: './__path__/__name@dasherize@if-flat__/__name@dasherize__.component.html',
|
||||
stylesheet: './__path__/__name@dasherize@if-flat__/__name@dasherize__.component.__styleext__',
|
||||
}),
|
||||
options.skipImport ? noop() : addDragDropModulesToModule(options)
|
||||
]);
|
||||
}
|
||||
|
||||
/** Adds the required modules to the main module of the CLI project. */
|
||||
function addDragDropModulesToModule(options: Schema) {
|
||||
return (host: Tree) => {
|
||||
const modulePath = findModuleFromOptions(host, options)!;
|
||||
|
||||
addModuleImportToModule(host, modulePath, 'DragDropModule', '@angular/cdk/drag-drop');
|
||||
return host;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"id": "CdkSchematicsDragDrop",
|
||||
"title": "Angular CDK Drag and Drop schematic",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"format": "path",
|
||||
"description": "The path to create the component.",
|
||||
"visible": false
|
||||
},
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project.",
|
||||
"$default": {
|
||||
"$source": "projectName"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the component.",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
}
|
||||
},
|
||||
"inlineStyle": {
|
||||
"description": "Specifies if the style will be in the ts file.",
|
||||
"type": "boolean",
|
||||
"alias": "s"
|
||||
},
|
||||
"inlineTemplate": {
|
||||
"description": "Specifies if the template will be in the ts file.",
|
||||
"type": "boolean",
|
||||
"alias": "t"
|
||||
},
|
||||
"viewEncapsulation": {
|
||||
"description": "Specifies the view encapsulation strategy.",
|
||||
"enum": ["Emulated", "None"],
|
||||
"type": "string",
|
||||
"alias": "v"
|
||||
},
|
||||
"changeDetection": {
|
||||
"description": "Specifies the change detection strategy.",
|
||||
"enum": ["Default", "OnPush"],
|
||||
"type": "string",
|
||||
"default": "Default",
|
||||
"alias": "c"
|
||||
},
|
||||
"prefix": {
|
||||
"type": "string",
|
||||
"format": "html-selector",
|
||||
"description": "The prefix to apply to generated selectors.",
|
||||
"alias": "p"
|
||||
},
|
||||
"styleext": {
|
||||
"description": "The file extension to be used for style files.",
|
||||
"type": "string"
|
||||
},
|
||||
"spec": {
|
||||
"type": "boolean",
|
||||
"description": "Specifies if a spec file is generated."
|
||||
},
|
||||
"flat": {
|
||||
"type": "boolean",
|
||||
"description": "Flag to indicate if a dir is created.",
|
||||
"default": false
|
||||
},
|
||||
"skipImport": {
|
||||
"type": "boolean",
|
||||
"description": "Flag to skip the module import.",
|
||||
"default": false
|
||||
},
|
||||
"selector": {
|
||||
"type": "string",
|
||||
"format": "html-selector",
|
||||
"description": "The selector to use for the component."
|
||||
},
|
||||
"module": {
|
||||
"type": "string",
|
||||
"description": "Allows specification of the declaring module.",
|
||||
"alias": "m"
|
||||
},
|
||||
"export": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Specifies if declaring module exports the component."
|
||||
},
|
||||
"entryComponent": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Specifies if the component is an entry component of declaring module."
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Schema as ComponentSchema} from '@schematics/angular/component/schema';
|
||||
|
||||
export interface Schema extends ComponentSchema {}
|
||||
@@ -18,6 +18,7 @@
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"**/files/**/*",
|
||||
"**/*.spec.ts"
|
||||
],
|
||||
"bazelOptions": {
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"exclude": [
|
||||
// Exclude schematic template files and test cases that can't be linted.
|
||||
"src/lib/schematics/**/files/**/*",
|
||||
"src/cdk/schematics/**/files/**/*",
|
||||
"src/lib/schematics/ng-update/test-cases/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user