1 | import { Component, Input, Output, EventEmitter, forwardRef, TemplateRef } from '@angular/core';
|
2 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
3 | import { DraggableItemService } from './draggable-item.service';
|
4 | export class SortableComponent {
|
5 | constructor(transfer) {
|
6 |
|
7 | this.wrapperClass = '';
|
8 |
|
9 | this.wrapperStyle = {};
|
10 |
|
11 | this.itemClass = '';
|
12 |
|
13 | this.itemStyle = {};
|
14 |
|
15 | this.itemActiveClass = '';
|
16 |
|
17 | this.itemActiveStyle = {};
|
18 |
|
19 | this.placeholderClass = '';
|
20 |
|
21 | this.placeholderStyle = {};
|
22 |
|
23 | this.placeholderItem = '';
|
24 | |
25 |
|
26 |
|
27 | this.onChange = new EventEmitter();
|
28 | this.showPlaceholder = false;
|
29 | this.activeItem = -1;
|
30 |
|
31 | this.onTouched = Function.prototype;
|
32 |
|
33 | this.onChanged = Function.prototype;
|
34 | this._items = [];
|
35 | this.transfer = transfer;
|
36 | this.currentZoneIndex = SortableComponent.globalZoneIndex++;
|
37 | this.transfer
|
38 | .onCaptureItem()
|
39 | .subscribe((item) => this.onDrop(item));
|
40 | }
|
41 | get items() {
|
42 | return this._items;
|
43 | }
|
44 | set items(value) {
|
45 | this._items = value;
|
46 | const out = this.items.map((x) => x.initData);
|
47 | this.onChanged(out);
|
48 | this.onChange.emit(out);
|
49 | }
|
50 | onItemDragstart(event, item, i) {
|
51 | this.initDragstartEvent(event);
|
52 | this.onTouched();
|
53 | this.transfer.dragStart({
|
54 | event,
|
55 | item,
|
56 | i,
|
57 | initialIndex: i,
|
58 | lastZoneIndex: this.currentZoneIndex,
|
59 | overZoneIndex: this.currentZoneIndex
|
60 | });
|
61 | }
|
62 | onItemDragover(event, i) {
|
63 | if (!this.transfer.getItem()) {
|
64 | return;
|
65 | }
|
66 | event.preventDefault();
|
67 | const dragItem = this.transfer.captureItem(this.currentZoneIndex, this.items.length);
|
68 |
|
69 | let newArray = [];
|
70 | if (!dragItem) {
|
71 | return;
|
72 | }
|
73 | if (!this.items.length) {
|
74 | newArray = [dragItem.item];
|
75 | }
|
76 | else if (dragItem.i > i) {
|
77 | newArray = [
|
78 | ...this.items.slice(0, i),
|
79 | dragItem.item,
|
80 | ...this.items.slice(i, dragItem.i),
|
81 | ...this.items.slice(dragItem.i + 1)
|
82 | ];
|
83 | }
|
84 | else {
|
85 |
|
86 | newArray = [
|
87 | ...this.items.slice(0, dragItem.i),
|
88 | ...this.items.slice(dragItem.i + 1, i + 1),
|
89 | dragItem.item,
|
90 | ...this.items.slice(i + 1)
|
91 | ];
|
92 | }
|
93 | this.items = newArray;
|
94 | dragItem.i = i;
|
95 | this.activeItem = i;
|
96 | this.updatePlaceholderState();
|
97 | }
|
98 | cancelEvent(event) {
|
99 | if (!this.transfer.getItem() || !event) {
|
100 | return;
|
101 | }
|
102 | event.preventDefault();
|
103 | }
|
104 | onDrop(item) {
|
105 | if (item &&
|
106 | item.overZoneIndex !== this.currentZoneIndex &&
|
107 | item.lastZoneIndex === this.currentZoneIndex) {
|
108 | this.items = this.items.filter((x, i) => i !== item.i);
|
109 | this.updatePlaceholderState();
|
110 | }
|
111 | this.resetActiveItem();
|
112 | }
|
113 | resetActiveItem(event) {
|
114 | this.cancelEvent(event);
|
115 | this.activeItem = -1;
|
116 | }
|
117 | registerOnChange(callback) {
|
118 | this.onChanged = callback;
|
119 | }
|
120 | registerOnTouched(callback) {
|
121 | this.onTouched = callback;
|
122 | }
|
123 |
|
124 | writeValue(value) {
|
125 | if (value) {
|
126 |
|
127 | this.items = value.map((x, i) => ({
|
128 | id: i,
|
129 | initData: x,
|
130 | value: this.fieldName ? x[this.fieldName] : x
|
131 | }));
|
132 | }
|
133 | else {
|
134 | this.items = [];
|
135 | }
|
136 | this.updatePlaceholderState();
|
137 | }
|
138 | updatePlaceholderState() {
|
139 | this.showPlaceholder = !this._items.length;
|
140 | }
|
141 | getItemStyle(isActive) {
|
142 | return isActive
|
143 | ? Object.assign({}, this.itemStyle, this.itemActiveStyle)
|
144 | : this.itemStyle;
|
145 | }
|
146 | initDragstartEvent(event) {
|
147 | var _a;
|
148 |
|
149 |
|
150 |
|
151 | (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.setData('Text', 'placeholder');
|
152 | }
|
153 | }
|
154 | SortableComponent.globalZoneIndex = 0;
|
155 | SortableComponent.decorators = [
|
156 | { type: Component, args: [{
|
157 | selector: 'bs-sortable',
|
158 | exportAs: 'bs-sortable',
|
159 | template: `
|
160 | <div
|
161 | [ngClass]="wrapperClass"
|
162 | [ngStyle]="wrapperStyle"
|
163 | (dragover)="cancelEvent($event)"
|
164 | (dragenter)="cancelEvent($event)"
|
165 | (drop)="resetActiveItem($event)"
|
166 | (mouseleave)="resetActiveItem($event)">
|
167 | <div
|
168 | *ngIf="showPlaceholder"
|
169 | [ngClass]="placeholderClass"
|
170 | [ngStyle]="placeholderStyle"
|
171 | (dragover)="onItemDragover($event, 0)"
|
172 | (dragenter)="cancelEvent($event)"
|
173 | >{{placeholderItem}}</div>
|
174 | <div
|
175 | *ngFor="let item of items; let i=index;"
|
176 | [ngClass]="[ itemClass, i === activeItem ? itemActiveClass : '' ]"
|
177 | [ngStyle]="getItemStyle(i === activeItem)"
|
178 | draggable="true"
|
179 | (dragstart)="onItemDragstart($event, item, i)"
|
180 | (dragend)="resetActiveItem($event)"
|
181 | (dragover)="onItemDragover($event, i)"
|
182 | (dragenter)="cancelEvent($event)"
|
183 | aria-dropeffect="move"
|
184 | [attr.aria-grabbed]="i === activeItem"
|
185 | ><ng-template [ngTemplateOutlet]="itemTemplate || defItemTemplate"
|
186 | [ngTemplateOutletContext]="{item:item, index: i}"></ng-template></div>
|
187 | </div>
|
188 |
|
189 | <ng-template #defItemTemplate let-item="item">{{item.value}}</ng-template>
|
190 | `,
|
191 | providers: [
|
192 | {
|
193 | provide: NG_VALUE_ACCESSOR,
|
194 | useExisting: forwardRef(() => SortableComponent),
|
195 | multi: true
|
196 | }
|
197 | ]
|
198 | },] }
|
199 | ];
|
200 | SortableComponent.ctorParameters = () => [
|
201 | { type: DraggableItemService }
|
202 | ];
|
203 | SortableComponent.propDecorators = {
|
204 | fieldName: [{ type: Input }],
|
205 | wrapperClass: [{ type: Input }],
|
206 | wrapperStyle: [{ type: Input }],
|
207 | itemClass: [{ type: Input }],
|
208 | itemStyle: [{ type: Input }],
|
209 | itemActiveClass: [{ type: Input }],
|
210 | itemActiveStyle: [{ type: Input }],
|
211 | placeholderClass: [{ type: Input }],
|
212 | placeholderStyle: [{ type: Input }],
|
213 | placeholderItem: [{ type: Input }],
|
214 | itemTemplate: [{ type: Input }],
|
215 | onChange: [{ type: Output }]
|
216 | };
|
217 |
|
\ | No newline at end of file |