@design.estate/dees-catalog 4.9.0 → 4.10.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/dist_bundle/bundle.js +565 -94
- package/dist_ts_web/00_commitinfo_data.js +2 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.d.ts +23 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.js +10 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +575 -99
- package/dist_ts_web/elements/00group-harness/interfaces.d.ts +10 -1
- package/package.json +2 -2
- package/readme.md +3 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.ts +10 -0
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +615 -95
- package/ts_web/elements/00group-harness/interfaces.ts +11 -1
- package/dist_watch/bundle.js +0 -235637
- package/dist_watch/bundle.js.map +0 -7
- package/dist_watch/index.html +0 -28
package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
property,
|
|
9
9
|
state,
|
|
10
10
|
cssManager,
|
|
11
|
+
directives,
|
|
11
12
|
} from '@design.estate/dees-element';
|
|
12
13
|
import { themeDefaultStyles } from '../../00theme.js';
|
|
13
14
|
import type {
|
|
@@ -20,6 +21,8 @@ import type {
|
|
|
20
21
|
import { harnessFormatRelativeTime } from '../harness.helpers.js';
|
|
21
22
|
import '../../00group-utility/dees-icon/dees-icon.js';
|
|
22
23
|
|
|
24
|
+
const { repeat } = directives;
|
|
25
|
+
|
|
23
26
|
declare global {
|
|
24
27
|
interface HTMLElementTagNameMap {
|
|
25
28
|
'dees-harness-session-list': DeesHarnessSessionList;
|
|
@@ -31,6 +34,10 @@ interface IDropSlot {
|
|
|
31
34
|
index: number;
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
type TSectionRenderEntry =
|
|
38
|
+
| { type: 'session'; key: string; session: IHarnessSessionMeta; logicalIndex: number }
|
|
39
|
+
| { type: 'placeholder'; key: string };
|
|
40
|
+
|
|
34
41
|
/**
|
|
35
42
|
* Searchable conversation list, embeddable in a sidebar or a DeesModal.
|
|
36
43
|
* Emits `harness-session-select` on click, `harness-session-open` on
|
|
@@ -43,7 +50,8 @@ interface IDropSlot {
|
|
|
43
50
|
* `harness-group-create-request`, and right-clicking a group header emits
|
|
44
51
|
* `harness-group-context` — the host persists changes and passes fresh
|
|
45
52
|
* `groups` back in. Sessions in no group render in the trailing ungrouped
|
|
46
|
-
* section
|
|
53
|
+
* section with pinned entries first, then explicit order, then new sessions by
|
|
54
|
+
* recency. While a search query is active the list is flat.
|
|
47
55
|
*/
|
|
48
56
|
@customElement('dees-harness-session-list')
|
|
49
57
|
export class DeesHarnessSessionList extends DeesElement {
|
|
@@ -68,6 +76,10 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
68
76
|
@property({ type: Boolean })
|
|
69
77
|
accessor enableGrouping: boolean = false;
|
|
70
78
|
|
|
79
|
+
/** Explicit order for sortable sessions outside user-defined groups. */
|
|
80
|
+
@property({ attribute: false })
|
|
81
|
+
accessor ungroupedSessionIds: string[] = [];
|
|
82
|
+
|
|
71
83
|
@state()
|
|
72
84
|
accessor search: string = '';
|
|
73
85
|
|
|
@@ -77,9 +89,19 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
77
89
|
@state()
|
|
78
90
|
accessor dropSlot: IDropSlot | undefined = undefined;
|
|
79
91
|
|
|
92
|
+
@state()
|
|
93
|
+
accessor draggedItemHeight = 0;
|
|
94
|
+
|
|
95
|
+
@state()
|
|
96
|
+
accessor expandedSessionIds: ReadonlySet<string> = new Set<string>();
|
|
97
|
+
|
|
80
98
|
@state()
|
|
81
99
|
accessor collapsedGroupIds: ReadonlySet<string> = new Set<string>();
|
|
82
100
|
|
|
101
|
+
private reflowGeneration = 0;
|
|
102
|
+
private readonly reflowAnimations = new Set<Animation>();
|
|
103
|
+
private dragSourceElement: HTMLElement | undefined;
|
|
104
|
+
|
|
83
105
|
public static styles = [
|
|
84
106
|
themeDefaultStyles,
|
|
85
107
|
cssManager.defaultStyles,
|
|
@@ -89,26 +111,48 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
89
111
|
flex-direction: column;
|
|
90
112
|
min-height: 0;
|
|
91
113
|
font-family: var(--dees-font-family);
|
|
114
|
+
--session-normal: var(--dees-color-text-muted);
|
|
115
|
+
--session-working: var(--dees-color-accent-primary);
|
|
116
|
+
--session-finished: var(--dees-color-accent-success);
|
|
117
|
+
--session-attention: #bf5af2;
|
|
118
|
+
--session-error: var(--dees-color-accent-error);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
:host([gobright]) {
|
|
122
|
+
--session-attention: #af52de;
|
|
92
123
|
}
|
|
93
124
|
|
|
94
125
|
.searchRow {
|
|
126
|
+
position: relative;
|
|
95
127
|
flex: 0 0 auto;
|
|
96
|
-
padding: var(--dees-spacing-sm);
|
|
128
|
+
padding: var(--dees-spacing-sm) var(--dees-spacing-md) var(--dees-spacing-md);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.searchIcon {
|
|
132
|
+
position: absolute;
|
|
133
|
+
top: 50%;
|
|
134
|
+
left: calc(var(--dees-spacing-md) + 10px);
|
|
135
|
+
color: var(--dees-color-text-muted);
|
|
136
|
+
transform: translateY(-62%);
|
|
137
|
+
pointer-events: none;
|
|
97
138
|
}
|
|
98
139
|
|
|
99
140
|
.searchRow input {
|
|
100
141
|
width: 100%;
|
|
101
|
-
height: var(--dees-control-height-
|
|
102
|
-
padding: 0 var(--dees-spacing-md);
|
|
142
|
+
height: var(--dees-control-height-lg);
|
|
143
|
+
padding: 0 var(--dees-spacing-md) 0 34px;
|
|
103
144
|
border: 1px solid var(--dees-color-border-default);
|
|
104
|
-
border-radius: var(--dees-radius-
|
|
145
|
+
border-radius: var(--dees-radius-lg);
|
|
105
146
|
corner-shape: var(--dees-corner-shape);
|
|
106
|
-
background: var(--dees-color-bg-primary);
|
|
147
|
+
background: color-mix(in srgb, var(--dees-color-bg-primary) 78%, transparent);
|
|
107
148
|
color: var(--dees-color-text-primary);
|
|
108
149
|
font-family: inherit;
|
|
109
|
-
font-size: var(--dees-font-control-size,
|
|
150
|
+
font-size: var(--dees-font-control-size-sm, 12px);
|
|
110
151
|
outline: none;
|
|
111
152
|
box-sizing: border-box;
|
|
153
|
+
box-shadow: var(--dees-shadow-xs);
|
|
154
|
+
transition: border-color var(--dees-transition-fast) var(--dees-ease-standard),
|
|
155
|
+
box-shadow var(--dees-transition-fast) var(--dees-ease-standard);
|
|
112
156
|
}
|
|
113
157
|
|
|
114
158
|
.searchRow input:focus {
|
|
@@ -122,46 +166,146 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
122
166
|
overflow-y: auto;
|
|
123
167
|
scrollbar-width: thin;
|
|
124
168
|
scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
|
|
125
|
-
padding: 0 var(--dees-spacing-
|
|
169
|
+
padding: 0 var(--dees-spacing-md) var(--dees-spacing-md);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.section {
|
|
173
|
+
position: relative;
|
|
126
174
|
display: grid;
|
|
127
|
-
gap:
|
|
175
|
+
gap: var(--dees-spacing-sm);
|
|
128
176
|
align-content: start;
|
|
129
177
|
}
|
|
130
178
|
|
|
131
179
|
.item {
|
|
180
|
+
--card-accent: var(--session-normal);
|
|
181
|
+
position: relative;
|
|
132
182
|
width: 100%;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
border:
|
|
136
|
-
border-radius: var(--dees-radius-
|
|
183
|
+
min-width: 0;
|
|
184
|
+
overflow: hidden;
|
|
185
|
+
border: 1px solid color-mix(in srgb, var(--card-accent) 24%, var(--dees-color-border-subtle));
|
|
186
|
+
border-radius: var(--dees-radius-xl);
|
|
137
187
|
corner-shape: var(--dees-corner-shape);
|
|
138
|
-
background: transparent;
|
|
188
|
+
background: color-mix(in srgb, var(--dees-color-bg-primary) 90%, transparent);
|
|
139
189
|
color: var(--dees-color-text-primary);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
190
|
+
box-shadow:
|
|
191
|
+
var(--dees-shadow-sm),
|
|
192
|
+
inset 3px 0 0 var(--card-accent);
|
|
193
|
+
box-sizing: border-box;
|
|
194
|
+
transition:
|
|
195
|
+
border-color var(--dees-transition-fast) var(--dees-ease-standard),
|
|
196
|
+
background var(--dees-transition-fast) var(--dees-ease-standard),
|
|
197
|
+
box-shadow var(--dees-transition-fast) var(--dees-ease-standard);
|
|
144
198
|
}
|
|
145
199
|
|
|
146
200
|
.item:hover {
|
|
147
|
-
|
|
201
|
+
border-color: color-mix(in srgb, var(--card-accent) 42%, var(--dees-color-border-default));
|
|
202
|
+
background: color-mix(in srgb, var(--dees-color-bg-primary) 96%, var(--card-accent) 4%);
|
|
148
203
|
}
|
|
149
204
|
|
|
150
205
|
.item.selected {
|
|
151
|
-
|
|
152
|
-
box-shadow:
|
|
206
|
+
border-color: color-mix(in srgb, var(--dees-color-accent-primary) 65%, var(--dees-color-border-default));
|
|
207
|
+
box-shadow:
|
|
208
|
+
0 0 0 2px color-mix(in srgb, var(--dees-color-accent-primary) 18%, transparent),
|
|
209
|
+
var(--dees-shadow-sm),
|
|
210
|
+
inset 3px 0 0 var(--card-accent);
|
|
153
211
|
}
|
|
154
212
|
|
|
155
213
|
.item.dragging {
|
|
156
|
-
|
|
214
|
+
position: absolute;
|
|
215
|
+
inset-inline: 0;
|
|
216
|
+
z-index: -1;
|
|
217
|
+
opacity: 0;
|
|
218
|
+
pointer-events: none;
|
|
157
219
|
}
|
|
158
220
|
|
|
159
|
-
.
|
|
221
|
+
.item.state-working {
|
|
222
|
+
--card-accent: var(--session-working);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.item.state-finished {
|
|
226
|
+
--card-accent: var(--session-finished);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.item.state-attention {
|
|
230
|
+
--card-accent: var(--session-attention);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.item.state-error {
|
|
234
|
+
--card-accent: var(--session-error);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@keyframes attentionPulse {
|
|
238
|
+
0%, 100% {
|
|
239
|
+
box-shadow:
|
|
240
|
+
0 0 0 0 color-mix(in srgb, var(--session-attention) 0%, transparent),
|
|
241
|
+
var(--dees-shadow-sm),
|
|
242
|
+
inset 3px 0 0 var(--session-attention);
|
|
243
|
+
}
|
|
244
|
+
50% {
|
|
245
|
+
box-shadow:
|
|
246
|
+
0 0 0 4px color-mix(in srgb, var(--session-attention) 26%, transparent),
|
|
247
|
+
0 7px 18px color-mix(in srgb, var(--session-attention) 16%, transparent),
|
|
248
|
+
inset 3px 0 0 var(--session-attention);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.item.state-attention:not(.selected) {
|
|
253
|
+
animation: attentionPulse 1.15s ease-in-out infinite;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.itemHeader {
|
|
257
|
+
display: grid;
|
|
258
|
+
grid-template-columns: minmax(0, 1fr) 30px;
|
|
259
|
+
min-height: 46px;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.itemPrimary,
|
|
263
|
+
.expandToggle {
|
|
264
|
+
appearance: none;
|
|
265
|
+
border: 0;
|
|
266
|
+
background: transparent;
|
|
267
|
+
color: inherit;
|
|
268
|
+
font-family: inherit;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.itemPrimary {
|
|
160
272
|
display: flex;
|
|
161
|
-
|
|
273
|
+
min-width: 0;
|
|
274
|
+
align-items: center;
|
|
162
275
|
gap: var(--dees-spacing-sm);
|
|
276
|
+
padding: var(--dees-spacing-sm) var(--dees-spacing-xs) var(--dees-spacing-sm) var(--dees-spacing-md);
|
|
277
|
+
text-align: left;
|
|
278
|
+
cursor: pointer;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.itemPrimary:focus-visible,
|
|
282
|
+
.expandToggle:focus-visible {
|
|
283
|
+
outline: 2px solid var(--dees-color-accent-primary);
|
|
284
|
+
outline-offset: -3px;
|
|
285
|
+
border-radius: 9px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.stateDot {
|
|
289
|
+
width: 8px;
|
|
290
|
+
height: 8px;
|
|
291
|
+
flex: 0 0 auto;
|
|
292
|
+
border-radius: 50%;
|
|
293
|
+
background: var(--card-accent);
|
|
294
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--card-accent) 15%, transparent);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.itemCopy {
|
|
298
|
+
min-width: 0;
|
|
299
|
+
flex: 1;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.itemTitle {
|
|
303
|
+
display: flex;
|
|
304
|
+
min-width: 0;
|
|
305
|
+
align-items: center;
|
|
306
|
+
gap: 6px;
|
|
163
307
|
font-size: var(--dees-font-control-size-sm, 12px);
|
|
164
|
-
font-weight:
|
|
308
|
+
font-weight: 590;
|
|
165
309
|
}
|
|
166
310
|
|
|
167
311
|
.itemTitle .titleText {
|
|
@@ -173,24 +317,99 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
173
317
|
|
|
174
318
|
.itemTitle .itemIcon {
|
|
175
319
|
flex: 0 0 auto;
|
|
176
|
-
align-self: center;
|
|
177
320
|
color: var(--dees-color-text-secondary);
|
|
178
321
|
}
|
|
179
322
|
|
|
180
323
|
.itemTime {
|
|
181
|
-
margin-
|
|
182
|
-
flex: 0 0 auto;
|
|
324
|
+
margin-top: 2px;
|
|
183
325
|
color: var(--dees-color-text-muted);
|
|
184
326
|
font-size: var(--dees-font-caption2-size, 10px);
|
|
185
327
|
}
|
|
186
328
|
|
|
329
|
+
.expandToggle {
|
|
330
|
+
display: grid;
|
|
331
|
+
place-items: center;
|
|
332
|
+
margin: 7px 7px 7px 0;
|
|
333
|
+
border-radius: 9px;
|
|
334
|
+
color: var(--dees-color-text-muted);
|
|
335
|
+
cursor: pointer;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.expandToggle:hover {
|
|
339
|
+
background: color-mix(in srgb, var(--card-accent) 11%, transparent);
|
|
340
|
+
color: var(--dees-color-text-primary);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.expandToggle dees-icon {
|
|
344
|
+
transition: transform var(--dees-transition-fast) var(--dees-ease-standard);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.item.expanded .expandToggle dees-icon {
|
|
348
|
+
transform: rotate(180deg);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.itemDetails {
|
|
352
|
+
display: grid;
|
|
353
|
+
gap: 6px;
|
|
354
|
+
margin: 0 10px 10px 28px;
|
|
355
|
+
padding: 9px 10px;
|
|
356
|
+
border-top: 1px solid color-mix(in srgb, var(--card-accent) 20%, var(--dees-color-border-subtle));
|
|
357
|
+
color: var(--dees-color-text-secondary);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.stateLine {
|
|
361
|
+
display: flex;
|
|
362
|
+
align-items: center;
|
|
363
|
+
gap: 6px;
|
|
364
|
+
color: var(--card-accent);
|
|
365
|
+
font-size: var(--dees-font-caption1-size, 11px);
|
|
366
|
+
font-weight: 650;
|
|
367
|
+
}
|
|
368
|
+
|
|
187
369
|
.itemPreview {
|
|
188
|
-
margin-top: 1px;
|
|
189
370
|
color: var(--dees-color-text-secondary);
|
|
190
371
|
font-size: var(--dees-font-caption1-size, 11px);
|
|
191
372
|
overflow: hidden;
|
|
192
|
-
|
|
373
|
+
display: -webkit-box;
|
|
374
|
+
-webkit-line-clamp: 2;
|
|
375
|
+
-webkit-box-orient: vertical;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.itemMeta {
|
|
379
|
+
color: var(--dees-color-text-muted);
|
|
380
|
+
font-size: var(--dees-font-caption2-size, 10px);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.visuallyHidden {
|
|
384
|
+
position: absolute;
|
|
385
|
+
width: 1px;
|
|
386
|
+
height: 1px;
|
|
387
|
+
padding: 0;
|
|
388
|
+
margin: -1px;
|
|
389
|
+
overflow: hidden;
|
|
390
|
+
clip: rect(0, 0, 0, 0);
|
|
193
391
|
white-space: nowrap;
|
|
392
|
+
border: 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.dropPlaceholder,
|
|
396
|
+
.emptyDropZone {
|
|
397
|
+
display: grid;
|
|
398
|
+
place-items: center;
|
|
399
|
+
min-height: 46px;
|
|
400
|
+
border: 1px dashed color-mix(in srgb, var(--dees-color-accent-primary) 68%, transparent);
|
|
401
|
+
border-radius: var(--dees-radius-xl);
|
|
402
|
+
background: color-mix(in srgb, var(--dees-color-accent-primary) 8%, transparent);
|
|
403
|
+
color: var(--dees-color-accent-primary);
|
|
404
|
+
box-sizing: border-box;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.dropPlaceholder::after,
|
|
408
|
+
.emptyDropZone::after {
|
|
409
|
+
content: 'Drop conversation here';
|
|
410
|
+
font-size: var(--dees-font-caption2-size, 10px);
|
|
411
|
+
font-weight: 650;
|
|
412
|
+
letter-spacing: 0.02em;
|
|
194
413
|
}
|
|
195
414
|
|
|
196
415
|
.emptyNote {
|
|
@@ -205,8 +424,8 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
205
424
|
align-items: center;
|
|
206
425
|
gap: var(--dees-spacing-xs);
|
|
207
426
|
width: 100%;
|
|
208
|
-
margin
|
|
209
|
-
padding:
|
|
427
|
+
margin: var(--dees-spacing-md) 0 7px;
|
|
428
|
+
padding: var(--dees-spacing-xs) 6px;
|
|
210
429
|
border: 0;
|
|
211
430
|
border-radius: var(--dees-radius-md);
|
|
212
431
|
background: transparent;
|
|
@@ -246,15 +465,12 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
246
465
|
}
|
|
247
466
|
|
|
248
467
|
.dropLine {
|
|
249
|
-
|
|
250
|
-
margin: 0 var(--dees-spacing-sm);
|
|
251
|
-
border-radius: 1px;
|
|
252
|
-
background: var(--dees-color-accent-primary);
|
|
468
|
+
display: none;
|
|
253
469
|
}
|
|
254
470
|
|
|
255
471
|
.newGroupRow {
|
|
256
472
|
flex: 0 0 auto;
|
|
257
|
-
padding:
|
|
473
|
+
padding: 0 var(--dees-spacing-md) var(--dees-spacing-md);
|
|
258
474
|
}
|
|
259
475
|
|
|
260
476
|
.newGroupButton {
|
|
@@ -262,10 +478,12 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
262
478
|
align-items: center;
|
|
263
479
|
gap: var(--dees-spacing-xs);
|
|
264
480
|
width: 100%;
|
|
481
|
+
min-height: 34px;
|
|
482
|
+
justify-content: center;
|
|
265
483
|
padding: var(--dees-spacing-xs) var(--dees-spacing-sm);
|
|
266
484
|
border: 1px dashed var(--dees-color-border-default);
|
|
267
|
-
border-radius:
|
|
268
|
-
background: transparent;
|
|
485
|
+
border-radius: 11px;
|
|
486
|
+
background: color-mix(in srgb, var(--dees-color-bg-primary) 55%, transparent);
|
|
269
487
|
color: var(--dees-color-text-secondary);
|
|
270
488
|
cursor: pointer;
|
|
271
489
|
font-family: inherit;
|
|
@@ -276,6 +494,15 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
276
494
|
border-color: var(--dees-color-accent-primary);
|
|
277
495
|
color: var(--dees-color-text-primary);
|
|
278
496
|
}
|
|
497
|
+
|
|
498
|
+
@media (prefers-reduced-motion: reduce) {
|
|
499
|
+
.item,
|
|
500
|
+
.item.state-attention,
|
|
501
|
+
.expandToggle dees-icon {
|
|
502
|
+
animation: none;
|
|
503
|
+
transition: none;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
279
506
|
`,
|
|
280
507
|
];
|
|
281
508
|
|
|
@@ -291,15 +518,26 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
291
518
|
return ids;
|
|
292
519
|
}
|
|
293
520
|
|
|
294
|
-
/**
|
|
521
|
+
/** Pinned entries first, followed by explicitly ordered sortable sessions. */
|
|
295
522
|
private get ungroupedSessions(): IHarnessSessionMeta[] {
|
|
296
523
|
const grouped = this.groupedSessionIds;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
524
|
+
const available = this.sessions.filter((session) => !grouped.has(session.id));
|
|
525
|
+
const pinned = available.filter((session) => session.draggable === false);
|
|
526
|
+
const sortableById = new Map(
|
|
527
|
+
available.filter((session) => session.draggable !== false).map((session) => [session.id, session]),
|
|
528
|
+
);
|
|
529
|
+
const ordered: IHarnessSessionMeta[] = [];
|
|
530
|
+
for (const sessionId of this.ungroupedSessionIds) {
|
|
531
|
+
const session = sortableById.get(sessionId);
|
|
532
|
+
if (!session) continue;
|
|
533
|
+
ordered.push(session);
|
|
534
|
+
sortableById.delete(sessionId);
|
|
535
|
+
}
|
|
536
|
+
const newSessions = [...sortableById.values()].sort(
|
|
537
|
+
(left, right) =>
|
|
538
|
+
(right.updatedAt ?? right.createdAt ?? 0) - (left.updatedAt ?? left.createdAt ?? 0),
|
|
539
|
+
);
|
|
540
|
+
return [...pinned, ...ordered, ...newSessions];
|
|
303
541
|
}
|
|
304
542
|
|
|
305
543
|
private get filteredSessions(): IHarnessSessionMeta[] {
|
|
@@ -317,10 +555,45 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
317
555
|
this.shadowRoot?.querySelector<HTMLInputElement>('.searchRow input')?.focus();
|
|
318
556
|
}
|
|
319
557
|
|
|
558
|
+
public updated(changedProperties: Map<PropertyKey, unknown>): void {
|
|
559
|
+
super.updated(changedProperties);
|
|
560
|
+
if (this.draggingSessionId && !this.dragSourceElement?.isConnected) {
|
|
561
|
+
this.clearDragState();
|
|
562
|
+
}
|
|
563
|
+
if (!changedProperties.has('sessions')) return;
|
|
564
|
+
const previousSessions = changedProperties.get('sessions') as IHarnessSessionMeta[] | undefined;
|
|
565
|
+
const previousById = new Map((previousSessions ?? []).map((session) => [session.id, session]));
|
|
566
|
+
const knownIds = new Set(this.sessions.map((session) => session.id));
|
|
567
|
+
if (this.draggingSessionId && !knownIds.has(this.draggingSessionId)) {
|
|
568
|
+
this.clearDragState();
|
|
569
|
+
}
|
|
570
|
+
const nextExpanded = new Set(
|
|
571
|
+
[...this.expandedSessionIds].filter((sessionId) => knownIds.has(sessionId)),
|
|
572
|
+
);
|
|
573
|
+
for (const session of this.sessions) {
|
|
574
|
+
const previous = previousById.get(session.id);
|
|
575
|
+
if (previous && previous.working !== true && session.working === true) {
|
|
576
|
+
nextExpanded.add(session.id);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (
|
|
580
|
+
nextExpanded.size !== this.expandedSessionIds.size
|
|
581
|
+
|| [...nextExpanded].some((sessionId) => !this.expandedSessionIds.has(sessionId))
|
|
582
|
+
) {
|
|
583
|
+
this.expandedSessionIds = nextExpanded;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
public async disconnectedCallback(): Promise<void> {
|
|
588
|
+
this.clearDragState();
|
|
589
|
+
await super.disconnectedCallback();
|
|
590
|
+
}
|
|
591
|
+
|
|
320
592
|
public render(): TemplateResult {
|
|
321
593
|
const groupingActive = this.enableGrouping && !this.search.trim();
|
|
322
594
|
return html`
|
|
323
595
|
<div class="searchRow">
|
|
596
|
+
<dees-icon class="searchIcon" icon="lucide:Search" iconSize="13"></dees-icon>
|
|
324
597
|
<input
|
|
325
598
|
placeholder="Search conversations"
|
|
326
599
|
.value=${this.search}
|
|
@@ -367,7 +640,15 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
367
640
|
${this.sessions.length ? 'No conversations match this search.' : this.emptyText}
|
|
368
641
|
</div>`;
|
|
369
642
|
}
|
|
370
|
-
return html
|
|
643
|
+
return html`
|
|
644
|
+
<div class="section">
|
|
645
|
+
${repeat(
|
|
646
|
+
sessions,
|
|
647
|
+
(session) => session.id,
|
|
648
|
+
(session) => this.renderItem(session, null, -1),
|
|
649
|
+
)}
|
|
650
|
+
</div>
|
|
651
|
+
`;
|
|
371
652
|
}
|
|
372
653
|
|
|
373
654
|
private renderGroupedList(): TemplateResult {
|
|
@@ -383,7 +664,9 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
383
664
|
.filter((session): session is IHarnessSessionMeta => session !== undefined);
|
|
384
665
|
const collapsed = this.collapsedGroupIds.has(group.id);
|
|
385
666
|
const isHeaderDrop =
|
|
386
|
-
this.dropSlot?.groupId === group.id
|
|
667
|
+
this.dropSlot?.groupId === group.id
|
|
668
|
+
&& this.dropSlot.index === this.sortableSessions(members).length
|
|
669
|
+
&& collapsed;
|
|
387
670
|
return html`
|
|
388
671
|
<button
|
|
389
672
|
class="groupHeader ${collapsed ? 'collapsed' : ''} ${isHeaderDrop ? 'dropTarget' : ''}"
|
|
@@ -401,8 +684,16 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
401
684
|
new CustomEvent('harness-group-context', { detail, bubbles: true, composed: true }),
|
|
402
685
|
);
|
|
403
686
|
}}
|
|
404
|
-
@dragover=${(event: DragEvent) => this.handleDragOverSlot(
|
|
405
|
-
|
|
687
|
+
@dragover=${(event: DragEvent) => this.handleDragOverSlot(
|
|
688
|
+
event,
|
|
689
|
+
group.id,
|
|
690
|
+
this.sortableSessions(members).length,
|
|
691
|
+
)}
|
|
692
|
+
@drop=${(event: DragEvent) => this.handleDrop(
|
|
693
|
+
event,
|
|
694
|
+
group.id,
|
|
695
|
+
this.sortableSessions(members).length,
|
|
696
|
+
)}
|
|
406
697
|
>
|
|
407
698
|
<dees-icon class="chevron" icon="lucide:ChevronDown" iconSize="12"></dees-icon>
|
|
408
699
|
<span>${group.name}</span>
|
|
@@ -413,7 +704,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
413
704
|
: this.renderSection(members, group.id)}
|
|
414
705
|
`;
|
|
415
706
|
})}
|
|
416
|
-
${this.groups.length && ungrouped.length
|
|
707
|
+
${this.groups.length && (ungrouped.length || this.draggingSessionId)
|
|
417
708
|
? html`
|
|
418
709
|
<button
|
|
419
710
|
class="groupHeader"
|
|
@@ -436,18 +727,62 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
436
727
|
groupId: string | null,
|
|
437
728
|
): TemplateResult {
|
|
438
729
|
const slot = this.dropSlot;
|
|
730
|
+
const entries: TSectionRenderEntry[] = [];
|
|
731
|
+
let logicalIndex = 0;
|
|
732
|
+
for (const session of sessions) {
|
|
733
|
+
const isDragging = session.id === this.draggingSessionId;
|
|
734
|
+
const sortable = session.draggable !== false;
|
|
735
|
+
if (
|
|
736
|
+
slot
|
|
737
|
+
&& slot.groupId === groupId
|
|
738
|
+
&& slot.index === logicalIndex
|
|
739
|
+
&& !isDragging
|
|
740
|
+
&& sortable
|
|
741
|
+
) {
|
|
742
|
+
entries.push({ type: 'placeholder', key: `placeholder:${groupId ?? 'ungrouped'}` });
|
|
743
|
+
}
|
|
744
|
+
entries.push({
|
|
745
|
+
type: 'session',
|
|
746
|
+
key: `session:${session.id}`,
|
|
747
|
+
session,
|
|
748
|
+
logicalIndex: sortable ? logicalIndex : -1,
|
|
749
|
+
});
|
|
750
|
+
if (sortable && !isDragging) logicalIndex += 1;
|
|
751
|
+
}
|
|
752
|
+
if (slot && slot.groupId === groupId && slot.index === logicalIndex) {
|
|
753
|
+
entries.push({ type: 'placeholder', key: `placeholder:${groupId ?? 'ungrouped'}` });
|
|
754
|
+
}
|
|
755
|
+
const hasPlaceholder = entries.some((entry) => entry.type === 'placeholder');
|
|
439
756
|
return html`
|
|
440
|
-
|
|
441
|
-
(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
757
|
+
<div class="section" data-group-id=${groupId ?? ''}>
|
|
758
|
+
${repeat(
|
|
759
|
+
entries,
|
|
760
|
+
(entry) => entry.key,
|
|
761
|
+
(entry) => entry.type === 'placeholder'
|
|
762
|
+
? this.renderDropPlaceholder(groupId, slot?.index ?? 0)
|
|
763
|
+
: this.renderItem(entry.session, groupId, entry.logicalIndex),
|
|
764
|
+
)}
|
|
765
|
+
${this.draggingSessionId && !hasPlaceholder && this.sortableSessions(sessions).length === 0
|
|
766
|
+
? html`
|
|
767
|
+
<div
|
|
768
|
+
class="emptyDropZone"
|
|
769
|
+
@dragover=${(event: DragEvent) => this.handleDragOverSlot(event, groupId, 0)}
|
|
770
|
+
@drop=${(event: DragEvent) => this.handleDrop(event, groupId, 0)}
|
|
771
|
+
></div>
|
|
772
|
+
`
|
|
773
|
+
: ''}
|
|
774
|
+
</div>
|
|
775
|
+
`;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
private renderDropPlaceholder(groupId: string | null, index: number): TemplateResult {
|
|
779
|
+
return html`
|
|
780
|
+
<div
|
|
781
|
+
class="dropPlaceholder"
|
|
782
|
+
style=${`height: ${Math.max(46, this.draggedItemHeight)}px`}
|
|
783
|
+
@dragover=${(event: DragEvent) => this.handleDragOverSlot(event, groupId, index)}
|
|
784
|
+
@drop=${(event: DragEvent) => this.handleDrop(event, groupId, index)}
|
|
785
|
+
></div>
|
|
451
786
|
`;
|
|
452
787
|
}
|
|
453
788
|
|
|
@@ -456,20 +791,30 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
456
791
|
groupId: string | null,
|
|
457
792
|
index: number,
|
|
458
793
|
): TemplateResult {
|
|
459
|
-
const draggable =
|
|
794
|
+
const draggable = (
|
|
795
|
+
this.enableGrouping
|
|
796
|
+
&& session.draggable !== false
|
|
797
|
+
&& index >= 0
|
|
798
|
+
&& !globalThis.matchMedia?.('(pointer: coarse)').matches
|
|
799
|
+
);
|
|
800
|
+
const expanded = this.expandedSessionIds.has(session.id);
|
|
801
|
+
const state = session.state ?? 'normal';
|
|
802
|
+
const detailsId = `session-details-${session.id}`;
|
|
460
803
|
return html`
|
|
461
|
-
<
|
|
462
|
-
class="item ${
|
|
804
|
+
<article
|
|
805
|
+
class="item state-${state} ${expanded ? 'expanded' : ''} ${
|
|
806
|
+
session.id === this.selectedSessionId ? 'selected' : ''
|
|
807
|
+
} ${
|
|
463
808
|
this.draggingSessionId === session.id ? 'dragging' : ''
|
|
464
809
|
}"
|
|
465
|
-
|
|
466
|
-
draggable=${draggable
|
|
467
|
-
@
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (event.key === 'Enter') this.emit('harness-session-open', session);
|
|
810
|
+
data-session-id=${session.id}
|
|
811
|
+
.draggable=${draggable}
|
|
812
|
+
@dblclick=${(event: MouseEvent) => {
|
|
813
|
+
if (this.eventTargetsExpandToggle(event)) return;
|
|
814
|
+
this.emit('harness-session-open', session);
|
|
471
815
|
}}
|
|
472
816
|
@contextmenu=${(event: MouseEvent) => {
|
|
817
|
+
if (this.eventTargetsExpandToggle(event)) return;
|
|
473
818
|
event.preventDefault();
|
|
474
819
|
const detail: IHarnessSessionContextDetail = {
|
|
475
820
|
session,
|
|
@@ -482,43 +827,102 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
482
827
|
);
|
|
483
828
|
}}
|
|
484
829
|
@dragstart=${(event: DragEvent) => {
|
|
485
|
-
|
|
486
|
-
this.draggingSessionId = session.id;
|
|
487
|
-
event.dataTransfer?.setData('text/plain', session.id);
|
|
488
|
-
if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move';
|
|
489
|
-
}}
|
|
490
|
-
@dragend=${() => {
|
|
491
|
-
this.draggingSessionId = '';
|
|
492
|
-
this.dropSlot = undefined;
|
|
830
|
+
this.handleDragStart(event, session, groupId, index, draggable);
|
|
493
831
|
}}
|
|
832
|
+
@dragend=${() => this.clearDragState()}
|
|
494
833
|
@dragover=${(event: DragEvent) => {
|
|
495
|
-
if (!this.draggingSessionId || index < 0) return;
|
|
834
|
+
if (!this.draggingSessionId || index < 0 || session.id === this.draggingSessionId) return;
|
|
496
835
|
event.preventDefault();
|
|
497
836
|
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
498
837
|
const before = event.clientY < rect.top + rect.height / 2;
|
|
499
838
|
this.setDropSlot(groupId, before ? index : index + 1);
|
|
500
839
|
}}
|
|
501
840
|
@drop=${(event: DragEvent) => {
|
|
502
|
-
if (index < 0) return;
|
|
841
|
+
if (index < 0 || session.id === this.draggingSessionId) return;
|
|
503
842
|
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
504
843
|
const before = event.clientY < rect.top + rect.height / 2;
|
|
505
844
|
this.handleDrop(event, groupId, before ? index : index + 1);
|
|
506
845
|
}}
|
|
507
846
|
>
|
|
508
|
-
<
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
847
|
+
<div class="itemHeader">
|
|
848
|
+
<button
|
|
849
|
+
class="itemPrimary"
|
|
850
|
+
type="button"
|
|
851
|
+
aria-current=${session.id === this.selectedSessionId ? 'true' : 'false'}
|
|
852
|
+
@click=${() => this.emit('harness-session-select', session)}
|
|
853
|
+
@keydown=${(event: KeyboardEvent) => {
|
|
854
|
+
if (event.key !== 'Enter') return;
|
|
855
|
+
event.preventDefault();
|
|
856
|
+
this.emit('harness-session-open', session);
|
|
857
|
+
}}
|
|
858
|
+
>
|
|
859
|
+
<span class="stateDot"></span>
|
|
860
|
+
<span class="itemCopy">
|
|
861
|
+
<span class="itemTitle">
|
|
862
|
+
${session.icon
|
|
863
|
+
? html`<dees-icon class="itemIcon" icon=${session.icon} iconSize="13"></dees-icon>`
|
|
864
|
+
: ''}
|
|
865
|
+
<span class="titleText">${session.title || session.id}</span>
|
|
866
|
+
</span>
|
|
867
|
+
<span class="itemTime">${harnessFormatRelativeTime(session.updatedAt ?? session.createdAt)}</span>
|
|
868
|
+
<span class="visuallyHidden">Status: ${this.sessionStateLabel(state)}</span>
|
|
869
|
+
</span>
|
|
870
|
+
</button>
|
|
871
|
+
<button
|
|
872
|
+
class="expandToggle"
|
|
873
|
+
type="button"
|
|
874
|
+
aria-expanded=${expanded ? 'true' : 'false'}
|
|
875
|
+
aria-controls=${detailsId}
|
|
876
|
+
title=${expanded ? 'Collapse conversation card' : 'Expand conversation card'}
|
|
877
|
+
@click=${(event: MouseEvent) => {
|
|
878
|
+
event.stopPropagation();
|
|
879
|
+
this.toggleSessionExpanded(session.id);
|
|
880
|
+
}}
|
|
881
|
+
>
|
|
882
|
+
<dees-icon icon="lucide:ChevronDown" iconSize="15"></dees-icon>
|
|
883
|
+
</button>
|
|
884
|
+
</div>
|
|
885
|
+
${expanded
|
|
886
|
+
? html`
|
|
887
|
+
<div class="itemDetails" id=${detailsId}>
|
|
888
|
+
<div class="stateLine">
|
|
889
|
+
<span class="stateDot"></span>
|
|
890
|
+
<span>${this.sessionStateLabel(state)}</span>
|
|
891
|
+
</div>
|
|
892
|
+
${session.preview ? html`<div class="itemPreview">${session.preview}</div>` : ''}
|
|
893
|
+
<div class="itemMeta">
|
|
894
|
+
${session.messageCount ? `${session.messageCount} messages · ` : ''}${session.id}
|
|
895
|
+
</div>
|
|
896
|
+
</div>
|
|
897
|
+
`
|
|
517
898
|
: ''}
|
|
518
|
-
</
|
|
899
|
+
</article>
|
|
519
900
|
`;
|
|
520
901
|
}
|
|
521
902
|
|
|
903
|
+
private eventTargetsExpandToggle(event: Event): boolean {
|
|
904
|
+
return event.composedPath().some(
|
|
905
|
+
(target) => target instanceof HTMLElement && target.classList.contains('expandToggle'),
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
private sessionStateLabel(state: NonNullable<IHarnessSessionMeta['state']>): string {
|
|
910
|
+
switch (state) {
|
|
911
|
+
case 'working': return 'Working';
|
|
912
|
+
case 'finished': return 'Finished';
|
|
913
|
+
case 'attention': return 'Needs attention';
|
|
914
|
+
case 'error': return 'Error';
|
|
915
|
+
default: return 'Idle';
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
private toggleSessionExpanded(sessionIdArg: string): void {
|
|
920
|
+
const next = new Set(this.expandedSessionIds);
|
|
921
|
+
if (next.has(sessionIdArg)) next.delete(sessionIdArg);
|
|
922
|
+
else next.add(sessionIdArg);
|
|
923
|
+
this.expandedSessionIds = next;
|
|
924
|
+
}
|
|
925
|
+
|
|
522
926
|
private toggleGroupCollapsed(groupIdArg: string): void {
|
|
523
927
|
const next = new Set(this.collapsedGroupIds);
|
|
524
928
|
if (next.has(groupIdArg)) {
|
|
@@ -529,15 +933,62 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
529
933
|
this.collapsedGroupIds = next;
|
|
530
934
|
}
|
|
531
935
|
|
|
936
|
+
private sortableSessions(
|
|
937
|
+
sessionsArg: IHarnessSessionMeta[],
|
|
938
|
+
excludeDraggingArg = true,
|
|
939
|
+
): IHarnessSessionMeta[] {
|
|
940
|
+
return sessionsArg.filter((session) => (
|
|
941
|
+
session.draggable !== false
|
|
942
|
+
&& (!excludeDraggingArg || session.id !== this.draggingSessionId)
|
|
943
|
+
));
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
private sessionsForGroup(groupIdArg: string | null): IHarnessSessionMeta[] {
|
|
947
|
+
if (groupIdArg === null) return this.ungroupedSessions;
|
|
948
|
+
const group = this.groups.find((candidate) => candidate.id === groupIdArg);
|
|
949
|
+
if (!group) return [];
|
|
950
|
+
const byId = this.sessionsById;
|
|
951
|
+
return group.sessionIds
|
|
952
|
+
.map((sessionId) => byId.get(sessionId))
|
|
953
|
+
.filter((session): session is IHarnessSessionMeta => session !== undefined);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
private handleDragStart(
|
|
957
|
+
event: DragEvent,
|
|
958
|
+
session: IHarnessSessionMeta,
|
|
959
|
+
groupId: string | null,
|
|
960
|
+
logicalIndex: number,
|
|
961
|
+
draggable: boolean,
|
|
962
|
+
): void {
|
|
963
|
+
if (!draggable || logicalIndex < 0) {
|
|
964
|
+
event.preventDefault();
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const item = event.currentTarget as HTMLElement;
|
|
968
|
+
this.reflowGeneration += 1;
|
|
969
|
+
this.cancelReflowAnimations();
|
|
970
|
+
this.draggedItemHeight = Math.ceil(item.getBoundingClientRect().height);
|
|
971
|
+
this.dragSourceElement = item;
|
|
972
|
+
this.draggingSessionId = session.id;
|
|
973
|
+
const remainingCount = this.sortableSessions(this.sessionsForGroup(groupId)).length;
|
|
974
|
+
this.dropSlot = { groupId, index: Math.min(logicalIndex, remainingCount) };
|
|
975
|
+
event.dataTransfer?.setData('text/plain', session.id);
|
|
976
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move';
|
|
977
|
+
}
|
|
978
|
+
|
|
532
979
|
private setDropSlot(groupIdArg: string | null, indexArg: number): void {
|
|
533
980
|
const current = this.dropSlot;
|
|
534
981
|
if (current && current.groupId === groupIdArg && current.index === indexArg) return;
|
|
982
|
+
const previousRects = this.captureCardRects();
|
|
983
|
+
const generation = ++this.reflowGeneration;
|
|
535
984
|
this.dropSlot = { groupId: groupIdArg, index: indexArg };
|
|
985
|
+
void this.animateCardReflow(previousRects, generation);
|
|
536
986
|
}
|
|
537
987
|
|
|
538
988
|
private handleDragOverSlot(event: DragEvent, groupIdArg: string | null, indexArg: number): void {
|
|
539
989
|
if (!this.draggingSessionId) return;
|
|
540
990
|
event.preventDefault();
|
|
991
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
|
|
541
992
|
this.setDropSlot(groupIdArg, indexArg);
|
|
542
993
|
}
|
|
543
994
|
|
|
@@ -545,19 +996,88 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
545
996
|
event.preventDefault();
|
|
546
997
|
event.stopPropagation();
|
|
547
998
|
const sessionId = this.draggingSessionId || event.dataTransfer?.getData('text/plain') || '';
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
999
|
+
if (!sessionId) {
|
|
1000
|
+
this.clearDragState();
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
const targetSessions = this.sortableSessions(this.sessionsForGroup(groupIdArg));
|
|
1004
|
+
const beforeSessionId = targetSessions[Math.max(0, indexArg)]?.id;
|
|
1005
|
+
const sourceGroupId = this.groups.find((group) => group.sessionIds.includes(sessionId))?.id ?? null;
|
|
1006
|
+
const sourceSessions = this.sortableSessions(this.sessionsForGroup(sourceGroupId), false);
|
|
1007
|
+
const sourceIndex = sourceSessions.findIndex((session) => session.id === sessionId);
|
|
1008
|
+
const compatibilityIndex = (
|
|
1009
|
+
sourceGroupId === groupIdArg
|
|
1010
|
+
&& sourceIndex >= 0
|
|
1011
|
+
&& indexArg > sourceIndex
|
|
1012
|
+
) ? indexArg + 1 : indexArg;
|
|
551
1013
|
const detail: IHarnessSessionMoveDetail = {
|
|
552
1014
|
sessionId,
|
|
553
1015
|
groupId: groupIdArg,
|
|
554
|
-
index: Math.max(0,
|
|
1016
|
+
index: Math.max(0, compatibilityIndex),
|
|
1017
|
+
...(beforeSessionId ? { beforeSessionId } : {}),
|
|
555
1018
|
};
|
|
1019
|
+
this.clearDragState();
|
|
556
1020
|
this.dispatchEvent(
|
|
557
1021
|
new CustomEvent('harness-session-move', { detail, bubbles: true, composed: true }),
|
|
558
1022
|
);
|
|
559
1023
|
}
|
|
560
1024
|
|
|
1025
|
+
private captureCardRects(): Map<string, DOMRect> {
|
|
1026
|
+
const result = new Map<string, DOMRect>();
|
|
1027
|
+
for (const item of this.shadowRoot?.querySelectorAll<HTMLElement>('.item[data-session-id]') ?? []) {
|
|
1028
|
+
if (item.dataset.sessionId === this.draggingSessionId) continue;
|
|
1029
|
+
result.set(item.dataset.sessionId ?? '', item.getBoundingClientRect());
|
|
1030
|
+
}
|
|
1031
|
+
return result;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
private async animateCardReflow(
|
|
1035
|
+
previousRectsArg: Map<string, DOMRect>,
|
|
1036
|
+
generationArg: number,
|
|
1037
|
+
): Promise<void> {
|
|
1038
|
+
await this.updateComplete;
|
|
1039
|
+
if (
|
|
1040
|
+
generationArg !== this.reflowGeneration
|
|
1041
|
+
|| !this.isConnected
|
|
1042
|
+
|| globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches
|
|
1043
|
+
) return;
|
|
1044
|
+
this.cancelReflowAnimations();
|
|
1045
|
+
for (const item of this.shadowRoot?.querySelectorAll<HTMLElement>('.item[data-session-id]') ?? []) {
|
|
1046
|
+
const sessionId = item.dataset.sessionId ?? '';
|
|
1047
|
+
if (!sessionId || sessionId === this.draggingSessionId) continue;
|
|
1048
|
+
const previous = previousRectsArg.get(sessionId);
|
|
1049
|
+
if (!previous) continue;
|
|
1050
|
+
const current = item.getBoundingClientRect();
|
|
1051
|
+
const deltaY = previous.top - current.top;
|
|
1052
|
+
if (Math.abs(deltaY) < 0.5) continue;
|
|
1053
|
+
const animation = item.animate(
|
|
1054
|
+
[
|
|
1055
|
+
{ transform: `translateY(${deltaY}px)` },
|
|
1056
|
+
{ transform: 'translateY(0)' },
|
|
1057
|
+
],
|
|
1058
|
+
{ duration: 180, easing: 'cubic-bezier(0.22, 1, 0.36, 1)' },
|
|
1059
|
+
);
|
|
1060
|
+
this.reflowAnimations.add(animation);
|
|
1061
|
+
void animation.finished.catch(() => undefined).finally(() => {
|
|
1062
|
+
this.reflowAnimations.delete(animation);
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
private cancelReflowAnimations(): void {
|
|
1068
|
+
for (const animation of this.reflowAnimations) animation.cancel();
|
|
1069
|
+
this.reflowAnimations.clear();
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
private clearDragState(): void {
|
|
1073
|
+
this.reflowGeneration += 1;
|
|
1074
|
+
this.cancelReflowAnimations();
|
|
1075
|
+
this.draggingSessionId = '';
|
|
1076
|
+
this.draggedItemHeight = 0;
|
|
1077
|
+
this.dropSlot = undefined;
|
|
1078
|
+
this.dragSourceElement = undefined;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
561
1081
|
private emit(name: string, session: IHarnessSessionMeta): void {
|
|
562
1082
|
this.dispatchEvent(new CustomEvent(name, { detail: { session }, bubbles: true, composed: true }));
|
|
563
1083
|
}
|