@design.estate/dees-catalog 6.7.0 → 6.8.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 +1153 -193
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.d.ts +2 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.js +57 -17
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.d.ts +92 -6
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +921 -49
- package/dist_ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.d.ts +5 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.js +42 -7
- package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.d.ts +6 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.js +53 -14
- package/dist_ts_web/elements/00group-harness/dees-harness-reasoning/dees-harness-reasoning.d.ts +2 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-reasoning/dees-harness-reasoning.js +13 -3
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +31 -13
- package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.js +9 -3
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.d.ts +6 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.js +135 -13
- package/dist_ts_web/elements/00group-harness/harness.styles.js +16 -13
- package/dist_ts_web/elements/00group-harness/interfaces.d.ts +30 -0
- package/dist_ts_web/elements/00group-harness/toolregistry.d.ts +2 -0
- package/dist_ts_web/elements/00group-harness/toolregistry.js +7 -1
- package/package.json +2 -2
- package/readme.md +4 -2
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.ts +53 -15
- package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +943 -51
- package/ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.ts +37 -8
- package/ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.ts +40 -12
- package/ts_web/elements/00group-harness/dees-harness-reasoning/dees-harness-reasoning.ts +6 -2
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +30 -12
- package/ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.ts +8 -2
- package/ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.ts +119 -11
- package/ts_web/elements/00group-harness/harness.styles.ts +15 -12
- package/ts_web/elements/00group-harness/interfaces.ts +34 -0
- package/ts_web/elements/00group-harness/toolregistry.ts +7 -0
package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts
CHANGED
|
@@ -13,9 +13,13 @@ import {
|
|
|
13
13
|
import { themeDefaultStyles } from '../../00theme.js';
|
|
14
14
|
import type {
|
|
15
15
|
IHarnessMessage,
|
|
16
|
+
IHarnessMessageViewState,
|
|
16
17
|
IHarnessPermissionRequest,
|
|
18
|
+
IHarnessPermissionViewState,
|
|
17
19
|
IHarnessQuestionRequest,
|
|
20
|
+
IHarnessQuestionViewState,
|
|
18
21
|
IHarnessStatus,
|
|
22
|
+
IHarnessTranscriptViewState,
|
|
19
23
|
THarnessMessageStreamDelta,
|
|
20
24
|
THarnessStreamDelta,
|
|
21
25
|
} from '../interfaces.js';
|
|
@@ -36,6 +40,10 @@ declare global {
|
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
const pinThresholdPx = 48;
|
|
43
|
+
const virtualizationThreshold = 40;
|
|
44
|
+
const virtualOverscanPx = 800;
|
|
45
|
+
const virtualGapPx = 8;
|
|
46
|
+
const maximumToolMessagesPerGroup = 4;
|
|
39
47
|
|
|
40
48
|
type TMessageTimelineItem = {
|
|
41
49
|
kind: 'message';
|
|
@@ -73,11 +81,10 @@ interface IToolGroupTimelineItem {
|
|
|
73
81
|
type TTimelineRenderItem = Exclude<TTimelineItem, TMessageTimelineItem> | TMessageTimelineItem | IToolGroupTimelineItem;
|
|
74
82
|
|
|
75
83
|
/**
|
|
76
|
-
* Scrollable, streaming-safe message log.
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* "jump to latest" chip instead of yanking the scroll position.
|
|
84
|
+
* Scrollable, streaming-safe message log. Short transcripts keep stable keyed
|
|
85
|
+
* DOM; long transcripts mount a bounded, measured window and retain row state
|
|
86
|
+
* by id. `applyDelta()` targets a mounted row or updates its offscreen model.
|
|
87
|
+
* Auto-follow stays at the bottom until the user intentionally scrolls away.
|
|
81
88
|
*/
|
|
82
89
|
@customElement('dees-harness-message-list')
|
|
83
90
|
export class DeesHarnessMessageList extends DeesElement {
|
|
@@ -114,6 +121,9 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
114
121
|
@property({ attribute: false })
|
|
115
122
|
accessor toolRegistry: DeesHarnessToolRegistry = DeesHarnessToolRegistry.default;
|
|
116
123
|
|
|
124
|
+
@property({ attribute: false })
|
|
125
|
+
accessor viewState: IHarnessTranscriptViewState | undefined = undefined;
|
|
126
|
+
|
|
117
127
|
/** Nested transcripts set this false to prevent recursive inline streams. */
|
|
118
128
|
@property({ type: Boolean })
|
|
119
129
|
accessor allowSubtaskStreams: boolean = true;
|
|
@@ -145,6 +155,47 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
145
155
|
private readonly toolGroupIdByMessageId = new Map<string, string>();
|
|
146
156
|
private nextToolGroupId = 1;
|
|
147
157
|
private timelineRenderItems: TTimelineRenderItem[] = [];
|
|
158
|
+
private timelineRegistryRevision = -1;
|
|
159
|
+
private readonly knownContentKeys = new Set<string>();
|
|
160
|
+
private currentNewContentKeys = new Set<string>();
|
|
161
|
+
private readonly localViewState: IHarnessTranscriptViewState = {
|
|
162
|
+
messages: {},
|
|
163
|
+
permissions: {},
|
|
164
|
+
questions: {},
|
|
165
|
+
};
|
|
166
|
+
private readonly measuredVirtualHeights = new Map<string, number>();
|
|
167
|
+
private timelineIndexByKey = new Map<string, number>();
|
|
168
|
+
private virtualOffsets: number[] = [];
|
|
169
|
+
private virtualTotalHeight = 0;
|
|
170
|
+
private virtualStartIndex = 0;
|
|
171
|
+
private virtualEndIndex = 0;
|
|
172
|
+
private retainedVirtualKeys = new Set<string>();
|
|
173
|
+
private selectionGestureKeys = new Set<string>();
|
|
174
|
+
private selectionGestureActive = false;
|
|
175
|
+
private selectionGestureStartOwner: Element | undefined;
|
|
176
|
+
private selectionGestureCrossesOwners = false;
|
|
177
|
+
private selectionGestureSnapshot: {
|
|
178
|
+
anchorNode: Node | null;
|
|
179
|
+
anchorOffset: number;
|
|
180
|
+
focusNode: Node | null;
|
|
181
|
+
focusOffset: number;
|
|
182
|
+
rangeCount: number;
|
|
183
|
+
} | undefined;
|
|
184
|
+
private explicitRetainedVirtualKey: string | undefined;
|
|
185
|
+
private virtualRowResizeObserver: ResizeObserver | null = null;
|
|
186
|
+
private virtualViewportResizeObserver: ResizeObserver | null = null;
|
|
187
|
+
private virtualMeasureFrame: number | null = null;
|
|
188
|
+
private virtualRangeFrame: number | null = null;
|
|
189
|
+
private lastVirtualViewportWidth = 0;
|
|
190
|
+
private pendingVirtualAnchor: { key: string; offset: number } | undefined;
|
|
191
|
+
private pendingFullAnchor: { key: string; offset: number } | undefined;
|
|
192
|
+
private measurementAnchor: { key: string; offset: number } | undefined;
|
|
193
|
+
private pendingVirtualRepin = false;
|
|
194
|
+
private virtualizationEnabled = false;
|
|
195
|
+
private virtualizationEnablePending = false;
|
|
196
|
+
private virtualizationDisablePending = false;
|
|
197
|
+
private virtualizationActivationFrame: number | null = null;
|
|
198
|
+
private selectionChangeListening = false;
|
|
148
199
|
|
|
149
200
|
public static styles = [
|
|
150
201
|
themeDefaultStyles,
|
|
@@ -162,6 +213,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
162
213
|
.scroll {
|
|
163
214
|
height: 100%;
|
|
164
215
|
overflow-y: auto;
|
|
216
|
+
overflow-anchor: none;
|
|
165
217
|
overscroll-behavior: contain;
|
|
166
218
|
scrollbar-width: thin;
|
|
167
219
|
scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
|
|
@@ -230,6 +282,19 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
230
282
|
grid-column: 1 / -1;
|
|
231
283
|
}
|
|
232
284
|
|
|
285
|
+
.virtualStack {
|
|
286
|
+
position: relative;
|
|
287
|
+
flex: 0 0 auto;
|
|
288
|
+
min-width: 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.virtualItem {
|
|
292
|
+
position: absolute;
|
|
293
|
+
inset: 0 0 auto;
|
|
294
|
+
min-width: 0;
|
|
295
|
+
transform: translate3d(0, var(--harness-virtual-offset), 0);
|
|
296
|
+
}
|
|
297
|
+
|
|
233
298
|
.toolGroup {
|
|
234
299
|
min-width: 0;
|
|
235
300
|
}
|
|
@@ -423,7 +488,21 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
423
488
|
|
|
424
489
|
public willUpdate(changedProperties: Map<PropertyKey, unknown>): void {
|
|
425
490
|
super.willUpdate(changedProperties);
|
|
426
|
-
|
|
491
|
+
if (changedProperties.has('viewState') && this.viewState) {
|
|
492
|
+
this.clearTranscriptViewState(this.localViewState);
|
|
493
|
+
}
|
|
494
|
+
const registryChanged = this.timelineRegistryRevision !== this.toolRegistry.revision;
|
|
495
|
+
if (
|
|
496
|
+
this.timelineRegistryRevision < 0
|
|
497
|
+
|| changedProperties.has('messages')
|
|
498
|
+
|| changedProperties.has('permissions')
|
|
499
|
+
|| changedProperties.has('questions')
|
|
500
|
+
|| changedProperties.has('toolRegistry')
|
|
501
|
+
|| changedProperties.has('viewState')
|
|
502
|
+
|| registryChanged
|
|
503
|
+
) {
|
|
504
|
+
this.rebuildTimeline();
|
|
505
|
+
}
|
|
427
506
|
}
|
|
428
507
|
|
|
429
508
|
private appendToolRun(
|
|
@@ -432,20 +511,231 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
432
511
|
layoutArg: THarnessToolLayout,
|
|
433
512
|
usedGroupIdsArg: Set<string>,
|
|
434
513
|
): void {
|
|
435
|
-
|
|
436
|
-
.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
514
|
+
for (let offset = 0; offset < runArg.length; offset += maximumToolMessagesPerGroup) {
|
|
515
|
+
const messages = runArg.slice(offset, offset + maximumToolMessagesPerGroup);
|
|
516
|
+
const groupId = messages
|
|
517
|
+
.map((item) => this.toolGroupIdByMessageId.get(item.message.id))
|
|
518
|
+
.find((candidate): candidate is string => candidate !== undefined && !usedGroupIdsArg.has(candidate))
|
|
519
|
+
?? `tool-group-${this.nextToolGroupId++}`;
|
|
520
|
+
usedGroupIdsArg.add(groupId);
|
|
521
|
+
for (const item of messages) {
|
|
522
|
+
this.toolGroupIdByMessageId.set(item.message.id, groupId);
|
|
523
|
+
}
|
|
524
|
+
targetArg.push({
|
|
525
|
+
kind: 'tool-group',
|
|
526
|
+
key: groupId,
|
|
527
|
+
layout: layoutArg,
|
|
528
|
+
messages,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
private rebuildTimeline(): void {
|
|
534
|
+
const container = this.scrollContainer;
|
|
535
|
+
const preserveAnchor = this.userUnpinned || !this.autoFollow;
|
|
536
|
+
const anchor = container && preserveAnchor
|
|
537
|
+
? this.captureCurrentAnchor(container)
|
|
538
|
+
: undefined;
|
|
539
|
+
const nextItems = this.buildTimelineRenderItems();
|
|
540
|
+
const retainedKeys = new Set(nextItems.map((item) => item.key));
|
|
541
|
+
const contentKeys = [
|
|
542
|
+
...this.messages.map((message) => `message:${message.id}`),
|
|
543
|
+
...this.permissions.map((request) => `permission:${request.id}`),
|
|
544
|
+
...this.questions.map((request) => `question:${request.id}`),
|
|
545
|
+
];
|
|
546
|
+
this.currentNewContentKeys = new Set(
|
|
547
|
+
contentKeys.filter((key) => !this.knownContentKeys.has(key)),
|
|
548
|
+
);
|
|
549
|
+
this.knownContentKeys.clear();
|
|
550
|
+
for (const key of contentKeys) this.knownContentKeys.add(key);
|
|
551
|
+
for (const key of this.measuredVirtualHeights.keys()) {
|
|
552
|
+
if (!retainedKeys.has(key)) this.measuredVirtualHeights.delete(key);
|
|
553
|
+
}
|
|
554
|
+
if (this.explicitRetainedVirtualKey && !retainedKeys.has(this.explicitRetainedVirtualKey)) {
|
|
555
|
+
this.explicitRetainedVirtualKey = undefined;
|
|
556
|
+
}
|
|
557
|
+
for (const key of this.retainedVirtualKeys) {
|
|
558
|
+
if (!retainedKeys.has(key)) this.retainedVirtualKeys.delete(key);
|
|
559
|
+
}
|
|
560
|
+
const messageIds = new Set(this.messages.map((message) => message.id));
|
|
561
|
+
const permissionIds = new Set(this.permissions.map((request) => request.id));
|
|
562
|
+
const questionIds = new Set(this.questions.map((request) => request.id));
|
|
563
|
+
const effectiveViewState = this.effectiveViewState;
|
|
564
|
+
for (const key of Object.keys(effectiveViewState.messages)) {
|
|
565
|
+
if (!messageIds.has(key)) delete effectiveViewState.messages[key];
|
|
566
|
+
}
|
|
567
|
+
for (const key of Object.keys(effectiveViewState.permissions)) {
|
|
568
|
+
if (!permissionIds.has(key)) delete effectiveViewState.permissions[key];
|
|
569
|
+
}
|
|
570
|
+
for (const key of Object.keys(effectiveViewState.questions)) {
|
|
571
|
+
if (!questionIds.has(key)) delete effectiveViewState.questions[key];
|
|
572
|
+
}
|
|
573
|
+
for (const message of this.messages) effectiveViewState.messages[message.id] ??= {};
|
|
574
|
+
for (const request of this.permissions) effectiveViewState.permissions[request.id] ??= {};
|
|
575
|
+
for (const request of this.questions) effectiveViewState.questions[request.id] ??= {};
|
|
576
|
+
for (const message of this.messages) this.syncSubtaskViewState(message);
|
|
577
|
+
this.timelineRenderItems = nextItems;
|
|
578
|
+
this.timelineIndexByKey = new Map(nextItems.map((item, index) => [item.key, index]));
|
|
579
|
+
this.timelineRegistryRevision = this.toolRegistry.revision;
|
|
580
|
+
const shouldVirtualize = contentKeys.length > virtualizationThreshold;
|
|
581
|
+
if (!this.virtualizationEnabled && shouldVirtualize) {
|
|
582
|
+
this.virtualizationDisablePending = false;
|
|
583
|
+
if (this.activeTimelineKey()) {
|
|
584
|
+
this.virtualizationEnablePending = true;
|
|
585
|
+
this.connectSelectionChangeListener();
|
|
586
|
+
} else {
|
|
587
|
+
this.captureFullTimelineMeasurements();
|
|
588
|
+
this.virtualizationEnabled = true;
|
|
589
|
+
this.virtualizationEnablePending = false;
|
|
590
|
+
this.disconnectSelectionChangeListener();
|
|
591
|
+
}
|
|
592
|
+
} else if (this.virtualizationEnabled && !shouldVirtualize) {
|
|
593
|
+
this.virtualizationEnablePending = false;
|
|
594
|
+
if (this.activeTimelineKey()) {
|
|
595
|
+
this.virtualizationDisablePending = true;
|
|
596
|
+
this.connectSelectionChangeListener();
|
|
597
|
+
} else {
|
|
598
|
+
this.virtualizationEnabled = false;
|
|
599
|
+
this.virtualizationDisablePending = false;
|
|
600
|
+
this.retainedVirtualKeys.clear();
|
|
601
|
+
this.disconnectSelectionChangeListener();
|
|
602
|
+
}
|
|
603
|
+
} else {
|
|
604
|
+
this.virtualizationEnablePending = false;
|
|
605
|
+
this.virtualizationDisablePending = false;
|
|
606
|
+
if (this.activeSelectionKeys().length === 0) this.disconnectSelectionChangeListener();
|
|
607
|
+
}
|
|
608
|
+
this.recomputeVirtualOffsets();
|
|
609
|
+
if (anchor && container) {
|
|
610
|
+
if (this.virtualizationActive) {
|
|
611
|
+
this.pendingVirtualAnchor = anchor;
|
|
612
|
+
this.measurementAnchor = anchor;
|
|
613
|
+
this.setVirtualRangeAround(anchor.key);
|
|
614
|
+
} else {
|
|
615
|
+
this.pendingVirtualAnchor = undefined;
|
|
616
|
+
this.pendingFullAnchor = anchor;
|
|
617
|
+
this.setInitialVirtualRange();
|
|
618
|
+
}
|
|
619
|
+
} else {
|
|
620
|
+
this.measurementAnchor = undefined;
|
|
621
|
+
this.setInitialVirtualRange();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
private pruneViewState(
|
|
626
|
+
stateArg: IHarnessTranscriptViewState,
|
|
627
|
+
messageIdsArg: string[],
|
|
628
|
+
permissionIdsArg: string[],
|
|
629
|
+
questionIdsArg: string[],
|
|
630
|
+
): void {
|
|
631
|
+
const messageIds = new Set(messageIdsArg);
|
|
632
|
+
const permissionIds = new Set(permissionIdsArg);
|
|
633
|
+
const questionIds = new Set(questionIdsArg);
|
|
634
|
+
for (const key of Object.keys(stateArg.messages)) if (!messageIds.has(key)) delete stateArg.messages[key];
|
|
635
|
+
for (const key of Object.keys(stateArg.permissions)) if (!permissionIds.has(key)) delete stateArg.permissions[key];
|
|
636
|
+
for (const key of Object.keys(stateArg.questions)) if (!questionIds.has(key)) delete stateArg.questions[key];
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
private clearTranscriptViewState(stateArg: IHarnessTranscriptViewState): void {
|
|
640
|
+
for (const key of Object.keys(stateArg.messages)) delete stateArg.messages[key];
|
|
641
|
+
for (const key of Object.keys(stateArg.permissions)) delete stateArg.permissions[key];
|
|
642
|
+
for (const key of Object.keys(stateArg.questions)) delete stateArg.questions[key];
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private syncSubtaskViewState(messageArg: IHarnessMessage): void {
|
|
646
|
+
const messageState = this.effectiveViewState.messages[messageArg.id];
|
|
647
|
+
if (!messageState) return;
|
|
648
|
+
const subtask = messageArg.toolCall?.subtask;
|
|
649
|
+
if (
|
|
650
|
+
!this.allowSubtaskStreams
|
|
651
|
+
|| !subtask
|
|
652
|
+
|| subtask.previewState !== 'ready'
|
|
653
|
+
|| subtask.status.type !== 'busy'
|
|
654
|
+
) {
|
|
655
|
+
delete messageState.subtaskTranscript;
|
|
656
|
+
delete messageState.subtaskTranscriptSessionId;
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (
|
|
660
|
+
messageState.subtaskTranscriptSessionId !== subtask.sessionId
|
|
661
|
+
|| !messageState.subtaskTranscript
|
|
662
|
+
) {
|
|
663
|
+
messageState.subtaskTranscriptSessionId = subtask.sessionId;
|
|
664
|
+
messageState.subtaskTranscript = { messages: {}, permissions: {}, questions: {} };
|
|
665
|
+
}
|
|
666
|
+
this.pruneViewState(
|
|
667
|
+
messageState.subtaskTranscript,
|
|
668
|
+
subtask.messages.map((entry) => entry.id),
|
|
669
|
+
(subtask.permissions ?? []).map((entry) => entry.id),
|
|
670
|
+
(subtask.questions ?? []).map((entry) => entry.id),
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
private get virtualizationActive(): boolean {
|
|
675
|
+
return this.virtualizationEnabled;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
private estimatedVirtualHeight(itemArg: TTimelineRenderItem): number {
|
|
679
|
+
if (itemArg.kind === 'permission') return 150;
|
|
680
|
+
if (itemArg.kind === 'question') return 180;
|
|
681
|
+
if (itemArg.kind === 'tool-group') {
|
|
682
|
+
if (itemArg.layout === 'subtask') return 320;
|
|
683
|
+
return itemArg.layout === 'full-row' ? 120 * itemArg.messages.length : 110;
|
|
684
|
+
}
|
|
685
|
+
if (itemArg.message.reasoning?.some((part) => !part.endedAt)) return 220;
|
|
686
|
+
return itemArg.message.text.length > 1_000 ? 180 : 96;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
private recomputeVirtualOffsets(): void {
|
|
690
|
+
const offsets: number[] = [];
|
|
691
|
+
let offset = 0;
|
|
692
|
+
for (const item of this.timelineRenderItems) {
|
|
693
|
+
offsets.push(offset);
|
|
694
|
+
offset += this.measuredVirtualHeights.get(item.key) ?? this.estimatedVirtualHeight(item);
|
|
695
|
+
offset += virtualGapPx;
|
|
696
|
+
}
|
|
697
|
+
this.virtualOffsets = offsets;
|
|
698
|
+
this.virtualTotalHeight = Math.max(0, offset - virtualGapPx);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
private setInitialVirtualRange(): void {
|
|
702
|
+
if (!this.virtualizationActive) {
|
|
703
|
+
this.virtualStartIndex = 0;
|
|
704
|
+
this.virtualEndIndex = this.timelineRenderItems.length;
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
const initialCount = Math.min(this.timelineRenderItems.length, 24);
|
|
708
|
+
this.virtualEndIndex = this.autoFollow ? this.timelineRenderItems.length : initialCount;
|
|
709
|
+
this.virtualStartIndex = this.autoFollow ? this.virtualEndIndex - initialCount : 0;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
private setVirtualRangeAround(keyArg: string): void {
|
|
713
|
+
const anchorIndex = this.timelineIndexByKey.get(keyArg);
|
|
714
|
+
if (anchorIndex === undefined) {
|
|
715
|
+
this.setInitialVirtualRange();
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
this.virtualStartIndex = Math.max(0, anchorIndex - 12);
|
|
719
|
+
this.virtualEndIndex = Math.min(this.timelineRenderItems.length, anchorIndex + 13);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
private captureFullTimelineMeasurements(): void {
|
|
723
|
+
for (const element of this.shadowRoot?.querySelectorAll<HTMLElement>('.stack > [data-timeline-key]') ?? []) {
|
|
724
|
+
const key = element.dataset.timelineKey;
|
|
725
|
+
const height = element?.getBoundingClientRect().height ?? 0;
|
|
726
|
+
if (key && height > 0) this.measuredVirtualHeights.set(key, height);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
private captureCurrentAnchor(containerArg: HTMLElement): { key: string; offset: number } | undefined {
|
|
731
|
+
if (this.virtualizationActive) return this.captureVirtualAnchor(containerArg);
|
|
732
|
+
const containerTop = containerArg.getBoundingClientRect().top;
|
|
733
|
+
const itemElements = Array.from(
|
|
734
|
+
this.shadowRoot?.querySelectorAll<HTMLElement>('.stack > [data-timeline-key]') ?? [],
|
|
735
|
+
);
|
|
736
|
+
const element = itemElements.find((candidate) => candidate.getBoundingClientRect().bottom > containerTop);
|
|
737
|
+
const key = element?.dataset.timelineKey;
|
|
738
|
+
return key ? { key, offset: containerTop - element.getBoundingClientRect().top } : undefined;
|
|
449
739
|
}
|
|
450
740
|
|
|
451
741
|
public render(): TemplateResult {
|
|
@@ -456,37 +746,18 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
456
746
|
@scroll=${this.handleScroll}
|
|
457
747
|
@wheel=${this.handleUserScrollIntent}
|
|
458
748
|
@touchmove=${this.handleUserScrollIntent}
|
|
459
|
-
@pointerdown=${this.
|
|
749
|
+
@pointerdown=${this.handlePointerDown}
|
|
750
|
+
@pointerup=${this.handlePointerUp}
|
|
751
|
+
@pointercancel=${this.handlePointerCancel}
|
|
460
752
|
@keydown=${this.handleUserScrollIntent}
|
|
753
|
+
@keyup=${this.handleProtectedStateChange}
|
|
754
|
+
@focusout=${this.handleProtectedStateChange}
|
|
755
|
+
@harness-card-retention=${this.handleCardRetention}
|
|
461
756
|
>
|
|
462
757
|
<div class="scrollContent">
|
|
463
758
|
<slot name="leading"></slot>
|
|
464
759
|
${items.length
|
|
465
|
-
?
|
|
466
|
-
<div class="stack">
|
|
467
|
-
${repeat(
|
|
468
|
-
items,
|
|
469
|
-
(item) => item.key,
|
|
470
|
-
(item) => item.kind === 'tool-group'
|
|
471
|
-
? this.renderToolGroup(item)
|
|
472
|
-
: item.kind === 'message'
|
|
473
|
-
? this.renderMessage(item)
|
|
474
|
-
: item.kind === 'permission'
|
|
475
|
-
? html`
|
|
476
|
-
<dees-harness-permission-card
|
|
477
|
-
.request=${item.request}
|
|
478
|
-
.busy=${this.permissionsBusy}
|
|
479
|
-
></dees-harness-permission-card>
|
|
480
|
-
`
|
|
481
|
-
: html`
|
|
482
|
-
<dees-harness-question-card
|
|
483
|
-
.request=${item.question}
|
|
484
|
-
.busy=${this.permissionsBusy}
|
|
485
|
-
></dees-harness-question-card>
|
|
486
|
-
`,
|
|
487
|
-
)}
|
|
488
|
-
</div>
|
|
489
|
-
`
|
|
760
|
+
? this.virtualizationActive ? this.renderVirtualTimeline() : this.renderFullTimeline(items)
|
|
490
761
|
: html`
|
|
491
762
|
<div class="empty">
|
|
492
763
|
<slot name="empty">
|
|
@@ -511,9 +782,94 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
511
782
|
`;
|
|
512
783
|
}
|
|
513
784
|
|
|
514
|
-
private
|
|
785
|
+
private renderFullTimeline(itemsArg: TTimelineRenderItem[]): TemplateResult {
|
|
786
|
+
return html`
|
|
787
|
+
<div class="stack" role="log" aria-label="Conversation transcript">
|
|
788
|
+
${repeat(
|
|
789
|
+
itemsArg,
|
|
790
|
+
(item) => item.key,
|
|
791
|
+
(item, index) => this.renderTimelineItem(item, index),
|
|
792
|
+
)}
|
|
793
|
+
</div>
|
|
794
|
+
`;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
private renderVirtualTimeline(): TemplateResult {
|
|
798
|
+
const indexedItems = this.virtualRenderedIndexes().map((index) => ({
|
|
799
|
+
index,
|
|
800
|
+
item: this.timelineRenderItems[index]!,
|
|
801
|
+
}));
|
|
802
|
+
return html`
|
|
803
|
+
<div
|
|
804
|
+
class="virtualStack"
|
|
805
|
+
role="log"
|
|
806
|
+
aria-label="Conversation transcript"
|
|
807
|
+
style=${`height: ${this.virtualTotalHeight}px;`}
|
|
808
|
+
>
|
|
809
|
+
${repeat(
|
|
810
|
+
indexedItems,
|
|
811
|
+
(entry) => entry.item.key,
|
|
812
|
+
(entry) => html`
|
|
813
|
+
<article
|
|
814
|
+
class="virtualItem"
|
|
815
|
+
data-virtual-key=${entry.item.key}
|
|
816
|
+
aria-posinset=${entry.index + 1}
|
|
817
|
+
aria-setsize=${this.timelineRenderItems.length}
|
|
818
|
+
style=${`--harness-virtual-offset: ${this.virtualOffsets[entry.index] ?? 0}px;`}
|
|
819
|
+
>
|
|
820
|
+
${this.renderTimelineItem(entry.item, entry.index)}
|
|
821
|
+
</article>
|
|
822
|
+
`,
|
|
823
|
+
)}
|
|
824
|
+
</div>
|
|
825
|
+
`;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
private virtualRenderedIndexes(): number[] {
|
|
829
|
+
const indexes: number[] = [];
|
|
830
|
+
for (let index = this.virtualStartIndex; index < this.virtualEndIndex; index += 1) {
|
|
831
|
+
indexes.push(index);
|
|
832
|
+
}
|
|
833
|
+
for (const retainedKey of this.retainedVirtualKeys) {
|
|
834
|
+
const retainedIndex = this.timelineIndexByKey.get(retainedKey);
|
|
835
|
+
if (retainedIndex !== undefined && !indexes.includes(retainedIndex)) indexes.push(retainedIndex);
|
|
836
|
+
}
|
|
837
|
+
return indexes.sort((left, right) => left - right);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
private renderTimelineItem(itemArg: TTimelineRenderItem, logicalIndexArg: number): TemplateResult {
|
|
841
|
+
if (itemArg.kind === 'tool-group') return this.renderToolGroup(itemArg);
|
|
842
|
+
if (itemArg.kind === 'message') return this.renderMessage(itemArg, itemArg.key);
|
|
843
|
+
if (itemArg.kind === 'permission') {
|
|
844
|
+
return html`
|
|
845
|
+
<dees-harness-permission-card
|
|
846
|
+
data-timeline-key=${itemArg.key}
|
|
847
|
+
aria-posinset=${logicalIndexArg + 1}
|
|
848
|
+
aria-setsize=${this.timelineRenderItems.length}
|
|
849
|
+
.request=${itemArg.request}
|
|
850
|
+
.busy=${this.permissionsBusy}
|
|
851
|
+
.viewState=${this.permissionViewState(itemArg.request.id)}
|
|
852
|
+
.suppressEntry=${!this.entryAnimationsEnabled || !this.currentNewContentKeys.has(itemArg.key)}
|
|
853
|
+
></dees-harness-permission-card>
|
|
854
|
+
`;
|
|
855
|
+
}
|
|
856
|
+
return html`
|
|
857
|
+
<dees-harness-question-card
|
|
858
|
+
data-timeline-key=${itemArg.key}
|
|
859
|
+
aria-posinset=${logicalIndexArg + 1}
|
|
860
|
+
aria-setsize=${this.timelineRenderItems.length}
|
|
861
|
+
.request=${itemArg.question}
|
|
862
|
+
.busy=${this.permissionsBusy}
|
|
863
|
+
.viewState=${this.questionViewState(itemArg.question.id)}
|
|
864
|
+
.suppressEntry=${!this.entryAnimationsEnabled || !this.currentNewContentKeys.has(itemArg.key)}
|
|
865
|
+
></dees-harness-question-card>
|
|
866
|
+
`;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
private renderMessage(itemArg: TMessageTimelineItem, timelineKeyArg: string): TemplateResult {
|
|
515
870
|
return html`
|
|
516
871
|
<dees-harness-message
|
|
872
|
+
data-timeline-key=${timelineKeyArg}
|
|
517
873
|
data-message-id=${itemArg.message.id}
|
|
518
874
|
data-tool-layout=${itemArg.toolLayout ?? 'message'}
|
|
519
875
|
.message=${itemArg.message}
|
|
@@ -521,17 +877,35 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
521
877
|
.showTimestamp=${this.showTimestamps}
|
|
522
878
|
.toolRegistry=${this.toolRegistry}
|
|
523
879
|
.allowSubtaskStreams=${this.allowSubtaskStreams}
|
|
524
|
-
.
|
|
880
|
+
.viewState=${this.messageViewState(itemArg.message.id)}
|
|
881
|
+
.suppressEntry=${!this.entryAnimationsEnabled || !this.currentNewContentKeys.has(itemArg.key)}
|
|
525
882
|
></dees-harness-message>
|
|
526
883
|
`;
|
|
527
884
|
}
|
|
528
885
|
|
|
886
|
+
private messageViewState(messageIdArg: string): IHarnessMessageViewState {
|
|
887
|
+
return this.effectiveViewState.messages[messageIdArg]!;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
private permissionViewState(requestIdArg: string): IHarnessPermissionViewState {
|
|
891
|
+
return this.effectiveViewState.permissions[requestIdArg]!;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
private questionViewState(requestIdArg: string): IHarnessQuestionViewState {
|
|
895
|
+
return this.effectiveViewState.questions[requestIdArg]!;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
private get effectiveViewState(): IHarnessTranscriptViewState {
|
|
899
|
+
return this.viewState ?? this.localViewState;
|
|
900
|
+
}
|
|
901
|
+
|
|
529
902
|
private renderToolGroup(groupArg: IToolGroupTimelineItem): TemplateResult {
|
|
530
903
|
const count = groupArg.messages.length;
|
|
531
904
|
const countClass = count >= 4 ? 'toolCount4' : `toolCount${count}`;
|
|
532
905
|
return html`
|
|
533
906
|
<section
|
|
534
907
|
class="toolGroup"
|
|
908
|
+
data-timeline-key=${groupArg.key}
|
|
535
909
|
data-tool-group-id=${groupArg.key}
|
|
536
910
|
data-tool-layout=${groupArg.layout}
|
|
537
911
|
>
|
|
@@ -539,7 +913,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
539
913
|
${repeat(
|
|
540
914
|
groupArg.messages,
|
|
541
915
|
(item) => item.key,
|
|
542
|
-
(item) => this.renderMessage(item),
|
|
916
|
+
(item) => this.renderMessage(item, groupArg.key),
|
|
543
917
|
)}
|
|
544
918
|
</div>
|
|
545
919
|
</section>
|
|
@@ -591,9 +965,15 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
591
965
|
else harnessApplyMessageDelta(this.messages, delta);
|
|
592
966
|
break;
|
|
593
967
|
case 'tool-update':
|
|
968
|
+
const previousLayout = message.toolCall ? this.toolRegistry.resolveLayout(message.toolCall) : null;
|
|
594
969
|
message.toolCall = { ...(message.toolCall ?? { id: delta.tool.id, name: '', status: 'pending' }), ...delta.tool };
|
|
970
|
+
const nextLayout = this.toolRegistry.resolveLayout(message.toolCall);
|
|
971
|
+
this.syncSubtaskViewState(message);
|
|
595
972
|
element?.requestUpdate();
|
|
596
|
-
|
|
973
|
+
if (previousLayout !== nextLayout) {
|
|
974
|
+
this.rebuildTimeline();
|
|
975
|
+
this.requestUpdate();
|
|
976
|
+
}
|
|
597
977
|
break;
|
|
598
978
|
case 'message-end':
|
|
599
979
|
if (element) element.endStream(delta.usage, delta.error);
|
|
@@ -676,6 +1056,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
676
1056
|
) return;
|
|
677
1057
|
if (event.type === 'pointerdown' && event.target !== this.scrollContainer) return;
|
|
678
1058
|
this.userUnpinned = true;
|
|
1059
|
+
this.measurementAnchor = undefined;
|
|
679
1060
|
};
|
|
680
1061
|
|
|
681
1062
|
public scrollToBottom(force = false): void {
|
|
@@ -683,6 +1064,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
683
1064
|
if (!container) return;
|
|
684
1065
|
if (force) {
|
|
685
1066
|
this.userUnpinned = false;
|
|
1067
|
+
this.measurementAnchor = undefined;
|
|
686
1068
|
}
|
|
687
1069
|
if (force || this.isPinnedToBottom) {
|
|
688
1070
|
container.scrollTop = container.scrollHeight;
|
|
@@ -705,8 +1087,113 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
705
1087
|
}
|
|
706
1088
|
}
|
|
707
1089
|
this.lastObservedScrollTop = top;
|
|
1090
|
+
this.scheduleVirtualRangeUpdate();
|
|
1091
|
+
this.schedulePendingVirtualizationActivation();
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
private schedulePendingVirtualizationActivation(): void {
|
|
1095
|
+
if (
|
|
1096
|
+
(!this.virtualizationEnablePending && !this.virtualizationDisablePending)
|
|
1097
|
+
|| this.virtualizationActivationFrame !== null
|
|
1098
|
+
) return;
|
|
1099
|
+
this.virtualizationActivationFrame = requestAnimationFrame(() => {
|
|
1100
|
+
this.virtualizationActivationFrame = null;
|
|
1101
|
+
if (!this.isConnected || this.activeTimelineKey()) return;
|
|
1102
|
+
const container = this.scrollContainer;
|
|
1103
|
+
const anchor = container ? this.captureCurrentAnchor(container) : undefined;
|
|
1104
|
+
if (this.virtualizationEnablePending) {
|
|
1105
|
+
this.captureFullTimelineMeasurements();
|
|
1106
|
+
this.virtualizationEnabled = true;
|
|
1107
|
+
this.virtualizationEnablePending = false;
|
|
1108
|
+
this.recomputeVirtualOffsets();
|
|
1109
|
+
if (anchor) {
|
|
1110
|
+
this.pendingVirtualAnchor = anchor;
|
|
1111
|
+
this.setVirtualRangeAround(anchor.key);
|
|
1112
|
+
} else {
|
|
1113
|
+
this.setInitialVirtualRange();
|
|
1114
|
+
}
|
|
1115
|
+
} else if (this.virtualizationDisablePending) {
|
|
1116
|
+
this.virtualizationEnabled = false;
|
|
1117
|
+
this.virtualizationDisablePending = false;
|
|
1118
|
+
this.retainedVirtualKeys.clear();
|
|
1119
|
+
if (anchor) this.pendingFullAnchor = anchor;
|
|
1120
|
+
this.setInitialVirtualRange();
|
|
1121
|
+
}
|
|
1122
|
+
this.disconnectSelectionChangeListener();
|
|
1123
|
+
this.requestUpdate();
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
private readonly handleSelectionChange = (): void => {
|
|
1128
|
+
const selection = document.getSelection();
|
|
1129
|
+
if (
|
|
1130
|
+
!selection
|
|
1131
|
+
|| selection.rangeCount === 0
|
|
1132
|
+
|| (
|
|
1133
|
+
!this.selectionGestureActive
|
|
1134
|
+
&& this.selectionGestureSnapshot
|
|
1135
|
+
&& !this.selectionMatchesGestureSnapshot(selection)
|
|
1136
|
+
)
|
|
1137
|
+
|| (
|
|
1138
|
+
!this.selectionGestureActive
|
|
1139
|
+
&& selection.isCollapsed
|
|
1140
|
+
&& !this.hasCrossShadowSelectionFallback
|
|
1141
|
+
)
|
|
1142
|
+
) this.clearSelectionGesture();
|
|
1143
|
+
this.scheduleVirtualRangeUpdate();
|
|
1144
|
+
this.schedulePendingVirtualizationActivation();
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
private readonly handlePointerDown = (eventArg: PointerEvent): void => {
|
|
1148
|
+
this.handleUserScrollIntent(eventArg);
|
|
1149
|
+
if (eventArg.button !== 0) return;
|
|
1150
|
+
this.clearSelectionGesture();
|
|
1151
|
+
this.selectionGestureActive = true;
|
|
1152
|
+
this.selectionGestureStartOwner = this.selectionOwnerForEvent(eventArg);
|
|
1153
|
+
const key = this.timelineKeyForEvent(eventArg);
|
|
1154
|
+
if (key) this.selectionGestureKeys.add(key);
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
private readonly handlePointerUp = (eventArg: PointerEvent): void => {
|
|
1158
|
+
if (eventArg.button !== 0) return;
|
|
1159
|
+
const key = this.timelineKeyForEvent(eventArg);
|
|
1160
|
+
if (key) this.selectionGestureKeys.add(key);
|
|
1161
|
+
const endOwner = this.selectionOwnerForEvent(eventArg);
|
|
1162
|
+
this.selectionGestureCrossesOwners = Boolean(
|
|
1163
|
+
this.selectionGestureStartOwner
|
|
1164
|
+
&& endOwner
|
|
1165
|
+
&& this.selectionGestureStartOwner !== endOwner,
|
|
1166
|
+
);
|
|
1167
|
+
this.selectionGestureActive = false;
|
|
1168
|
+
this.selectionGestureSnapshot = this.captureSelectionSnapshot(document.getSelection());
|
|
1169
|
+
if (!this.hasCrossShadowSelectionFallback && document.getSelection()?.isCollapsed !== false) {
|
|
1170
|
+
this.clearSelectionGesture();
|
|
1171
|
+
}
|
|
1172
|
+
this.handleProtectedStateChange();
|
|
708
1173
|
};
|
|
709
1174
|
|
|
1175
|
+
private readonly handlePointerCancel = (): void => {
|
|
1176
|
+
this.clearSelectionGesture();
|
|
1177
|
+
this.handleProtectedStateChange();
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
private readonly handleProtectedStateChange = (): void => {
|
|
1181
|
+
this.scheduleVirtualRangeUpdate();
|
|
1182
|
+
this.schedulePendingVirtualizationActivation();
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
private connectSelectionChangeListener(): void {
|
|
1186
|
+
if (this.selectionChangeListening) return;
|
|
1187
|
+
document.addEventListener('selectionchange', this.handleSelectionChange);
|
|
1188
|
+
this.selectionChangeListening = true;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
private disconnectSelectionChangeListener(): void {
|
|
1192
|
+
if (!this.selectionChangeListening) return;
|
|
1193
|
+
document.removeEventListener('selectionchange', this.handleSelectionChange);
|
|
1194
|
+
this.selectionChangeListening = false;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
710
1197
|
public updated(changedProperties: Map<string, any>): void {
|
|
711
1198
|
super.updated(changedProperties);
|
|
712
1199
|
if (changedProperties.has('messages') || changedProperties.has('status') || changedProperties.has('permissions') || changedProperties.has('questions')) {
|
|
@@ -725,6 +1212,23 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
725
1212
|
|| changedProperties.has('permissions')
|
|
726
1213
|
|| changedProperties.has('questions')
|
|
727
1214
|
) this.syncScrollEdgeFades();
|
|
1215
|
+
if (this.virtualizationActive) {
|
|
1216
|
+
this.connectVirtualObservers();
|
|
1217
|
+
if (this.pendingVirtualAnchor) {
|
|
1218
|
+
this.restoreVirtualAnchor(this.scrollContainer!, this.pendingVirtualAnchor);
|
|
1219
|
+
this.pendingVirtualAnchor = undefined;
|
|
1220
|
+
} else if (this.pendingVirtualRepin) {
|
|
1221
|
+
this.repinToBottom();
|
|
1222
|
+
this.pendingVirtualRepin = false;
|
|
1223
|
+
}
|
|
1224
|
+
this.scheduleVirtualRangeUpdate();
|
|
1225
|
+
} else {
|
|
1226
|
+
this.disconnectVirtualObservers();
|
|
1227
|
+
if (this.pendingFullAnchor) {
|
|
1228
|
+
this.restoreFullAnchor(this.scrollContainer!, this.pendingFullAnchor);
|
|
1229
|
+
this.pendingFullAnchor = undefined;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
728
1232
|
}
|
|
729
1233
|
|
|
730
1234
|
public firstUpdated(changedProperties: Map<string, any>): void {
|
|
@@ -733,6 +1237,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
733
1237
|
this.connectContentResizeObserver();
|
|
734
1238
|
this.repinToBottom();
|
|
735
1239
|
this.syncScrollEdgeFades();
|
|
1240
|
+
if (this.virtualizationActive) this.connectVirtualObservers();
|
|
736
1241
|
// let the initial batch paint without entry animations, then enable them
|
|
737
1242
|
this.entryFrame = requestAnimationFrame(() => {
|
|
738
1243
|
this.entryFrame = null;
|
|
@@ -747,7 +1252,14 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
747
1252
|
|
|
748
1253
|
public async connectedCallback(): Promise<void> {
|
|
749
1254
|
const connected = super.connectedCallback();
|
|
750
|
-
if (this.
|
|
1255
|
+
if (this.virtualizationEnablePending || this.virtualizationDisablePending) {
|
|
1256
|
+
this.connectSelectionChangeListener();
|
|
1257
|
+
}
|
|
1258
|
+
if (this.hasUpdated) {
|
|
1259
|
+
this.connectContentResizeObserver();
|
|
1260
|
+
if (this.virtualizationActive) this.connectVirtualObservers();
|
|
1261
|
+
this.schedulePendingVirtualizationActivation();
|
|
1262
|
+
}
|
|
751
1263
|
await connected;
|
|
752
1264
|
}
|
|
753
1265
|
|
|
@@ -767,6 +1279,369 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
767
1279
|
this.observedScrollContent = content;
|
|
768
1280
|
}
|
|
769
1281
|
|
|
1282
|
+
private connectVirtualObservers(): void {
|
|
1283
|
+
const container = this.scrollContainer;
|
|
1284
|
+
if (!container) return;
|
|
1285
|
+
this.virtualViewportResizeObserver ??= new ResizeObserver((entries) => {
|
|
1286
|
+
const width = entries[0]?.contentRect.width ?? 0;
|
|
1287
|
+
if (width > 0 && Math.abs(width - this.lastVirtualViewportWidth) > 0.5) {
|
|
1288
|
+
const preserveAnchor = this.userUnpinned || !this.autoFollow;
|
|
1289
|
+
const anchor = preserveAnchor ? this.captureVirtualMetricAnchor(container) : undefined;
|
|
1290
|
+
this.lastVirtualViewportWidth = width;
|
|
1291
|
+
this.measuredVirtualHeights.clear();
|
|
1292
|
+
this.recomputeVirtualOffsets();
|
|
1293
|
+
if (anchor) this.pendingVirtualAnchor = anchor;
|
|
1294
|
+
else this.pendingVirtualRepin = true;
|
|
1295
|
+
this.requestUpdate();
|
|
1296
|
+
this.scheduleVirtualRangeUpdate(true);
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
this.virtualViewportResizeObserver.disconnect();
|
|
1300
|
+
this.virtualViewportResizeObserver.observe(container);
|
|
1301
|
+
this.lastVirtualViewportWidth ||= container.getBoundingClientRect().width;
|
|
1302
|
+
|
|
1303
|
+
this.virtualRowResizeObserver ??= new ResizeObserver(() => this.scheduleVirtualMeasurements());
|
|
1304
|
+
this.virtualRowResizeObserver.disconnect();
|
|
1305
|
+
for (const row of this.shadowRoot?.querySelectorAll<HTMLElement>('.virtualItem') ?? []) {
|
|
1306
|
+
this.virtualRowResizeObserver.observe(row);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
private scheduleVirtualMeasurements(): void {
|
|
1311
|
+
if (this.virtualMeasureFrame !== null) return;
|
|
1312
|
+
this.virtualMeasureFrame = requestAnimationFrame(() => {
|
|
1313
|
+
this.virtualMeasureFrame = null;
|
|
1314
|
+
this.measureVirtualRows();
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
private measureVirtualRows(): void {
|
|
1319
|
+
const container = this.scrollContainer;
|
|
1320
|
+
if (!container || !this.virtualizationActive) return;
|
|
1321
|
+
const preserveAnchor = this.userUnpinned || !this.autoFollow;
|
|
1322
|
+
const anchor = preserveAnchor
|
|
1323
|
+
? (this.measurementAnchor ?? this.captureVirtualMetricAnchor(container))
|
|
1324
|
+
: undefined;
|
|
1325
|
+
let changed = false;
|
|
1326
|
+
for (const row of this.shadowRoot?.querySelectorAll<HTMLElement>('.virtualItem') ?? []) {
|
|
1327
|
+
const key = row.dataset.virtualKey;
|
|
1328
|
+
if (!key) continue;
|
|
1329
|
+
const height = row.getBoundingClientRect().height;
|
|
1330
|
+
if (height <= 0 || Math.abs((this.measuredVirtualHeights.get(key) ?? 0) - height) <= 0.5) continue;
|
|
1331
|
+
this.measuredVirtualHeights.set(key, height);
|
|
1332
|
+
changed = true;
|
|
1333
|
+
}
|
|
1334
|
+
if (!changed) {
|
|
1335
|
+
this.measurementAnchor = undefined;
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
this.recomputeVirtualOffsets();
|
|
1339
|
+
if (anchor) this.pendingVirtualAnchor = anchor;
|
|
1340
|
+
else this.pendingVirtualRepin = true;
|
|
1341
|
+
this.requestUpdate();
|
|
1342
|
+
if (this.measurementAnchor) this.scheduleVirtualMeasurements();
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
private captureVirtualAnchor(containerArg: HTMLElement): { key: string; offset: number } | undefined {
|
|
1346
|
+
const containerTop = containerArg.getBoundingClientRect().top;
|
|
1347
|
+
const row = Array.from(this.shadowRoot?.querySelectorAll<HTMLElement>('.virtualItem') ?? [])
|
|
1348
|
+
.find((candidate) => candidate.getBoundingClientRect().bottom > containerTop);
|
|
1349
|
+
const key = row?.dataset.virtualKey;
|
|
1350
|
+
if (row && key) return { key, offset: containerTop - row.getBoundingClientRect().top };
|
|
1351
|
+
const localTop = this.virtualLocalScrollTop(containerArg);
|
|
1352
|
+
return this.captureVirtualOffsetAnchorAt(localTop);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
private captureVirtualMetricAnchor(
|
|
1356
|
+
containerArg: HTMLElement,
|
|
1357
|
+
): { key: string; offset: number } | undefined {
|
|
1358
|
+
const localTop = this.virtualLocalScrollTop(containerArg);
|
|
1359
|
+
for (const row of this.shadowRoot?.querySelectorAll<HTMLElement>('.virtualItem') ?? []) {
|
|
1360
|
+
const key = row.dataset.virtualKey;
|
|
1361
|
+
const index = key ? this.timelineIndexByKey.get(key) : undefined;
|
|
1362
|
+
if (index === undefined) continue;
|
|
1363
|
+
const item = this.timelineRenderItems[index]!;
|
|
1364
|
+
const offset = this.virtualOffsets[index] ?? 0;
|
|
1365
|
+
const height = this.measuredVirtualHeights.get(item.key) ?? this.estimatedVirtualHeight(item);
|
|
1366
|
+
if (offset + height > localTop) return { key: item.key, offset: localTop - offset };
|
|
1367
|
+
}
|
|
1368
|
+
return this.captureVirtualOffsetAnchorAt(localTop);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
private captureVirtualOffsetAnchorAt(localTopArg: number): { key: string; offset: number } | undefined {
|
|
1372
|
+
let index = this.virtualIndexAtOffset(localTopArg);
|
|
1373
|
+
const item = this.timelineRenderItems[index];
|
|
1374
|
+
const itemHeight = item
|
|
1375
|
+
? (this.measuredVirtualHeights.get(item.key) ?? this.estimatedVirtualHeight(item))
|
|
1376
|
+
: 0;
|
|
1377
|
+
if (
|
|
1378
|
+
item
|
|
1379
|
+
&& localTopArg > (this.virtualOffsets[index] ?? 0) + itemHeight
|
|
1380
|
+
&& index + 1 < this.timelineRenderItems.length
|
|
1381
|
+
) index += 1;
|
|
1382
|
+
const anchorItem = this.timelineRenderItems[index];
|
|
1383
|
+
return anchorItem
|
|
1384
|
+
? { key: anchorItem.key, offset: localTopArg - (this.virtualOffsets[index] ?? 0) }
|
|
1385
|
+
: undefined;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
private restoreVirtualAnchor(
|
|
1389
|
+
containerArg: HTMLElement,
|
|
1390
|
+
anchorArg: { key: string; offset: number },
|
|
1391
|
+
): void {
|
|
1392
|
+
const index = this.timelineIndexByKey.get(anchorArg.key);
|
|
1393
|
+
if (index === undefined) return;
|
|
1394
|
+
const stack = this.shadowRoot?.querySelector<HTMLElement>('.virtualStack');
|
|
1395
|
+
containerArg.scrollTop = (stack?.offsetTop ?? 0) + (this.virtualOffsets[index] ?? 0) + anchorArg.offset;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
private restoreFullAnchor(
|
|
1399
|
+
containerArg: HTMLElement,
|
|
1400
|
+
anchorArg: { key: string; offset: number },
|
|
1401
|
+
): void {
|
|
1402
|
+
const element = this.shadowRoot?.querySelector<HTMLElement>(
|
|
1403
|
+
`.stack > [data-timeline-key="${CSS.escape(anchorArg.key)}"]`,
|
|
1404
|
+
);
|
|
1405
|
+
if (!element) return;
|
|
1406
|
+
containerArg.scrollTop += element.getBoundingClientRect().top
|
|
1407
|
+
- containerArg.getBoundingClientRect().top
|
|
1408
|
+
+ anchorArg.offset;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
private virtualLocalScrollTop(containerArg: HTMLElement): number {
|
|
1412
|
+
const stack = this.shadowRoot?.querySelector<HTMLElement>('.virtualStack');
|
|
1413
|
+
return Math.max(0, containerArg.scrollTop - (stack?.offsetTop ?? 0));
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
private virtualIndexAtOffset(offsetArg: number, offsetsArg = this.virtualOffsets): number {
|
|
1417
|
+
if (offsetsArg.length === 0) return 0;
|
|
1418
|
+
let low = 0;
|
|
1419
|
+
let high = offsetsArg.length - 1;
|
|
1420
|
+
while (low < high) {
|
|
1421
|
+
const middle = Math.floor((low + high + 1) / 2);
|
|
1422
|
+
if ((offsetsArg[middle] ?? 0) <= offsetArg) low = middle;
|
|
1423
|
+
else high = middle - 1;
|
|
1424
|
+
}
|
|
1425
|
+
return low;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
private scheduleVirtualRangeUpdate(force = false): void {
|
|
1429
|
+
if (!this.virtualizationActive) return;
|
|
1430
|
+
if (force && this.virtualRangeFrame !== null) {
|
|
1431
|
+
cancelAnimationFrame(this.virtualRangeFrame);
|
|
1432
|
+
this.virtualRangeFrame = null;
|
|
1433
|
+
}
|
|
1434
|
+
if (this.virtualRangeFrame !== null) return;
|
|
1435
|
+
this.virtualRangeFrame = requestAnimationFrame(() => {
|
|
1436
|
+
this.virtualRangeFrame = null;
|
|
1437
|
+
this.updateVirtualRange();
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
private updateVirtualRange(): void {
|
|
1442
|
+
const container = this.scrollContainer;
|
|
1443
|
+
if (!container || !this.virtualizationActive) return;
|
|
1444
|
+
const localTop = this.virtualLocalScrollTop(container);
|
|
1445
|
+
const startTarget = Math.max(0, localTop - virtualOverscanPx);
|
|
1446
|
+
const endTarget = localTop + container.clientHeight + virtualOverscanPx;
|
|
1447
|
+
const start = Math.max(0, this.virtualIndexAtOffset(startTarget) - 1);
|
|
1448
|
+
const end = Math.min(this.timelineRenderItems.length, this.virtualIndexAtOffset(endTarget) + 2);
|
|
1449
|
+
const retainedKeys = new Set(this.activeVirtualKeys());
|
|
1450
|
+
if (
|
|
1451
|
+
start === this.virtualStartIndex
|
|
1452
|
+
&& end === this.virtualEndIndex
|
|
1453
|
+
&& retainedKeys.size === this.retainedVirtualKeys.size
|
|
1454
|
+
&& [...retainedKeys].every((key) => this.retainedVirtualKeys.has(key))
|
|
1455
|
+
) return;
|
|
1456
|
+
this.virtualStartIndex = start;
|
|
1457
|
+
this.virtualEndIndex = end;
|
|
1458
|
+
this.retainedVirtualKeys = retainedKeys;
|
|
1459
|
+
if (this.activeSelectionKeys().length > 0) this.connectSelectionChangeListener();
|
|
1460
|
+
else if (!this.virtualizationEnablePending && !this.virtualizationDisablePending) {
|
|
1461
|
+
this.disconnectSelectionChangeListener();
|
|
1462
|
+
}
|
|
1463
|
+
this.requestUpdate();
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
private activeVirtualKeys(): string[] {
|
|
1467
|
+
const focusKey = this.activeFocusKey();
|
|
1468
|
+
return [
|
|
1469
|
+
...(this.explicitRetainedVirtualKey ? [this.explicitRetainedVirtualKey] : []),
|
|
1470
|
+
...(focusKey ? [focusKey] : []),
|
|
1471
|
+
...this.activeSelectionKeys(),
|
|
1472
|
+
].filter((key, index, keys) => keys.indexOf(key) === index);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
private activeTimelineKey(): string | undefined {
|
|
1476
|
+
if (this.explicitRetainedVirtualKey) return this.explicitRetainedVirtualKey;
|
|
1477
|
+
return this.activeFocusKey() ?? this.activeSelectionKeys()[0];
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
private activeFocusKey(): string | undefined {
|
|
1481
|
+
const activeElement = this.shadowRoot?.activeElement;
|
|
1482
|
+
if (activeElement instanceof Element) {
|
|
1483
|
+
const focusedKey = this.ownedTimelineElement(activeElement)?.dataset.timelineKey
|
|
1484
|
+
?? this.ownedVirtualRow(activeElement)?.dataset.virtualKey;
|
|
1485
|
+
if (focusedKey) return focusedKey;
|
|
1486
|
+
}
|
|
1487
|
+
return undefined;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
private activeSelectionKeys(): string[] {
|
|
1491
|
+
if (this.selectionGestureActive) return [...this.selectionGestureKeys];
|
|
1492
|
+
const selection = document.getSelection();
|
|
1493
|
+
if (!selection || selection.rangeCount === 0) return [];
|
|
1494
|
+
if (selection.isCollapsed) {
|
|
1495
|
+
return this.hasCrossShadowSelectionFallback ? [...this.selectionGestureKeys] : [];
|
|
1496
|
+
}
|
|
1497
|
+
const range = selection.getRangeAt(0);
|
|
1498
|
+
const nodes: Array<Node | null> = [
|
|
1499
|
+
selection.anchorNode,
|
|
1500
|
+
selection.focusNode,
|
|
1501
|
+
range.startContainer,
|
|
1502
|
+
range.endContainer,
|
|
1503
|
+
];
|
|
1504
|
+
if ('getComposedRanges' in selection && typeof selection.getComposedRanges === 'function') {
|
|
1505
|
+
const composedRanges = selection.getComposedRanges({
|
|
1506
|
+
shadowRoots: this.mountedShadowRoots(),
|
|
1507
|
+
});
|
|
1508
|
+
const composedRange = composedRanges[0];
|
|
1509
|
+
if (composedRange) nodes.push(composedRange.startContainer, composedRange.endContainer);
|
|
1510
|
+
}
|
|
1511
|
+
return [
|
|
1512
|
+
...nodes
|
|
1513
|
+
.filter((node): node is Node => node !== null)
|
|
1514
|
+
.map((node) => this.timelineKeyForNode(node))
|
|
1515
|
+
.filter((key): key is string => key !== undefined),
|
|
1516
|
+
...this.selectionGestureKeys,
|
|
1517
|
+
]
|
|
1518
|
+
.filter((key, index, keys) => keys.indexOf(key) === index);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
private mountedShadowRoots(): ShadowRoot[] {
|
|
1522
|
+
const roots: ShadowRoot[] = [];
|
|
1523
|
+
const collect = (rootArg: ShadowRoot): void => {
|
|
1524
|
+
for (const element of rootArg.querySelectorAll<HTMLElement>('*')) {
|
|
1525
|
+
if (!element.shadowRoot) continue;
|
|
1526
|
+
roots.push(element.shadowRoot);
|
|
1527
|
+
collect(element.shadowRoot);
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
if (this.shadowRoot) collect(this.shadowRoot);
|
|
1531
|
+
return roots;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
private timelineKeyForNode(nodeArg: Node): string | undefined {
|
|
1535
|
+
let node: Node | null = nodeArg;
|
|
1536
|
+
while (node) {
|
|
1537
|
+
if (node instanceof HTMLElement && node.classList.contains('virtualItem')) {
|
|
1538
|
+
const ownedRow = this.ownedVirtualRow(node);
|
|
1539
|
+
if (ownedRow) return ownedRow.dataset.virtualKey;
|
|
1540
|
+
}
|
|
1541
|
+
if (node instanceof HTMLElement) {
|
|
1542
|
+
const timelineElement = this.ownedTimelineElement(node);
|
|
1543
|
+
if (timelineElement) return timelineElement.dataset.timelineKey;
|
|
1544
|
+
}
|
|
1545
|
+
const root = node.getRootNode();
|
|
1546
|
+
node = node.parentNode ?? (root instanceof ShadowRoot ? root.host : null);
|
|
1547
|
+
}
|
|
1548
|
+
return undefined;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
private timelineKeyForEvent(eventArg: Event): string | undefined {
|
|
1552
|
+
for (const candidate of eventArg.composedPath()) {
|
|
1553
|
+
if (!(candidate instanceof Element)) continue;
|
|
1554
|
+
const rowKey = this.ownedVirtualRow(candidate)?.dataset.virtualKey;
|
|
1555
|
+
if (rowKey) return rowKey;
|
|
1556
|
+
const timelineKey = this.ownedTimelineElement(candidate)?.dataset.timelineKey;
|
|
1557
|
+
if (timelineKey) return timelineKey;
|
|
1558
|
+
}
|
|
1559
|
+
return undefined;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
private selectionOwnerForEvent(eventArg: Event): Element | undefined {
|
|
1563
|
+
for (const candidate of eventArg.composedPath()) {
|
|
1564
|
+
if (!(candidate instanceof HTMLElement)) continue;
|
|
1565
|
+
if (
|
|
1566
|
+
(
|
|
1567
|
+
candidate.tagName === 'DEES-HARNESS-MESSAGE'
|
|
1568
|
+
|| candidate.tagName === 'DEES-HARNESS-PERMISSION-CARD'
|
|
1569
|
+
|| candidate.tagName === 'DEES-HARNESS-QUESTION-CARD'
|
|
1570
|
+
)
|
|
1571
|
+
&& this.timelineKeyForNode(candidate)
|
|
1572
|
+
) return candidate;
|
|
1573
|
+
}
|
|
1574
|
+
return eventArg.composedPath()
|
|
1575
|
+
.filter((candidate): candidate is Element => candidate instanceof Element)
|
|
1576
|
+
.map((candidate) => this.ownedTimelineElement(candidate) ?? this.ownedVirtualRow(candidate))
|
|
1577
|
+
.find((candidate): candidate is HTMLElement => candidate !== undefined);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
private get hasCrossShadowSelectionFallback(): boolean {
|
|
1581
|
+
return this.selectionGestureKeys.size > 1 || this.selectionGestureCrossesOwners;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
private captureSelectionSnapshot(selectionArg: Selection | null): typeof this.selectionGestureSnapshot {
|
|
1585
|
+
if (!selectionArg) return undefined;
|
|
1586
|
+
return {
|
|
1587
|
+
anchorNode: selectionArg.anchorNode,
|
|
1588
|
+
anchorOffset: selectionArg.anchorOffset,
|
|
1589
|
+
focusNode: selectionArg.focusNode,
|
|
1590
|
+
focusOffset: selectionArg.focusOffset,
|
|
1591
|
+
rangeCount: selectionArg.rangeCount,
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
private selectionMatchesGestureSnapshot(selectionArg: Selection): boolean {
|
|
1596
|
+
const snapshot = this.selectionGestureSnapshot;
|
|
1597
|
+
return Boolean(
|
|
1598
|
+
snapshot
|
|
1599
|
+
&& snapshot.anchorNode === selectionArg.anchorNode
|
|
1600
|
+
&& snapshot.anchorOffset === selectionArg.anchorOffset
|
|
1601
|
+
&& snapshot.focusNode === selectionArg.focusNode
|
|
1602
|
+
&& snapshot.focusOffset === selectionArg.focusOffset
|
|
1603
|
+
&& snapshot.rangeCount === selectionArg.rangeCount,
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
private clearSelectionGesture(): void {
|
|
1608
|
+
this.selectionGestureKeys.clear();
|
|
1609
|
+
this.selectionGestureActive = false;
|
|
1610
|
+
this.selectionGestureStartOwner = undefined;
|
|
1611
|
+
this.selectionGestureCrossesOwners = false;
|
|
1612
|
+
this.selectionGestureSnapshot = undefined;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
private readonly handleCardRetention = (eventArg: CustomEvent<{ active?: boolean }>): void => {
|
|
1616
|
+
const pathElements = eventArg.composedPath()
|
|
1617
|
+
.filter((candidate): candidate is HTMLElement => candidate instanceof HTMLElement);
|
|
1618
|
+
const row = pathElements.map((candidate) => this.ownedVirtualRow(candidate))
|
|
1619
|
+
.find((candidate): candidate is HTMLElement => candidate !== undefined);
|
|
1620
|
+
const timelineElement = pathElements.map((candidate) => this.ownedTimelineElement(candidate))
|
|
1621
|
+
.find((candidate): candidate is HTMLElement => candidate !== undefined);
|
|
1622
|
+
const key = row?.dataset.virtualKey ?? timelineElement?.dataset.timelineKey;
|
|
1623
|
+
if (eventArg.detail?.active === true && key) this.explicitRetainedVirtualKey = key;
|
|
1624
|
+
else if (!key || this.explicitRetainedVirtualKey === key) this.explicitRetainedVirtualKey = undefined;
|
|
1625
|
+
this.retainedVirtualKeys = new Set(this.activeVirtualKeys());
|
|
1626
|
+
this.scheduleVirtualRangeUpdate();
|
|
1627
|
+
this.schedulePendingVirtualizationActivation();
|
|
1628
|
+
};
|
|
1629
|
+
|
|
1630
|
+
private ownedVirtualRow(elementArg: Element): HTMLElement | undefined {
|
|
1631
|
+
const row = elementArg.closest<HTMLElement>('.virtualItem');
|
|
1632
|
+
return row?.getRootNode() === this.shadowRoot ? row : undefined;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
private ownedTimelineElement(elementArg: Element): HTMLElement | undefined {
|
|
1636
|
+
const timelineElement = elementArg.closest<HTMLElement>('[data-timeline-key]');
|
|
1637
|
+
return timelineElement?.getRootNode() === this.shadowRoot ? timelineElement : undefined;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
private disconnectVirtualObservers(): void {
|
|
1641
|
+
this.virtualRowResizeObserver?.disconnect();
|
|
1642
|
+
this.virtualViewportResizeObserver?.disconnect();
|
|
1643
|
+
}
|
|
1644
|
+
|
|
770
1645
|
private repinAfterContentGrowth(): void {
|
|
771
1646
|
const content = this.observedScrollContent;
|
|
772
1647
|
if (!content) return;
|
|
@@ -804,16 +1679,33 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
804
1679
|
}
|
|
805
1680
|
|
|
806
1681
|
public async disconnectedCallback(): Promise<void> {
|
|
1682
|
+
this.disconnectSelectionChangeListener();
|
|
807
1683
|
if (this.followFrame !== null) cancelAnimationFrame(this.followFrame);
|
|
808
1684
|
if (this.followSecondFrame !== null) cancelAnimationFrame(this.followSecondFrame);
|
|
809
1685
|
if (this.entryFrame !== null) cancelAnimationFrame(this.entryFrame);
|
|
810
1686
|
if (this.entrySecondFrame !== null) cancelAnimationFrame(this.entrySecondFrame);
|
|
1687
|
+
if (this.virtualMeasureFrame !== null) cancelAnimationFrame(this.virtualMeasureFrame);
|
|
1688
|
+
if (this.virtualRangeFrame !== null) cancelAnimationFrame(this.virtualRangeFrame);
|
|
1689
|
+
if (this.virtualizationActivationFrame !== null) cancelAnimationFrame(this.virtualizationActivationFrame);
|
|
811
1690
|
this.followFrame = null;
|
|
812
1691
|
this.followSecondFrame = null;
|
|
813
1692
|
this.entryFrame = null;
|
|
814
1693
|
this.entrySecondFrame = null;
|
|
1694
|
+
this.virtualMeasureFrame = null;
|
|
1695
|
+
this.virtualRangeFrame = null;
|
|
1696
|
+
this.virtualizationActivationFrame = null;
|
|
1697
|
+
this.pendingVirtualAnchor = undefined;
|
|
1698
|
+
this.pendingFullAnchor = undefined;
|
|
1699
|
+
this.measurementAnchor = undefined;
|
|
1700
|
+
this.pendingVirtualRepin = false;
|
|
1701
|
+
this.explicitRetainedVirtualKey = undefined;
|
|
1702
|
+
this.retainedVirtualKeys.clear();
|
|
1703
|
+
this.clearSelectionGesture();
|
|
815
1704
|
this.entryAnimationsEnabled = true;
|
|
816
1705
|
this.disconnectContentResizeObserver();
|
|
1706
|
+
this.disconnectVirtualObservers();
|
|
1707
|
+
this.virtualRowResizeObserver = null;
|
|
1708
|
+
this.virtualViewportResizeObserver = null;
|
|
817
1709
|
await super.disconnectedCallback();
|
|
818
1710
|
}
|
|
819
1711
|
}
|