@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.
Files changed (61) hide show
  1. package/.github/workflows/build.yml +4 -5
  2. package/CHANGELOG.md +21 -5
  3. package/demo/index.html +1 -1
  4. package/dist/{7b0d8aca.js → a374554b.js} +304 -78
  5. package/dist/index.js +304 -78
  6. package/dist/sw.js +1 -1
  7. package/dist/sw.js.map +1 -1
  8. package/dist/templates/components-body.html +1 -1
  9. package/dist/templates/components-head.html +1 -1
  10. package/out-tsc/src/FormElement.js +9 -6
  11. package/out-tsc/src/FormElement.js.map +1 -1
  12. package/out-tsc/src/contacts/ContactDetails.js +1 -1
  13. package/out-tsc/src/contacts/ContactDetails.js.map +1 -1
  14. package/out-tsc/src/contacts/ContactFields.js +6 -5
  15. package/out-tsc/src/contacts/ContactFields.js.map +1 -1
  16. package/out-tsc/src/contacts/ContactHistory.js +4 -4
  17. package/out-tsc/src/contacts/ContactHistory.js.map +1 -1
  18. package/out-tsc/src/fields/FieldManager.js +323 -0
  19. package/out-tsc/src/fields/FieldManager.js.map +1 -0
  20. package/out-tsc/src/interfaces.js +3 -0
  21. package/out-tsc/src/interfaces.js.map +1 -1
  22. package/out-tsc/src/list/SortableList.js +193 -0
  23. package/out-tsc/src/list/SortableList.js.map +1 -0
  24. package/out-tsc/src/store/Store.js +28 -17
  25. package/out-tsc/src/store/Store.js.map +1 -1
  26. package/out-tsc/src/store/StoreElement.js +1 -0
  27. package/out-tsc/src/store/StoreElement.js.map +1 -1
  28. package/out-tsc/src/vectoricon/index.js +3 -0
  29. package/out-tsc/src/vectoricon/index.js.map +1 -1
  30. package/out-tsc/temba-modules.js +4 -0
  31. package/out-tsc/temba-modules.js.map +1 -1
  32. package/out-tsc/test/temba-field-manager.test.js +47 -0
  33. package/out-tsc/test/temba-field-manager.test.js.map +1 -0
  34. package/out-tsc/test/temba-sortable-list.test.js +48 -0
  35. package/out-tsc/test/temba-sortable-list.test.js.map +1 -0
  36. package/out-tsc/test/temba-store.test.js +1 -1
  37. package/out-tsc/test/temba-store.test.js.map +1 -1
  38. package/package.json +1 -1
  39. package/screenshots/truth/list/fields-dragging.png +0 -0
  40. package/screenshots/truth/list/fields-filtered.png +0 -0
  41. package/screenshots/truth/list/fields-hovered.png +0 -0
  42. package/screenshots/truth/list/fields.png +0 -0
  43. package/screenshots/truth/list/sortable-dragging.png +0 -0
  44. package/screenshots/truth/list/sortable-dropped.png +0 -0
  45. package/screenshots/truth/list/sortable.png +0 -0
  46. package/src/FormElement.ts +9 -6
  47. package/src/contacts/ContactDetails.ts +1 -1
  48. package/src/contacts/ContactFields.ts +6 -5
  49. package/src/contacts/ContactHistory.ts +4 -4
  50. package/src/fields/FieldManager.ts +353 -0
  51. package/src/interfaces.ts +5 -1
  52. package/src/list/SortableList.ts +224 -0
  53. package/src/store/Store.ts +34 -21
  54. package/src/store/StoreElement.ts +1 -0
  55. package/src/vectoricon/index.ts +3 -0
  56. package/static/svg/index.pdf +137 -0
  57. package/temba-modules.ts +4 -0
  58. package/test/temba-field-manager.test.ts +60 -0
  59. package/test/temba-sortable-list.test.ts +58 -0
  60. package/test/temba-store.test.ts +1 -1
  61. 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;AAuMD,MAAM,CAAN,IAAY,eAiBX;AAjBD,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;AACvB,CAAC,EAjBW,eAAe,KAAf,eAAe,QAiB1B","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 pinned: boolean;\n priority: 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}\n"]}
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"]}