@nyaruka/temba-components 0.33.3 → 0.33.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build.yml +4 -5
- package/CHANGELOG.md +21 -5
- package/demo/index.html +1 -1
- package/dist/{7b0d8aca.js → a374554b.js} +304 -78
- package/dist/index.js +304 -78
- package/dist/sw.js +1 -1
- package/dist/sw.js.map +1 -1
- package/dist/templates/components-body.html +1 -1
- package/dist/templates/components-head.html +1 -1
- package/out-tsc/src/FormElement.js +9 -6
- package/out-tsc/src/FormElement.js.map +1 -1
- package/out-tsc/src/contacts/ContactDetails.js +1 -1
- package/out-tsc/src/contacts/ContactDetails.js.map +1 -1
- package/out-tsc/src/contacts/ContactFields.js +6 -5
- package/out-tsc/src/contacts/ContactFields.js.map +1 -1
- package/out-tsc/src/contacts/ContactHistory.js +4 -4
- package/out-tsc/src/contacts/ContactHistory.js.map +1 -1
- package/out-tsc/src/fields/FieldManager.js +323 -0
- package/out-tsc/src/fields/FieldManager.js.map +1 -0
- package/out-tsc/src/interfaces.js +3 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +193 -0
- package/out-tsc/src/list/SortableList.js.map +1 -0
- package/out-tsc/src/store/Store.js +28 -17
- package/out-tsc/src/store/Store.js.map +1 -1
- package/out-tsc/src/store/StoreElement.js +1 -0
- package/out-tsc/src/store/StoreElement.js.map +1 -1
- package/out-tsc/src/vectoricon/index.js +3 -0
- package/out-tsc/src/vectoricon/index.js.map +1 -1
- package/out-tsc/temba-modules.js +4 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/temba-field-manager.test.js +47 -0
- package/out-tsc/test/temba-field-manager.test.js.map +1 -0
- package/out-tsc/test/temba-sortable-list.test.js +48 -0
- package/out-tsc/test/temba-sortable-list.test.js.map +1 -0
- package/out-tsc/test/temba-store.test.js +1 -1
- package/out-tsc/test/temba-store.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/list/fields-dragging.png +0 -0
- package/screenshots/truth/list/fields-filtered.png +0 -0
- package/screenshots/truth/list/fields-hovered.png +0 -0
- package/screenshots/truth/list/fields.png +0 -0
- package/screenshots/truth/list/sortable-dragging.png +0 -0
- package/screenshots/truth/list/sortable-dropped.png +0 -0
- package/screenshots/truth/list/sortable.png +0 -0
- package/src/FormElement.ts +9 -6
- package/src/contacts/ContactDetails.ts +1 -1
- package/src/contacts/ContactFields.ts +6 -5
- package/src/contacts/ContactHistory.ts +4 -4
- package/src/fields/FieldManager.ts +353 -0
- package/src/interfaces.ts +5 -1
- package/src/list/SortableList.ts +224 -0
- package/src/store/Store.ts +34 -21
- package/src/store/StoreElement.ts +1 -0
- package/src/vectoricon/index.ts +3 -0
- package/static/svg/index.pdf +137 -0
- package/temba-modules.ts +4 -0
- package/test/temba-field-manager.test.ts +60 -0
- package/test/temba-sortable-list.test.ts +58 -0
- package/test/temba-store.test.ts +1 -1
- package/test-assets/store/fields.json +50 -5
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { css, html } from 'lit';
|
|
3
|
+
import { property } from 'lit/decorators';
|
|
4
|
+
import { CustomEventType } from '../interfaces';
|
|
5
|
+
import { StoreElement } from '../store/StoreElement';
|
|
6
|
+
import { postJSON } from '../utils';
|
|
7
|
+
const TYPE_NAMES = {
|
|
8
|
+
text: 'Text',
|
|
9
|
+
numeric: 'Number',
|
|
10
|
+
number: 'Number',
|
|
11
|
+
datetime: 'Date & Time',
|
|
12
|
+
state: 'State',
|
|
13
|
+
ward: 'Ward',
|
|
14
|
+
district: 'District',
|
|
15
|
+
};
|
|
16
|
+
const matches = (field, query) => {
|
|
17
|
+
if (!query) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
const search = (field.label +
|
|
21
|
+
field.key +
|
|
22
|
+
TYPE_NAMES[field.value_type]).toLowerCase();
|
|
23
|
+
if (search.toLowerCase().indexOf(query) > -1) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
};
|
|
28
|
+
export class FieldManager extends StoreElement {
|
|
29
|
+
constructor() {
|
|
30
|
+
super(...arguments);
|
|
31
|
+
this.otherFieldKeys = [];
|
|
32
|
+
this.query = '';
|
|
33
|
+
}
|
|
34
|
+
static get styles() {
|
|
35
|
+
return css `
|
|
36
|
+
:host {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-grow: 1;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
min-height: 0px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.featured,
|
|
44
|
+
.other-fields {
|
|
45
|
+
background: #fff;
|
|
46
|
+
border-radius: var(--curvature);
|
|
47
|
+
box-shadow: var(--shadow);
|
|
48
|
+
margin-bottom: 1em;
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.featured {
|
|
54
|
+
max-height: 40%;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.other-fields {
|
|
58
|
+
flex-grow: 2;
|
|
59
|
+
min-height: 0px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
temba-textinput {
|
|
63
|
+
margin-bottom: 1em;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.scroll-box {
|
|
67
|
+
overflow-y: auto;
|
|
68
|
+
flex-grow: 1;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
display: flex;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.header temba-icon {
|
|
74
|
+
margin-right: 0.5em;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.label {
|
|
78
|
+
flex-grow: 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.header {
|
|
82
|
+
padding: 0.5em 1em;
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: flex-start;
|
|
85
|
+
border-bottom: 1px solid var(--color-widget-border);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.featured-field {
|
|
89
|
+
user-select: none;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
temba-sortable-list {
|
|
93
|
+
padding: 0.5em 0em;
|
|
94
|
+
width: 100%;
|
|
95
|
+
overflow-y: auto;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.scroll-box {
|
|
99
|
+
padding: 0.5em 0em;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
temba-icon[name='usages']:hover {
|
|
103
|
+
--icon-color: var(--color-link-primary);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.field:hover temba-icon[name='delete_small'] {
|
|
107
|
+
opacity: 1 !important;
|
|
108
|
+
cursor: pointer !important;
|
|
109
|
+
pointer-events: all !important;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
temba-icon[name='delete_small']:hover {
|
|
113
|
+
--icon-color: var(--color-link-primary);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.field {
|
|
117
|
+
border: 1px solid transparent;
|
|
118
|
+
margin: 0 0.5em;
|
|
119
|
+
border-radius: var(--curvature);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.featured temba-sortable-list .field:hover {
|
|
123
|
+
cursor: move;
|
|
124
|
+
border-color: #e6e6e6;
|
|
125
|
+
background: #fcfcfc;
|
|
126
|
+
}
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
firstUpdated(_changedProperties) {
|
|
130
|
+
super.firstUpdated(_changedProperties);
|
|
131
|
+
this.url = this.store.fieldsEndpoint;
|
|
132
|
+
}
|
|
133
|
+
filterFields() {
|
|
134
|
+
const filteredKeys = this.store.getFieldKeys().filter(key => {
|
|
135
|
+
const field = this.store.getContactField(key);
|
|
136
|
+
if (field.featured) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return matches(field, this.query);
|
|
140
|
+
});
|
|
141
|
+
// sort by the label instead of the key
|
|
142
|
+
filteredKeys.sort((a, b) => {
|
|
143
|
+
return this.store
|
|
144
|
+
.getContactField(a)
|
|
145
|
+
.label.localeCompare(this.store.getContactField(b).label);
|
|
146
|
+
});
|
|
147
|
+
const featured = [];
|
|
148
|
+
this.store.getFeaturedFields().forEach(field => {
|
|
149
|
+
if (matches(field, this.query)) {
|
|
150
|
+
featured.push(field);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
this.otherFieldKeys = filteredKeys;
|
|
154
|
+
this.featuredFields = featured;
|
|
155
|
+
}
|
|
156
|
+
updated(properties) {
|
|
157
|
+
super.update(properties);
|
|
158
|
+
if (properties.has('data')) {
|
|
159
|
+
this.filterFields();
|
|
160
|
+
}
|
|
161
|
+
else if (properties.has('query')) {
|
|
162
|
+
this.filterFields();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
handleSaveOrder(event) {
|
|
166
|
+
const list = event.currentTarget;
|
|
167
|
+
postJSON(this.priorityEndpoint, list
|
|
168
|
+
.getIds()
|
|
169
|
+
.reverse()
|
|
170
|
+
.reduce((map, key, idx) => {
|
|
171
|
+
map[key] = idx;
|
|
172
|
+
return map;
|
|
173
|
+
}, {})).then(() => {
|
|
174
|
+
this.store.refreshFields();
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
handleOrderChanged(event) {
|
|
178
|
+
const swapsies = event.detail;
|
|
179
|
+
const temp = this.featuredFields[swapsies.fromIdx];
|
|
180
|
+
this.featuredFields[swapsies.fromIdx] = this.featuredFields[swapsies.toIdx];
|
|
181
|
+
this.featuredFields[swapsies.toIdx] = temp;
|
|
182
|
+
this.requestUpdate('featuredFields');
|
|
183
|
+
}
|
|
184
|
+
handleDragStart(event) {
|
|
185
|
+
this.draggingId = event.detail.id;
|
|
186
|
+
}
|
|
187
|
+
handleDragStop() {
|
|
188
|
+
this.draggingId = null;
|
|
189
|
+
}
|
|
190
|
+
handleFieldAction(event) {
|
|
191
|
+
const ele = event.target;
|
|
192
|
+
const key = ele.dataset.key;
|
|
193
|
+
const action = ele.dataset.action;
|
|
194
|
+
this.fireCustomEvent(CustomEventType.Selection, { key, action });
|
|
195
|
+
}
|
|
196
|
+
handleSearch(event) {
|
|
197
|
+
this.query = (event.target.value || '').trim();
|
|
198
|
+
}
|
|
199
|
+
hasUsages(field) {
|
|
200
|
+
return (field.usages.campaign_events + field.usages.flows + field.usages.groups >
|
|
201
|
+
0);
|
|
202
|
+
}
|
|
203
|
+
renderField(field) {
|
|
204
|
+
return html `
|
|
205
|
+
<div
|
|
206
|
+
class="field sortable"
|
|
207
|
+
id="${field.key}"
|
|
208
|
+
style="
|
|
209
|
+
display: flex;
|
|
210
|
+
flex-direction: row;
|
|
211
|
+
align-items: center;
|
|
212
|
+
padding: 0.25em 1em;
|
|
213
|
+
${field.key === this.draggingId
|
|
214
|
+
? 'background: var(--color-selection)'
|
|
215
|
+
: ''}"
|
|
216
|
+
>
|
|
217
|
+
<div
|
|
218
|
+
style="display: flex; min-width: 200px; width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 2em"
|
|
219
|
+
>
|
|
220
|
+
<span
|
|
221
|
+
@click=${this.handleFieldAction}
|
|
222
|
+
data-key=${field.key}
|
|
223
|
+
data-action="update"
|
|
224
|
+
style="color: var(--color-link-primary); cursor:pointer;"
|
|
225
|
+
>
|
|
226
|
+
${field.label}
|
|
227
|
+
</span>
|
|
228
|
+
${this.hasUsages(field)
|
|
229
|
+
? html `
|
|
230
|
+
<temba-icon
|
|
231
|
+
size="0.8"
|
|
232
|
+
style="color: #ccc; margin-left: 0.7em;"
|
|
233
|
+
name="usages"
|
|
234
|
+
data-key=${field.key}
|
|
235
|
+
data-action="usages"
|
|
236
|
+
@click=${this.handleFieldAction}
|
|
237
|
+
clickable
|
|
238
|
+
></temba-icon>
|
|
239
|
+
`
|
|
240
|
+
: null}
|
|
241
|
+
<div class="flex-grow:1"></div>
|
|
242
|
+
</div>
|
|
243
|
+
<div style="flex-grow:1; font-family: monospace; font-size:0.8em;">
|
|
244
|
+
@fields.${field.key}
|
|
245
|
+
</div>
|
|
246
|
+
<div>${TYPE_NAMES[field.value_type]}</div>
|
|
247
|
+
<temba-icon
|
|
248
|
+
style="pointer-events:none;color:#ccc;margin-left:0.3em;margin-right:-0.5em;opacity:0"
|
|
249
|
+
name="delete_small"
|
|
250
|
+
data-key=${field.key}
|
|
251
|
+
data-action="delete"
|
|
252
|
+
@click=${this.handleFieldAction}
|
|
253
|
+
></temba-icon>
|
|
254
|
+
</div>
|
|
255
|
+
`;
|
|
256
|
+
}
|
|
257
|
+
render() {
|
|
258
|
+
if (!this.featuredFields) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
return html `
|
|
262
|
+
<temba-textinput
|
|
263
|
+
id="search"
|
|
264
|
+
placeholder="Search"
|
|
265
|
+
@change=${this.handleSearch}
|
|
266
|
+
clearable
|
|
267
|
+
></temba-textinput>
|
|
268
|
+
|
|
269
|
+
${this.featuredFields.length > 0
|
|
270
|
+
? html `
|
|
271
|
+
<div class="featured">
|
|
272
|
+
<div class="header">
|
|
273
|
+
<temba-icon name="featured"></temba-icon>
|
|
274
|
+
<div class="label">Featured</div>
|
|
275
|
+
</div>
|
|
276
|
+
${this.query
|
|
277
|
+
? html `
|
|
278
|
+
<div class="scroll-box">
|
|
279
|
+
${this.featuredFields.map(field => this.renderField(field))}
|
|
280
|
+
</div>
|
|
281
|
+
`
|
|
282
|
+
: html `
|
|
283
|
+
<temba-sortable-list
|
|
284
|
+
@change=${this.handleSaveOrder}
|
|
285
|
+
@temba-order-changed=${this.handleOrderChanged}
|
|
286
|
+
@temba-drag-start=${this.handleDragStart}
|
|
287
|
+
@temba-drag-stop=${this.handleDragStop}
|
|
288
|
+
>
|
|
289
|
+
${this.featuredFields.map(field => this.renderField(field))}
|
|
290
|
+
</temba-sortable-list>
|
|
291
|
+
`}
|
|
292
|
+
</div>
|
|
293
|
+
`
|
|
294
|
+
: null}
|
|
295
|
+
|
|
296
|
+
<div class="other-fields">
|
|
297
|
+
<div class="header">
|
|
298
|
+
<temba-icon name="fields"></temba-icon>
|
|
299
|
+
<div class="label">Everything Else</div>
|
|
300
|
+
</div>
|
|
301
|
+
<div class="scroll-box">
|
|
302
|
+
${this.otherFieldKeys.map(field => this.renderField(this.store.getContactField(field)))}
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
`;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
__decorate([
|
|
309
|
+
property({ type: String, attribute: 'priority-endpoint' })
|
|
310
|
+
], FieldManager.prototype, "priorityEndpoint", void 0);
|
|
311
|
+
__decorate([
|
|
312
|
+
property({ type: Object, attribute: false })
|
|
313
|
+
], FieldManager.prototype, "featuredFields", void 0);
|
|
314
|
+
__decorate([
|
|
315
|
+
property({ type: Object, attribute: false })
|
|
316
|
+
], FieldManager.prototype, "otherFieldKeys", void 0);
|
|
317
|
+
__decorate([
|
|
318
|
+
property({ type: String })
|
|
319
|
+
], FieldManager.prototype, "draggingId", void 0);
|
|
320
|
+
__decorate([
|
|
321
|
+
property({ type: String })
|
|
322
|
+
], FieldManager.prototype, "query", void 0);
|
|
323
|
+
//# sourceMappingURL=FieldManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldManager.js","sourceRoot":"","sources":["../../../src/fields/FieldManager.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, PropertyValueMap, TemplateResult } from 'lit';\nimport { property } from 'lit/decorators';\nimport { ContactField, CustomEventType } from '../interfaces';\n\nimport { SortableList } from '../list/SortableList';\nimport { StoreElement } from '../store/StoreElement';\nimport { postJSON } from '../utils';\n\nconst TYPE_NAMES = {\n text: 'Text',\n numeric: 'Number',\n number: 'Number',\n datetime: 'Date & Time',\n state: 'State',\n ward: 'Ward',\n district: 'District',\n};\n\nconst matches = (field: ContactField, query: string): boolean => {\n if (!query) {\n return true;\n }\n const search = (\n field.label +\n field.key +\n TYPE_NAMES[field.value_type]\n ).toLowerCase();\n if (search.toLowerCase().indexOf(query) > -1) {\n return true;\n }\n return false;\n};\n\nexport class FieldManager extends StoreElement {\n static get styles() {\n return css`\n :host {\n display: flex;\n flex-grow: 1;\n flex-direction: column;\n min-height: 0px;\n }\n\n .featured,\n .other-fields {\n background: #fff;\n border-radius: var(--curvature);\n box-shadow: var(--shadow);\n margin-bottom: 1em;\n display: flex;\n flex-direction: column;\n }\n\n .featured {\n max-height: 40%;\n }\n\n .other-fields {\n flex-grow: 2;\n min-height: 0px;\n }\n\n temba-textinput {\n margin-bottom: 1em;\n }\n\n .scroll-box {\n overflow-y: auto;\n flex-grow: 1;\n flex-direction: column;\n display: flex;\n }\n\n .header temba-icon {\n margin-right: 0.5em;\n }\n\n .label {\n flex-grow: 1;\n }\n\n .header {\n padding: 0.5em 1em;\n display: flex;\n align-items: flex-start;\n border-bottom: 1px solid var(--color-widget-border);\n }\n\n .featured-field {\n user-select: none;\n }\n\n temba-sortable-list {\n padding: 0.5em 0em;\n width: 100%;\n overflow-y: auto;\n }\n\n .scroll-box {\n padding: 0.5em 0em;\n }\n\n temba-icon[name='usages']:hover {\n --icon-color: var(--color-link-primary);\n }\n\n .field:hover temba-icon[name='delete_small'] {\n opacity: 1 !important;\n cursor: pointer !important;\n pointer-events: all !important;\n }\n\n temba-icon[name='delete_small']:hover {\n --icon-color: var(--color-link-primary);\n }\n\n .field {\n border: 1px solid transparent;\n margin: 0 0.5em;\n border-radius: var(--curvature);\n }\n\n .featured temba-sortable-list .field:hover {\n cursor: move;\n border-color: #e6e6e6;\n background: #fcfcfc;\n }\n `;\n }\n\n @property({ type: String, attribute: 'priority-endpoint' })\n priorityEndpoint: string;\n\n @property({ type: Object, attribute: false })\n featuredFields: ContactField[];\n\n @property({ type: Object, attribute: false })\n otherFieldKeys: string[] = [];\n\n @property({ type: String })\n draggingId: string;\n\n @property({ type: String })\n query = '';\n\n protected firstUpdated(\n _changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.firstUpdated(_changedProperties);\n this.url = this.store.fieldsEndpoint;\n }\n\n private filterFields() {\n const filteredKeys = this.store.getFieldKeys().filter(key => {\n const field = this.store.getContactField(key);\n if (field.featured) {\n return false;\n }\n return matches(field, this.query);\n });\n\n // sort by the label instead of the key\n filteredKeys.sort((a, b) => {\n return this.store\n .getContactField(a)\n .label.localeCompare(this.store.getContactField(b).label);\n });\n\n const featured: ContactField[] = [];\n this.store.getFeaturedFields().forEach(field => {\n if (matches(field, this.query)) {\n featured.push(field);\n }\n });\n\n this.otherFieldKeys = filteredKeys;\n this.featuredFields = featured;\n }\n\n protected updated(\n properties: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.update(properties);\n if (properties.has('data')) {\n this.filterFields();\n } else if (properties.has('query')) {\n this.filterFields();\n }\n }\n\n private handleSaveOrder(event) {\n const list = event.currentTarget as SortableList;\n postJSON(\n this.priorityEndpoint,\n list\n .getIds()\n .reverse()\n .reduce((map, key, idx) => {\n map[key] = idx;\n return map;\n }, {})\n ).then(() => {\n this.store.refreshFields();\n });\n }\n\n private handleOrderChanged(event) {\n const swapsies = event.detail;\n const temp = this.featuredFields[swapsies.fromIdx];\n this.featuredFields[swapsies.fromIdx] = this.featuredFields[swapsies.toIdx];\n this.featuredFields[swapsies.toIdx] = temp;\n this.requestUpdate('featuredFields');\n }\n\n private handleDragStart(event) {\n this.draggingId = event.detail.id;\n }\n\n private handleDragStop() {\n this.draggingId = null;\n }\n\n private handleFieldAction(event: MouseEvent) {\n const ele = event.target as HTMLDivElement;\n const key = ele.dataset.key;\n const action = ele.dataset.action;\n this.fireCustomEvent(CustomEventType.Selection, { key, action });\n }\n\n private handleSearch(event) {\n this.query = (event.target.value || '').trim();\n }\n\n private hasUsages(field: ContactField): boolean {\n return (\n field.usages.campaign_events + field.usages.flows + field.usages.groups >\n 0\n );\n }\n\n private renderField(field: ContactField) {\n return html`\n <div\n class=\"field sortable\"\n id=\"${field.key}\"\n style=\"\n display: flex; \n flex-direction: row; \n align-items: center;\n padding: 0.25em 1em; \n ${field.key === this.draggingId\n ? 'background: var(--color-selection)'\n : ''}\"\n >\n <div\n style=\"display: flex; min-width: 200px; width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 2em\"\n >\n <span\n @click=${this.handleFieldAction}\n data-key=${field.key}\n data-action=\"update\"\n style=\"color: var(--color-link-primary); cursor:pointer;\"\n >\n ${field.label}\n </span>\n ${this.hasUsages(field)\n ? html`\n <temba-icon\n size=\"0.8\"\n style=\"color: #ccc; margin-left: 0.7em;\"\n name=\"usages\"\n data-key=${field.key}\n data-action=\"usages\"\n @click=${this.handleFieldAction}\n clickable\n ></temba-icon>\n `\n : null}\n <div class=\"flex-grow:1\"></div>\n </div>\n <div style=\"flex-grow:1; font-family: monospace; font-size:0.8em;\">\n @fields.${field.key}\n </div>\n <div>${TYPE_NAMES[field.value_type]}</div>\n <temba-icon\n style=\"pointer-events:none;color:#ccc;margin-left:0.3em;margin-right:-0.5em;opacity:0\"\n name=\"delete_small\"\n data-key=${field.key}\n data-action=\"delete\"\n @click=${this.handleFieldAction}\n ></temba-icon>\n </div>\n `;\n }\n\n public render(): TemplateResult {\n if (!this.featuredFields) {\n return null;\n }\n\n return html`\n <temba-textinput\n id=\"search\"\n placeholder=\"Search\"\n @change=${this.handleSearch}\n clearable\n ></temba-textinput>\n\n ${this.featuredFields.length > 0\n ? html`\n <div class=\"featured\">\n <div class=\"header\">\n <temba-icon name=\"featured\"></temba-icon>\n <div class=\"label\">Featured</div>\n </div>\n ${this.query\n ? html`\n <div class=\"scroll-box\">\n ${this.featuredFields.map(field =>\n this.renderField(field)\n )}\n </div>\n `\n : html`\n <temba-sortable-list\n @change=${this.handleSaveOrder}\n @temba-order-changed=${this.handleOrderChanged}\n @temba-drag-start=${this.handleDragStart}\n @temba-drag-stop=${this.handleDragStop}\n >\n ${this.featuredFields.map(field =>\n this.renderField(field)\n )}\n </temba-sortable-list>\n `}\n </div>\n `\n : null}\n\n <div class=\"other-fields\">\n <div class=\"header\">\n <temba-icon name=\"fields\"></temba-icon>\n <div class=\"label\">Everything Else</div>\n </div>\n <div class=\"scroll-box\">\n ${this.otherFieldKeys.map(field =>\n this.renderField(this.store.getContactField(field))\n )}\n </div>\n </div>\n `;\n }\n}\n"]}
|
|
@@ -33,5 +33,8 @@ export var CustomEventType;
|
|
|
33
33
|
CustomEventType["NoPath"] = "temba-no-path";
|
|
34
34
|
CustomEventType["StoreUpdated"] = "temba-store-updated";
|
|
35
35
|
CustomEventType["Ready"] = "temba-ready";
|
|
36
|
+
CustomEventType["OrderChanged"] = "temba-order-changed";
|
|
37
|
+
CustomEventType["DragStart"] = "temba-drag-start";
|
|
38
|
+
CustomEventType["DragStop"] = "temba-drag-stop";
|
|
36
39
|
})(CustomEventType || (CustomEventType = {}));
|
|
37
40
|
//# sourceMappingURL=interfaces.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":"AAUA,MAAM,CAAN,IAAY,SAIX;AAJD,WAAY,SAAS;IACnB,mCAAsB,CAAA;IACtB,uCAA0B,CAAA;IAC1B,qCAAwB,CAAA;AAC1B,CAAC,EAJW,SAAS,KAAT,SAAS,QAIpB;AAED,MAAM,CAAN,IAAY,kBAIX;AAJD,WAAY,kBAAkB;IAC5B,sDAAgC,CAAA;IAChC,gEAA0C,CAAA;IAC1C,4DAAsC,CAAA;AACxC,CAAC,EAJW,kBAAkB,KAAlB,kBAAkB,QAI7B;AAED,MAAM,CAAN,IAAY,YAGX;AAHD,WAAY,YAAY;IACtB,6BAAa,CAAA;IACb,iCAAiB,CAAA;AACnB,CAAC,EAHW,YAAY,KAAZ,YAAY,QAGvB;
|
|
1
|
+
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":"AAUA,MAAM,CAAN,IAAY,SAIX;AAJD,WAAY,SAAS;IACnB,mCAAsB,CAAA;IACtB,uCAA0B,CAAA;IAC1B,qCAAwB,CAAA;AAC1B,CAAC,EAJW,SAAS,KAAT,SAAS,QAIpB;AAED,MAAM,CAAN,IAAY,kBAIX;AAJD,WAAY,kBAAkB;IAC5B,sDAAgC,CAAA;IAChC,gEAA0C,CAAA;IAC1C,4DAAsC,CAAA;AACxC,CAAC,EAJW,kBAAkB,KAAlB,kBAAkB,QAI7B;AAED,MAAM,CAAN,IAAY,YAGX;AAHD,WAAY,YAAY;IACtB,6BAAa,CAAA;IACb,iCAAiB,CAAA;AACnB,CAAC,EAHW,YAAY,KAAZ,YAAY,QAGvB;AAwMD,MAAM,CAAN,IAAY,eAoBX;AApBD,WAAY,eAAe;IACzB,0CAAuB,CAAA;IACvB,8CAA2B,CAAA;IAC3B,yDAAsC,CAAA;IACtC,gDAA6B,CAAA;IAC7B,gDAA6B,CAAA;IAC7B,yDAAsC,CAAA;IACtC,uDAAoC,CAAA;IACpC,6DAA0C,CAAA;IAC1C,2DAAwC,CAAA;IACxC,2DAAwC,CAAA;IACxC,yDAAsC,CAAA;IACtC,gDAA6B,CAAA;IAC7B,kDAA+B,CAAA;IAC/B,2CAAwB,CAAA;IACxB,uDAAoC,CAAA;IACpC,wCAAqB,CAAA;IACrB,uDAAoC,CAAA;IACpC,iDAA8B,CAAA;IAC9B,+CAA4B,CAAA;AAC9B,CAAC,EApBW,eAAe,KAAf,eAAe,QAoB1B","sourcesContent":["export interface Workspace {\n uuid: string;\n name: string;\n country: string;\n languages: string[];\n timezone: string;\n date_style: DateStyle;\n anon: boolean;\n}\n\nexport enum DateStyle {\n DayFirst = 'day_first',\n MonthFirst = 'month_first',\n YearFirst = 'year_first',\n}\n\nexport enum ScheduledEventType {\n CampaignEvent = 'campaign_event',\n ScheduledBroadcast = 'scheduled_broadcast',\n ScheduledTrigger = 'scheduled_trigger',\n}\n\nexport enum TicketStatus {\n Open = 'open',\n Closed = 'closed',\n}\n\nexport interface ScheduledEvent {\n type: ScheduledEventType;\n scheduled: string;\n repeat_period: string;\n campaign?: ObjectReference;\n flow?: ObjectReference;\n message?: string;\n}\n\nexport interface User {\n id?: number;\n first_name?: string;\n last_name?: string;\n email?: string;\n role?: string;\n created_on?: string;\n}\n\nexport interface Ticket {\n uuid: string;\n subject: string;\n body?: string;\n closed_on: string;\n opened_on: string;\n status: string;\n contact: ObjectReference;\n ticketer: ObjectReference;\n topic: ObjectReference;\n assignee?: User;\n}\n\nexport interface FlowResult {\n key: string;\n name: string;\n categories: string[];\n node_uuids: string[];\n}\n\nexport interface FlowDetails {\n name: string;\n results: FlowResult[];\n modified_on: string;\n runs: {\n active: number;\n completed: number;\n expired: number;\n interrupted: number;\n };\n}\n\nexport interface Msg {\n text: string;\n status: string;\n channel: ObjectReference;\n quick_replies: string[];\n urn: string;\n id: number;\n direction: string;\n type: string;\n created_by?: User;\n attachments: string[];\n}\n\nexport interface ObjectReference {\n uuid: string;\n name: string;\n}\n\nexport interface ContactField {\n key: string;\n label: string;\n value_type: string;\n featured: boolean;\n priority: number;\n usages: { campaign_events: number; flows: number; groups: number };\n}\n\nexport interface ContactGroup {\n uuid: string;\n count: number;\n name: string;\n query?: string;\n status: string;\n}\n\nexport interface URN {\n scheme: string;\n path: string;\n}\n\nexport interface Group {\n name: string;\n uuid: string;\n is_dynamic?: boolean;\n}\n\nexport interface ContactTicket {\n name: string;\n uuid: string;\n status: string;\n\n contact: {\n uuid: string;\n name: string;\n created_on: Date;\n last_seen_on: Date;\n };\n}\n\nexport interface Contact {\n name: string;\n uuid: string;\n stopped: boolean;\n blocked: boolean;\n urns: string[];\n language?: string;\n fields: { [key: string]: string };\n groups: Group[];\n modified_on: string;\n created_on: string;\n last_seen_on: string;\n status: string;\n\n anon_display?: string;\n flow?: ObjectReference;\n last_msg?: Msg;\n direction?: string;\n ticket: {\n uuid: string;\n subject: string;\n closed_on?: string;\n last_activity_on: string;\n assignee?: User;\n topic?: ObjectReference;\n };\n}\n\nexport interface FeatureProperties {\n name: string;\n osm_id: string;\n level: number;\n children?: FeatureProperties[];\n has_children?: boolean;\n aliases?: string;\n parent_osm_id?: string;\n id?: number;\n path?: string;\n}\n\nexport interface Position {\n top: number;\n left: number;\n}\n\nexport interface FunctionExample {\n template: string;\n output: string;\n}\n\nexport interface CompletionOption {\n name?: string;\n summary: string;\n\n // functions\n signature?: string;\n detail?: string;\n examples?: FunctionExample[];\n}\n\nexport interface CompletionResult {\n anchorPosition: Position;\n query: string;\n options: CompletionOption[];\n currentFunction: CompletionOption;\n}\n\nexport interface CompletionProperty {\n key: string;\n help: string;\n type: string;\n}\n\nexport interface CompletionType {\n name: string;\n\n key_source?: string;\n property_template?: CompletionProperty;\n properties?: CompletionProperty[];\n}\n\nexport interface CompletionSchema {\n types: CompletionType[];\n root: CompletionProperty[];\n root_no_session: CompletionProperty[];\n}\n\nexport type KeyedAssets = { [assetType: string]: string[] };\n\nexport enum CustomEventType {\n Loaded = 'temba-loaded',\n Canceled = 'temba-canceled',\n CursorChanged = 'temba-cursor-changed',\n Refreshed = 'temba-refreshed',\n Selection = 'temba-selection',\n ButtonClicked = 'temba-button-clicked',\n DialogHidden = 'temba-dialog-hidden',\n ScrollThreshold = 'temba-scroll-threshold',\n ContentChanged = 'temba-content-changed',\n ContextChanged = 'temba-context-changed',\n FetchComplete = 'temba-fetch-complete',\n Submitted = 'temba-submitted',\n Redirected = 'temba-redirected',\n NoPath = 'temba-no-path',\n StoreUpdated = 'temba-store-updated',\n Ready = 'temba-ready',\n OrderChanged = 'temba-order-changed',\n DragStart = 'temba-drag-start',\n DragStop = 'temba-drag-stop',\n}\n"]}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { css, html } from 'lit';
|
|
3
|
+
import { property } from 'lit/decorators';
|
|
4
|
+
import { CustomEventType } from '../interfaces';
|
|
5
|
+
import { RapidElement } from '../RapidElement';
|
|
6
|
+
/**
|
|
7
|
+
* A simple list that can be sorted by dragging
|
|
8
|
+
*/
|
|
9
|
+
// how far we have to drag before it starts
|
|
10
|
+
const DRAG_THRESHOLD = 5;
|
|
11
|
+
export class SortableList extends RapidElement {
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
this.ghostElement = null;
|
|
15
|
+
this.downEle = null;
|
|
16
|
+
this.xOffset = 0;
|
|
17
|
+
this.yOffset = 0;
|
|
18
|
+
this.yDown = 0;
|
|
19
|
+
this.draggingIdx = -1;
|
|
20
|
+
this.draggingEle = null;
|
|
21
|
+
this.handleMouseMove = this.handleMouseMove.bind(this);
|
|
22
|
+
this.handleMouseUp = this.handleMouseUp.bind(this);
|
|
23
|
+
this.handleMouseDown = this.handleMouseDown.bind(this);
|
|
24
|
+
}
|
|
25
|
+
static get styles() {
|
|
26
|
+
return css `
|
|
27
|
+
:host {
|
|
28
|
+
margin: auto;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.container {
|
|
32
|
+
user-select: none;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.dragging {
|
|
36
|
+
background: var(--color-selection);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.sortable {
|
|
40
|
+
transition: all 300ms ease-in-out;
|
|
41
|
+
display: flex;
|
|
42
|
+
padding: 0.4em 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.sortable:hover temba-icon {
|
|
46
|
+
opacity: 1;
|
|
47
|
+
cursor: move;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.ghost {
|
|
51
|
+
position: absolute;
|
|
52
|
+
opacity: 0.5;
|
|
53
|
+
transition: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.slot {
|
|
57
|
+
flex-grow: 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
slot > * {
|
|
61
|
+
user-select: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
temba-icon {
|
|
65
|
+
opacity: 0.1;
|
|
66
|
+
padding: 0.2em 0.5em;
|
|
67
|
+
transition: all 300ms ease-in-out;
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
firstUpdated(_changedProperties) {
|
|
72
|
+
super.firstUpdated(_changedProperties);
|
|
73
|
+
}
|
|
74
|
+
getIds() {
|
|
75
|
+
return this.shadowRoot
|
|
76
|
+
.querySelector('slot')
|
|
77
|
+
.assignedElements()
|
|
78
|
+
.map(ele => ele.id);
|
|
79
|
+
}
|
|
80
|
+
getRowIndex(id) {
|
|
81
|
+
return this.shadowRoot
|
|
82
|
+
.querySelector('slot')
|
|
83
|
+
.assignedElements()
|
|
84
|
+
.findIndex(ele => ele.id === id);
|
|
85
|
+
}
|
|
86
|
+
getOverlappingElement(mouseY) {
|
|
87
|
+
const ghostRect = this.ghostElement.getBoundingClientRect();
|
|
88
|
+
const ele = this.shadowRoot
|
|
89
|
+
.querySelector('slot')
|
|
90
|
+
.assignedElements()
|
|
91
|
+
.find(otherEle => {
|
|
92
|
+
const rect = otherEle.getBoundingClientRect();
|
|
93
|
+
// don't return ourselves
|
|
94
|
+
if (otherEle.id === this.ghostElement.id) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (mouseY > this.yDown) {
|
|
98
|
+
// moving down
|
|
99
|
+
return ghostRect.top < rect.bottom && ghostRect.bottom > rect.bottom;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// moving up
|
|
103
|
+
return rect.top < ghostRect.bottom && rect.bottom > ghostRect.bottom;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return ele;
|
|
107
|
+
}
|
|
108
|
+
handleMouseDown(event) {
|
|
109
|
+
let ele = event.target;
|
|
110
|
+
ele = ele.closest('.sortable');
|
|
111
|
+
if (ele) {
|
|
112
|
+
this.downEle = ele;
|
|
113
|
+
this.draggingId = ele.id;
|
|
114
|
+
this.draggingIdx = this.getRowIndex(ele.id);
|
|
115
|
+
this.draggingEle = ele;
|
|
116
|
+
this.xOffset = event.clientX - ele.offsetLeft;
|
|
117
|
+
this.yOffset = event.clientY - ele.offsetTop;
|
|
118
|
+
this.yDown = event.clientY;
|
|
119
|
+
document.addEventListener('mousemove', this.handleMouseMove);
|
|
120
|
+
document.addEventListener('mouseup', this.handleMouseUp);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
handleMouseMove(event) {
|
|
124
|
+
const scrollTop = this.shadowRoot
|
|
125
|
+
.querySelector('slot')
|
|
126
|
+
.assignedElements()[0].parentElement.scrollTop;
|
|
127
|
+
if (!this.ghostElement &&
|
|
128
|
+
this.downEle &&
|
|
129
|
+
Math.abs(event.clientY - this.yDown) > DRAG_THRESHOLD) {
|
|
130
|
+
this.fireCustomEvent(CustomEventType.DragStart, {
|
|
131
|
+
id: this.downEle.id,
|
|
132
|
+
});
|
|
133
|
+
this.ghostElement = this.downEle.cloneNode(true);
|
|
134
|
+
this.ghostElement.classList.add('ghost');
|
|
135
|
+
const computedStyle = getComputedStyle(this.downEle);
|
|
136
|
+
this.ghostElement.style.width =
|
|
137
|
+
this.downEle.clientWidth -
|
|
138
|
+
parseFloat(computedStyle.paddingLeft) -
|
|
139
|
+
parseFloat(computedStyle.paddingRight) +
|
|
140
|
+
'px';
|
|
141
|
+
const container = this.shadowRoot.querySelector('.container');
|
|
142
|
+
container.appendChild(this.ghostElement);
|
|
143
|
+
this.downEle = null;
|
|
144
|
+
}
|
|
145
|
+
if (this.ghostElement) {
|
|
146
|
+
this.ghostElement.style.left = event.clientX - this.xOffset + 'px';
|
|
147
|
+
this.ghostElement.style.top =
|
|
148
|
+
event.clientY - this.yOffset - scrollTop + 'px';
|
|
149
|
+
const other = this.getOverlappingElement(event.clientY);
|
|
150
|
+
if (other) {
|
|
151
|
+
const otherIdx = this.getRowIndex(other.id);
|
|
152
|
+
const dragId = this.ghostElement.id;
|
|
153
|
+
const otherId = other.id;
|
|
154
|
+
this.fireCustomEvent(CustomEventType.OrderChanged, {
|
|
155
|
+
from: dragId,
|
|
156
|
+
to: otherId,
|
|
157
|
+
fromIdx: this.draggingIdx,
|
|
158
|
+
toIdx: otherIdx,
|
|
159
|
+
});
|
|
160
|
+
// TODO: Dont do swapping, just send the full order?
|
|
161
|
+
this.draggingIdx = otherIdx;
|
|
162
|
+
this.draggingId = otherId;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
handleMouseUp() {
|
|
167
|
+
if (this.draggingId) {
|
|
168
|
+
this.fireCustomEvent(CustomEventType.DragStop, {
|
|
169
|
+
id: this.draggingId,
|
|
170
|
+
});
|
|
171
|
+
this.draggingId = null;
|
|
172
|
+
this.downEle = null;
|
|
173
|
+
if (this.ghostElement) {
|
|
174
|
+
this.ghostElement.remove();
|
|
175
|
+
this.ghostElement = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
document.removeEventListener('mousemove', this.handleMouseMove);
|
|
179
|
+
document.removeEventListener('mouseup', this.handleMouseUp);
|
|
180
|
+
this.dispatchEvent(new Event('change'));
|
|
181
|
+
}
|
|
182
|
+
render() {
|
|
183
|
+
return html `
|
|
184
|
+
<div class="container">
|
|
185
|
+
<slot @mousedown=${this.handleMouseDown}></slot>
|
|
186
|
+
</div>
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
__decorate([
|
|
191
|
+
property({ type: String })
|
|
192
|
+
], SortableList.prototype, "draggingId", void 0);
|
|
193
|
+
//# sourceMappingURL=SortableList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SortableList.js","sourceRoot":"","sources":["../../../src/list/SortableList.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, PropertyValueMap, TemplateResult } from 'lit';\nimport { property } from 'lit/decorators';\nimport { CustomEventType } from '../interfaces';\nimport { RapidElement } from '../RapidElement';\n\n/**\n * A simple list that can be sorted by dragging\n */\n\n// how far we have to drag before it starts\nconst DRAG_THRESHOLD = 5;\nexport class SortableList extends RapidElement {\n static get styles() {\n return css`\n :host {\n margin: auto;\n }\n\n .container {\n user-select: none;\n }\n\n .dragging {\n background: var(--color-selection);\n }\n\n .sortable {\n transition: all 300ms ease-in-out;\n display: flex;\n padding: 0.4em 0;\n }\n\n .sortable:hover temba-icon {\n opacity: 1;\n cursor: move;\n }\n\n .ghost {\n position: absolute;\n opacity: 0.5;\n transition: none;\n }\n\n .slot {\n flex-grow: 1;\n }\n\n slot > * {\n user-select: none;\n }\n\n temba-icon {\n opacity: 0.1;\n padding: 0.2em 0.5em;\n transition: all 300ms ease-in-out;\n }\n `;\n }\n\n @property({ type: String })\n draggingId: string;\n\n ghostElement: HTMLDivElement = null;\n downEle: HTMLDivElement = null;\n xOffset = 0;\n yOffset = 0;\n yDown = 0;\n\n draggingIdx = -1;\n draggingEle = null;\n\n public constructor() {\n super();\n this.handleMouseMove = this.handleMouseMove.bind(this);\n this.handleMouseUp = this.handleMouseUp.bind(this);\n this.handleMouseDown = this.handleMouseDown.bind(this);\n }\n\n protected firstUpdated(\n _changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.firstUpdated(_changedProperties);\n }\n\n public getIds() {\n return this.shadowRoot\n .querySelector('slot')\n .assignedElements()\n .map(ele => ele.id);\n }\n\n private getRowIndex(id: string): number {\n return this.shadowRoot\n .querySelector('slot')\n .assignedElements()\n .findIndex(ele => ele.id === id);\n }\n\n private getOverlappingElement(mouseY: number): HTMLDivElement {\n const ghostRect = this.ghostElement.getBoundingClientRect();\n\n const ele = this.shadowRoot\n .querySelector('slot')\n .assignedElements()\n .find(otherEle => {\n const rect = otherEle.getBoundingClientRect();\n\n // don't return ourselves\n if (otherEle.id === this.ghostElement.id) {\n return false;\n }\n\n if (mouseY > this.yDown) {\n // moving down\n return ghostRect.top < rect.bottom && ghostRect.bottom > rect.bottom;\n } else {\n // moving up\n return rect.top < ghostRect.bottom && rect.bottom > ghostRect.bottom;\n }\n });\n return ele as HTMLDivElement;\n }\n\n private handleMouseDown(event: MouseEvent) {\n let ele = event.target as HTMLDivElement;\n ele = ele.closest('.sortable');\n if (ele) {\n this.downEle = ele;\n this.draggingId = ele.id;\n this.draggingIdx = this.getRowIndex(ele.id);\n this.draggingEle = ele;\n\n this.xOffset = event.clientX - ele.offsetLeft;\n this.yOffset = event.clientY - ele.offsetTop;\n this.yDown = event.clientY;\n\n document.addEventListener('mousemove', this.handleMouseMove);\n document.addEventListener('mouseup', this.handleMouseUp);\n }\n }\n\n private handleMouseMove(event: MouseEvent) {\n const scrollTop = this.shadowRoot\n .querySelector('slot')\n .assignedElements()[0].parentElement.scrollTop;\n\n if (\n !this.ghostElement &&\n this.downEle &&\n Math.abs(event.clientY - this.yDown) > DRAG_THRESHOLD\n ) {\n this.fireCustomEvent(CustomEventType.DragStart, {\n id: this.downEle.id,\n });\n\n this.ghostElement = this.downEle.cloneNode(true) as HTMLDivElement;\n this.ghostElement.classList.add('ghost');\n\n const computedStyle = getComputedStyle(this.downEle);\n\n this.ghostElement.style.width =\n this.downEle.clientWidth -\n parseFloat(computedStyle.paddingLeft) -\n parseFloat(computedStyle.paddingRight) +\n 'px';\n const container = this.shadowRoot.querySelector('.container');\n\n container.appendChild(this.ghostElement);\n\n this.downEle = null;\n }\n\n if (this.ghostElement) {\n this.ghostElement.style.left = event.clientX - this.xOffset + 'px';\n this.ghostElement.style.top =\n event.clientY - this.yOffset - scrollTop + 'px';\n\n const other = this.getOverlappingElement(event.clientY);\n if (other) {\n const otherIdx = this.getRowIndex(other.id);\n const dragId = this.ghostElement.id;\n const otherId = other.id;\n\n this.fireCustomEvent(CustomEventType.OrderChanged, {\n from: dragId,\n to: otherId,\n fromIdx: this.draggingIdx,\n toIdx: otherIdx,\n });\n\n // TODO: Dont do swapping, just send the full order?\n this.draggingIdx = otherIdx;\n this.draggingId = otherId;\n }\n }\n }\n\n private handleMouseUp() {\n if (this.draggingId) {\n this.fireCustomEvent(CustomEventType.DragStop, {\n id: this.draggingId,\n });\n\n this.draggingId = null;\n this.downEle = null;\n\n if (this.ghostElement) {\n this.ghostElement.remove();\n this.ghostElement = null;\n }\n }\n document.removeEventListener('mousemove', this.handleMouseMove);\n document.removeEventListener('mouseup', this.handleMouseUp);\n this.dispatchEvent(new Event('change'));\n }\n\n public render(): TemplateResult {\n return html`\n <div class=\"container\">\n <slot @mousedown=${this.handleMouseDown}></slot>\n </div>\n `;\n }\n}\n"]}
|