@nyaruka/temba-components 0.123.0 → 0.124.0
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/copilot-instructions.md +22 -4
- package/CHANGELOG.md +11 -0
- package/demo/drag-drop-demo.html +141 -0
- package/demo/index.html +15 -0
- package/demo/test-drag-drop.html +94 -0
- package/dist/temba-components.js +323 -191
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/fields/FieldManager.js +27 -34
- package/out-tsc/src/fields/FieldManager.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +257 -60
- package/out-tsc/src/list/SortableList.js.map +1 -1
- package/out-tsc/src/omnibox/Omnibox.js +1 -1
- package/out-tsc/src/omnibox/Omnibox.js.map +1 -1
- package/out-tsc/src/select/Select.js +198 -38
- package/out-tsc/src/select/Select.js.map +1 -1
- package/out-tsc/src/webchat/WebChat.js +5 -2
- package/out-tsc/src/webchat/WebChat.js.map +1 -1
- package/out-tsc/test/temba-flow-editor-node.test.js +273 -0
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor.test.js +244 -0
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -0
- package/out-tsc/test/temba-flow-plumber.test.js +145 -0
- package/out-tsc/test/temba-flow-plumber.test.js.map +1 -0
- package/out-tsc/test/temba-flow-render.test.js +171 -0
- package/out-tsc/test/temba-flow-render.test.js.map +1 -0
- package/out-tsc/test/temba-omnibox.test.js +2 -3
- package/out-tsc/test/temba-omnibox.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +134 -53
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/temba-sortable-list.test.js +91 -15
- package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
- package/out-tsc/test/temba-webchat-lightbox-fix.test.js +42 -0
- package/out-tsc/test/temba-webchat-lightbox-fix.test.js.map +1 -0
- package/out-tsc/test/utils.test.js +30 -0
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/flow/editor-basic.png +0 -0
- package/screenshots/truth/list/fields-dragging.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/screenshots/truth/omnibox/selected.png +0 -0
- package/screenshots/truth/select/disabled-multi-selection.png +0 -0
- package/screenshots/truth/select/disabled-selection.png +0 -0
- package/screenshots/truth/select/disabled.png +0 -0
- package/screenshots/truth/select/embedded.png +0 -0
- package/screenshots/truth/select/empty-options.png +0 -0
- package/screenshots/truth/select/expression-selected.png +0 -0
- package/screenshots/truth/select/expressions.png +0 -0
- package/screenshots/truth/select/functions.png +0 -0
- package/screenshots/truth/select/local-options.png +0 -0
- package/screenshots/truth/select/multi-reorder-final.png +0 -0
- package/screenshots/truth/select/multi-reorder-initial.png +0 -0
- package/screenshots/truth/select/multi-with-endpoint.png +0 -0
- package/screenshots/truth/select/multiple-initial-values.png +0 -0
- package/screenshots/truth/select/remote-options.png +0 -0
- package/screenshots/truth/select/search-enabled.png +0 -0
- package/screenshots/truth/select/search-multi-no-matches.png +0 -0
- package/screenshots/truth/select/search-selected-focus.png +0 -0
- package/screenshots/truth/select/search-selected.png +0 -0
- package/screenshots/truth/select/search-with-selected.png +0 -0
- package/screenshots/truth/select/searching.png +0 -0
- package/screenshots/truth/select/selected-multi-maxitems-reached.png +0 -0
- package/screenshots/truth/select/selected-multi.png +0 -0
- package/screenshots/truth/select/selected-single.png +0 -0
- package/screenshots/truth/select/selection-clearable.png +0 -0
- package/screenshots/truth/select/static-initial-value.png +0 -0
- package/screenshots/truth/select/static-initial-via-selected.png +0 -0
- package/screenshots/truth/select/truncated-selection.png +0 -0
- package/screenshots/truth/select/with-placeholder.png +0 -0
- package/screenshots/truth/select/without-placeholder.png +0 -0
- package/screenshots/truth/templates/default.png +0 -0
- package/screenshots/truth/templates/unapproved.png +0 -0
- package/src/fields/FieldManager.ts +30 -38
- package/src/list/SortableList.ts +291 -67
- package/src/omnibox/Omnibox.ts +1 -1
- package/src/select/Select.ts +213 -42
- package/src/webchat/WebChat.ts +5 -2
- package/test/temba-flow-editor-node.test.ts +344 -0
- package/test/temba-flow-editor.test.ts +301 -0
- package/test/temba-flow-plumber.test.ts +189 -0
- package/test/temba-flow-render.test.ts +220 -0
- package/test/temba-omnibox.test.ts +2 -3
- package/test/temba-select.test.ts +180 -79
- package/test/temba-sortable-list.test.ts +108 -15
- package/test/temba-webchat-lightbox-fix.test.ts +57 -0
- package/test/utils.test.ts +52 -0
package/src/list/SortableList.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { RapidElement } from '../RapidElement';
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
// how far we have to drag before it starts
|
|
11
|
-
const DRAG_THRESHOLD =
|
|
11
|
+
const DRAG_THRESHOLD = 2;
|
|
12
12
|
export class SortableList extends RapidElement {
|
|
13
13
|
static get styles() {
|
|
14
14
|
return css`
|
|
@@ -18,18 +18,54 @@ export class SortableList extends RapidElement {
|
|
|
18
18
|
|
|
19
19
|
.container {
|
|
20
20
|
user-select: none;
|
|
21
|
+
position: relative;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.container.horizontal {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-wrap: wrap;
|
|
27
|
+
align-items: center;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
.dragging {
|
|
24
31
|
background: var(--color-selection);
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
.dragged-item {
|
|
35
|
+
opacity: 0;
|
|
36
|
+
pointer-events: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
.sortable {
|
|
28
40
|
transition: all 300ms ease-in-out;
|
|
29
41
|
display: flex;
|
|
30
42
|
padding: 0.4em 0;
|
|
31
43
|
}
|
|
32
44
|
|
|
45
|
+
.container.horizontal .sortable {
|
|
46
|
+
padding: 0;
|
|
47
|
+
margin-right: 0.25em;
|
|
48
|
+
margin-bottom: 0.25em;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.drop-indicator {
|
|
52
|
+
position: absolute;
|
|
53
|
+
background: var(--color-primary-dark, #1c7cd6);
|
|
54
|
+
z-index: 1000;
|
|
55
|
+
pointer-events: none;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.container.horizontal .drop-indicator {
|
|
59
|
+
width: 2px;
|
|
60
|
+
margin-top: -5px;
|
|
61
|
+
padding-bottom: 10px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.container:not(.horizontal) .drop-indicator {
|
|
65
|
+
height: 2px;
|
|
66
|
+
left: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
33
69
|
.sortable:hover temba-icon {
|
|
34
70
|
opacity: 1;
|
|
35
71
|
cursor: move;
|
|
@@ -37,8 +73,14 @@ export class SortableList extends RapidElement {
|
|
|
37
73
|
|
|
38
74
|
.ghost {
|
|
39
75
|
position: absolute;
|
|
40
|
-
opacity: 0.
|
|
76
|
+
opacity: 0.7;
|
|
41
77
|
transition: none;
|
|
78
|
+
background: var(--color-background, white);
|
|
79
|
+
border: 1px solid var(--color-primary, #1c7cd6);
|
|
80
|
+
border-radius: 4px;
|
|
81
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
82
|
+
pointer-events: none;
|
|
83
|
+
z-index: 999;
|
|
42
84
|
}
|
|
43
85
|
|
|
44
86
|
.slot {
|
|
@@ -60,14 +102,33 @@ export class SortableList extends RapidElement {
|
|
|
60
102
|
@property({ type: String })
|
|
61
103
|
draggingId: string;
|
|
62
104
|
|
|
105
|
+
@property({ type: Boolean })
|
|
106
|
+
horizontal: boolean = false;
|
|
107
|
+
|
|
108
|
+
@property({ type: String })
|
|
109
|
+
dropTargetId: string;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Optional callback to allow parent components to customize the ghost node.
|
|
113
|
+
* Called after the ghost node is cloned but before it is appended to the DOM.
|
|
114
|
+
*/
|
|
115
|
+
@property({ attribute: false })
|
|
116
|
+
prepareGhost?: (ghost: HTMLElement) => void;
|
|
117
|
+
|
|
63
118
|
ghostElement: HTMLDivElement = null;
|
|
64
119
|
downEle: HTMLDivElement = null;
|
|
65
120
|
xOffset = 0;
|
|
66
121
|
yOffset = 0;
|
|
67
122
|
yDown = 0;
|
|
123
|
+
xDown = 0;
|
|
68
124
|
|
|
69
125
|
draggingIdx = -1;
|
|
70
126
|
draggingEle = null;
|
|
127
|
+
dropIndicator: HTMLDivElement = null;
|
|
128
|
+
pendingDropIndex = -1;
|
|
129
|
+
pendingTargetElement: HTMLElement = null;
|
|
130
|
+
|
|
131
|
+
private clickBlocker: ((e: MouseEvent) => void) | null = null;
|
|
71
132
|
|
|
72
133
|
public constructor() {
|
|
73
134
|
super();
|
|
@@ -82,57 +143,139 @@ export class SortableList extends RapidElement {
|
|
|
82
143
|
super.firstUpdated(_changedProperties);
|
|
83
144
|
}
|
|
84
145
|
|
|
85
|
-
|
|
146
|
+
private getSortableElements(): Element[] {
|
|
86
147
|
return this.shadowRoot
|
|
87
148
|
.querySelector('slot')
|
|
88
149
|
.assignedElements()
|
|
89
|
-
.
|
|
150
|
+
.filter((ele) => ele.classList.contains('sortable'));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public getIds() {
|
|
154
|
+
return this.getSortableElements().map((ele) => ele.id);
|
|
90
155
|
}
|
|
91
156
|
|
|
92
157
|
private getRowIndex(id: string): number {
|
|
93
|
-
return this.
|
|
94
|
-
.querySelector('slot')
|
|
95
|
-
.assignedElements()
|
|
96
|
-
.findIndex((ele) => ele.id === id);
|
|
158
|
+
return this.getSortableElements().findIndex((ele) => ele.id === id);
|
|
97
159
|
}
|
|
98
160
|
|
|
99
|
-
private
|
|
100
|
-
|
|
161
|
+
private getDropTargetInfo(
|
|
162
|
+
mouseX: number,
|
|
163
|
+
mouseY: number
|
|
164
|
+
): { element: HTMLDivElement; insertAfter: boolean } | null {
|
|
165
|
+
const elements = this.getSortableElements().filter(
|
|
166
|
+
(ele) => ele.id !== this.draggingEle?.id
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (elements.length === 0) return null;
|
|
170
|
+
|
|
171
|
+
if (this.horizontal) {
|
|
172
|
+
// For horizontal layout, find the insertion point based on mouse X position
|
|
173
|
+
for (let i = 0; i < elements.length; i++) {
|
|
174
|
+
const ele = elements[i];
|
|
175
|
+
const rect = ele.getBoundingClientRect();
|
|
176
|
+
const centerX = rect.left + rect.width / 2;
|
|
177
|
+
|
|
178
|
+
if (mouseX < centerX) {
|
|
179
|
+
// Insert before this element
|
|
180
|
+
return { element: ele as HTMLDivElement, insertAfter: false };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// If we're past all elements, insert after the last one
|
|
184
|
+
return {
|
|
185
|
+
element: elements[elements.length - 1] as HTMLDivElement,
|
|
186
|
+
insertAfter: true
|
|
187
|
+
};
|
|
188
|
+
} else {
|
|
189
|
+
// For vertical layout, find the insertion point based on mouse Y position
|
|
190
|
+
for (let i = 0; i < elements.length; i++) {
|
|
191
|
+
const ele = elements[i];
|
|
192
|
+
const rect = ele.getBoundingClientRect();
|
|
193
|
+
const centerY = rect.top + rect.height / 2;
|
|
194
|
+
|
|
195
|
+
if (mouseY < centerY) {
|
|
196
|
+
// Insert before this element
|
|
197
|
+
return { element: ele as HTMLDivElement, insertAfter: false };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// If we're past all elements, insert after the last one
|
|
201
|
+
return {
|
|
202
|
+
element: elements[elements.length - 1] as HTMLDivElement,
|
|
203
|
+
insertAfter: true
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
101
207
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.assignedElements()
|
|
105
|
-
.find((otherEle) => {
|
|
106
|
-
const rect = otherEle.getBoundingClientRect();
|
|
208
|
+
private showDropIndicator(targetElement: HTMLElement, insertAfter: boolean) {
|
|
209
|
+
this.hideDropIndicator();
|
|
107
210
|
|
|
108
|
-
|
|
109
|
-
if (otherEle.id === this.ghostElement.id) {
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
211
|
+
if (!targetElement) return;
|
|
112
212
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
213
|
+
const container = this.shadowRoot.querySelector('.container');
|
|
214
|
+
this.dropIndicator = document.createElement('div');
|
|
215
|
+
this.dropIndicator.className = 'drop-indicator';
|
|
216
|
+
|
|
217
|
+
const targetRect = targetElement.getBoundingClientRect();
|
|
218
|
+
const containerRect = container.getBoundingClientRect();
|
|
219
|
+
|
|
220
|
+
if (this.horizontal) {
|
|
221
|
+
// For horizontal layout, show vertical line
|
|
222
|
+
this.dropIndicator.style.height = targetRect.height + 'px';
|
|
223
|
+
this.dropIndicator.style.top = targetRect.top - containerRect.top + 'px';
|
|
224
|
+
|
|
225
|
+
if (insertAfter) {
|
|
226
|
+
// Show line after target
|
|
227
|
+
this.dropIndicator.style.left =
|
|
228
|
+
targetRect.right - containerRect.left + 'px';
|
|
229
|
+
} else {
|
|
230
|
+
// Show line before target
|
|
231
|
+
this.dropIndicator.style.left =
|
|
232
|
+
targetRect.left - containerRect.left + 'px';
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
// For vertical layout, show horizontal line
|
|
236
|
+
this.dropIndicator.style.width = targetRect.width + 'px';
|
|
237
|
+
this.dropIndicator.style.left =
|
|
238
|
+
targetRect.left - containerRect.left + 'px';
|
|
239
|
+
|
|
240
|
+
if (insertAfter) {
|
|
241
|
+
// Show line after target
|
|
242
|
+
this.dropIndicator.style.top =
|
|
243
|
+
targetRect.bottom - containerRect.top + 'px';
|
|
244
|
+
} else {
|
|
245
|
+
// Show line before target
|
|
246
|
+
this.dropIndicator.style.top =
|
|
247
|
+
targetRect.top - containerRect.top + 'px';
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
container.appendChild(this.dropIndicator);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private hideDropIndicator() {
|
|
255
|
+
if (this.dropIndicator) {
|
|
256
|
+
this.dropIndicator.remove();
|
|
257
|
+
this.dropIndicator = null;
|
|
258
|
+
}
|
|
122
259
|
}
|
|
123
260
|
|
|
124
261
|
private handleMouseDown(event: MouseEvent) {
|
|
125
262
|
let ele = event.target as HTMLDivElement;
|
|
126
263
|
ele = ele.closest('.sortable');
|
|
127
264
|
if (ele) {
|
|
265
|
+
event.preventDefault();
|
|
266
|
+
event.stopPropagation();
|
|
267
|
+
|
|
128
268
|
this.downEle = ele;
|
|
129
269
|
this.draggingId = ele.id;
|
|
130
270
|
this.draggingIdx = this.getRowIndex(ele.id);
|
|
131
271
|
this.draggingEle = ele;
|
|
132
272
|
|
|
133
|
-
|
|
134
|
-
|
|
273
|
+
// Use getBoundingClientRect for accurate offsets
|
|
274
|
+
const rect = ele.getBoundingClientRect();
|
|
275
|
+
this.xOffset = event.clientX - rect.left;
|
|
276
|
+
this.yOffset = event.clientY - rect.top;
|
|
135
277
|
this.yDown = event.clientY;
|
|
278
|
+
this.xDown = event.clientX;
|
|
136
279
|
|
|
137
280
|
document.addEventListener('mousemove', this.handleMouseMove);
|
|
138
281
|
document.addEventListener('mouseup', this.handleMouseUp);
|
|
@@ -140,14 +283,11 @@ export class SortableList extends RapidElement {
|
|
|
140
283
|
}
|
|
141
284
|
|
|
142
285
|
private handleMouseMove(event: MouseEvent) {
|
|
143
|
-
const scrollTop = this.shadowRoot
|
|
144
|
-
.querySelector('slot')
|
|
145
|
-
.assignedElements()[0].parentElement.scrollTop;
|
|
146
|
-
|
|
147
286
|
if (
|
|
148
287
|
!this.ghostElement &&
|
|
149
288
|
this.downEle &&
|
|
150
|
-
Math.abs(event.clientY - this.yDown) > DRAG_THRESHOLD
|
|
289
|
+
(Math.abs(event.clientY - this.yDown) > DRAG_THRESHOLD ||
|
|
290
|
+
Math.abs(event.clientX - this.xDown) > DRAG_THRESHOLD)
|
|
151
291
|
) {
|
|
152
292
|
this.fireCustomEvent(CustomEventType.DragStart, {
|
|
153
293
|
id: this.downEle.id
|
|
@@ -156,58 +296,142 @@ export class SortableList extends RapidElement {
|
|
|
156
296
|
this.ghostElement = this.downEle.cloneNode(true) as HTMLDivElement;
|
|
157
297
|
this.ghostElement.classList.add('ghost');
|
|
158
298
|
|
|
159
|
-
|
|
299
|
+
// dim the original element while dragging
|
|
300
|
+
this.downEle.style.pointerEvents = 'none';
|
|
301
|
+
this.downEle.style.opacity = '0.5';
|
|
160
302
|
|
|
161
|
-
this.
|
|
162
|
-
|
|
163
|
-
parseFloat(computedStyle.paddingLeft) -
|
|
164
|
-
parseFloat(computedStyle.paddingRight) +
|
|
165
|
-
'px';
|
|
166
|
-
const container = this.shadowRoot.querySelector('.container');
|
|
303
|
+
const rect = this.downEle.getBoundingClientRect();
|
|
304
|
+
this.ghostElement.style.transition = 'transform 300ms linear';
|
|
167
305
|
|
|
168
|
-
|
|
306
|
+
this.ghostElement.style.width = rect.width + 'px';
|
|
307
|
+
this.ghostElement.style.height = rect.height + 'px';
|
|
308
|
+
this.ghostElement.style.position = 'fixed';
|
|
309
|
+
this.ghostElement.style.left = event.clientX - this.xOffset + 'px';
|
|
310
|
+
this.ghostElement.style.top = event.clientY - this.yOffset + 'px';
|
|
311
|
+
this.ghostElement.style.pointerEvents = 'none';
|
|
312
|
+
this.ghostElement.style.border =
|
|
313
|
+
'1px solid var(--color-primary, #1c7cd6)';
|
|
314
|
+
this.ghostElement.style.zIndex = '99999';
|
|
315
|
+
this.ghostElement.style.background = '#fff';
|
|
316
|
+
this.ghostElement.style.opacity = '0.7';
|
|
317
|
+
this.ghostElement.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.2)';
|
|
318
|
+
this.ghostElement.style.borderRadius = 'var(--curvature)';
|
|
319
|
+
|
|
320
|
+
// allow component to customize the ghost node
|
|
321
|
+
if (this.prepareGhost) {
|
|
322
|
+
this.prepareGhost(this.ghostElement);
|
|
323
|
+
}
|
|
169
324
|
|
|
170
|
-
this.
|
|
325
|
+
document.body.appendChild(this.ghostElement);
|
|
326
|
+
|
|
327
|
+
// this.downEle = null;
|
|
328
|
+
|
|
329
|
+
// Add global click blocker when drag starts
|
|
330
|
+
if (!this.clickBlocker) {
|
|
331
|
+
this.clickBlocker = (e: MouseEvent) => {
|
|
332
|
+
e.stopPropagation();
|
|
333
|
+
e.preventDefault();
|
|
334
|
+
};
|
|
335
|
+
// Use capture phase to intercept clicks before they reach any elements
|
|
336
|
+
document.addEventListener('click', this.clickBlocker, true);
|
|
337
|
+
}
|
|
171
338
|
}
|
|
172
339
|
|
|
173
340
|
if (this.ghostElement) {
|
|
174
341
|
this.ghostElement.style.left = event.clientX - this.xOffset + 'px';
|
|
175
|
-
this.ghostElement.style.top =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
342
|
+
this.ghostElement.style.top = event.clientY - this.yOffset + 'px';
|
|
343
|
+
|
|
344
|
+
const targetInfo = this.getDropTargetInfo(event.clientX, event.clientY);
|
|
345
|
+
if (targetInfo) {
|
|
346
|
+
const { element: targetElement, insertAfter } = targetInfo;
|
|
347
|
+
const targetIdx = this.getRowIndex(targetElement.id);
|
|
348
|
+
const originalDragIdx = this.getRowIndex(this.draggingEle.id);
|
|
349
|
+
|
|
350
|
+
// Calculate the intended drop index
|
|
351
|
+
let dropIdx = targetIdx;
|
|
352
|
+
if (insertAfter) {
|
|
353
|
+
dropIdx += 1;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Adjust dropIdx if dragging forward in the list
|
|
357
|
+
if (originalDragIdx < dropIdx) {
|
|
358
|
+
dropIdx -= 1;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Store pending drop info but don't fire event yet
|
|
362
|
+
this.dropTargetId = targetElement.id;
|
|
363
|
+
this.pendingDropIndex = dropIdx;
|
|
364
|
+
this.pendingTargetElement = targetElement;
|
|
365
|
+
|
|
366
|
+
// Show drop indicator
|
|
367
|
+
this.showDropIndicator(targetElement, insertAfter);
|
|
368
|
+
} else {
|
|
369
|
+
this.hideDropIndicator();
|
|
370
|
+
this.dropTargetId = null;
|
|
371
|
+
this.pendingDropIndex = -1;
|
|
372
|
+
this.pendingTargetElement = null;
|
|
194
373
|
}
|
|
195
374
|
}
|
|
196
375
|
}
|
|
197
376
|
|
|
198
|
-
private handleMouseUp() {
|
|
199
|
-
if (this.draggingId) {
|
|
377
|
+
private handleMouseUp(evt: MouseEvent) {
|
|
378
|
+
if (this.draggingId && this.ghostElement) {
|
|
379
|
+
evt.preventDefault();
|
|
380
|
+
evt.stopPropagation();
|
|
381
|
+
|
|
382
|
+
// restore visibility of the dragged item
|
|
383
|
+
|
|
384
|
+
if (this.downEle) {
|
|
385
|
+
this.downEle.style.pointerEvents = '';
|
|
386
|
+
this.downEle.style.opacity = '1';
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// fire the order changed event only when dropped if we have a valid drop position
|
|
390
|
+
if (this.pendingDropIndex >= 0 && this.pendingTargetElement) {
|
|
391
|
+
const originalDragIdx = this.getRowIndex(this.draggingEle.id);
|
|
392
|
+
|
|
393
|
+
// use swap-based logic - report which indexes need to be swapped
|
|
394
|
+
const fromIdx = originalDragIdx;
|
|
395
|
+
const toIdx = this.pendingDropIndex;
|
|
396
|
+
|
|
397
|
+
// only fire if the position actually changed
|
|
398
|
+
if (fromIdx !== toIdx) {
|
|
399
|
+
this.fireCustomEvent(CustomEventType.OrderChanged, {
|
|
400
|
+
swap: [fromIdx, toIdx]
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
200
405
|
this.fireCustomEvent(CustomEventType.DragStop, {
|
|
201
406
|
id: this.draggingId
|
|
202
407
|
});
|
|
203
408
|
|
|
204
409
|
this.draggingId = null;
|
|
410
|
+
this.dropTargetId = null;
|
|
205
411
|
this.downEle = null;
|
|
412
|
+
this.pendingDropIndex = -1;
|
|
413
|
+
this.pendingTargetElement = null;
|
|
206
414
|
|
|
207
415
|
if (this.ghostElement) {
|
|
208
|
-
|
|
416
|
+
// Remove from body if present
|
|
417
|
+
if (this.ghostElement.parentNode) {
|
|
418
|
+
this.ghostElement.parentNode.removeChild(this.ghostElement);
|
|
419
|
+
}
|
|
209
420
|
this.ghostElement = null;
|
|
210
421
|
}
|
|
422
|
+
|
|
423
|
+
this.hideDropIndicator();
|
|
424
|
+
|
|
425
|
+
// Keep the click blocker active for a short time after drop
|
|
426
|
+
if (this.clickBlocker) {
|
|
427
|
+
// We'll clean it up after a timeout
|
|
428
|
+
setTimeout(() => {
|
|
429
|
+
if (this.clickBlocker) {
|
|
430
|
+
document.removeEventListener('click', this.clickBlocker, true);
|
|
431
|
+
this.clickBlocker = null;
|
|
432
|
+
}
|
|
433
|
+
}, 100);
|
|
434
|
+
}
|
|
211
435
|
}
|
|
212
436
|
document.removeEventListener('mousemove', this.handleMouseMove);
|
|
213
437
|
document.removeEventListener('mouseup', this.handleMouseUp);
|
|
@@ -216,7 +440,7 @@ export class SortableList extends RapidElement {
|
|
|
216
440
|
|
|
217
441
|
public render(): TemplateResult {
|
|
218
442
|
return html`
|
|
219
|
-
<div class="container">
|
|
443
|
+
<div class="container ${this.horizontal ? 'horizontal' : ''}">
|
|
220
444
|
<slot @mousedown=${this.handleMouseDown}></slot>
|
|
221
445
|
</div>
|
|
222
446
|
`;
|
package/src/omnibox/Omnibox.ts
CHANGED
|
@@ -107,7 +107,7 @@ export class Omnibox extends Select<OmniOption> {
|
|
|
107
107
|
public renderSelectedItemDefault(option: OmniOption): TemplateResult {
|
|
108
108
|
return html`
|
|
109
109
|
<div
|
|
110
|
-
style="flex:1 1 auto; display: flex; align-items: stretch; color: var(--color-text-dark); font-size: 12px;"
|
|
110
|
+
style="flex:1 1 auto; text-overflow:ellipsis; overflow:hidden; white-space:nowrap; display: flex; align-items: stretch; color: var(--color-text-dark); font-size: 12px;"
|
|
111
111
|
>
|
|
112
112
|
<div style="align-self: center; padding: 0px 7px; color: #bbb">
|
|
113
113
|
${this.getIcon(option)}
|