inspect-ai 0.3.69__py3-none-any.whl → 0.3.71__py3-none-any.whl
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.
- inspect_ai/_cli/eval.py +27 -9
- inspect_ai/_display/core/display.py +2 -0
- inspect_ai/_display/core/footer.py +13 -3
- inspect_ai/_display/plain/display.py +6 -2
- inspect_ai/_display/rich/display.py +19 -6
- inspect_ai/_display/textual/app.py +9 -3
- inspect_ai/_display/textual/display.py +4 -0
- inspect_ai/_display/textual/widgets/samples.py +4 -10
- inspect_ai/_display/textual/widgets/transcript.py +35 -18
- inspect_ai/_eval/eval.py +14 -2
- inspect_ai/_eval/evalset.py +6 -1
- inspect_ai/_eval/run.py +6 -0
- inspect_ai/_eval/task/run.py +49 -23
- inspect_ai/_eval/task/task.py +26 -3
- inspect_ai/_util/content.py +20 -1
- inspect_ai/_util/interrupt.py +6 -0
- inspect_ai/_util/logger.py +19 -0
- inspect_ai/_util/rich.py +7 -8
- inspect_ai/_util/text.py +13 -0
- inspect_ai/_util/transcript.py +20 -6
- inspect_ai/_util/working.py +50 -0
- inspect_ai/_view/www/App.css +6 -0
- inspect_ai/_view/www/dist/assets/index.css +171 -99
- inspect_ai/_view/www/dist/assets/index.js +5972 -2770
- inspect_ai/_view/www/eslint.config.mjs +24 -1
- inspect_ai/_view/www/log-schema.json +619 -21
- inspect_ai/_view/www/package.json +8 -3
- inspect_ai/_view/www/src/App.tsx +2 -2
- inspect_ai/_view/www/src/appearance/icons.ts +3 -1
- inspect_ai/_view/www/src/components/AnsiDisplay.tsx +4 -3
- inspect_ai/_view/www/src/components/Card.tsx +9 -8
- inspect_ai/_view/www/src/components/DownloadButton.tsx +2 -1
- inspect_ai/_view/www/src/components/EmptyPanel.tsx +2 -2
- inspect_ai/_view/www/src/components/ErrorPanel.tsx +4 -3
- inspect_ai/_view/www/src/components/ExpandablePanel.tsx +13 -5
- inspect_ai/_view/www/src/components/FindBand.tsx +3 -3
- inspect_ai/_view/www/src/components/HumanBaselineView.tsx +3 -3
- inspect_ai/_view/www/src/components/LabeledValue.tsx +5 -4
- inspect_ai/_view/www/src/components/LargeModal.tsx +18 -13
- inspect_ai/_view/www/src/components/{LightboxCarousel.css → LightboxCarousel.module.css} +22 -18
- inspect_ai/_view/www/src/components/LightboxCarousel.tsx +36 -27
- inspect_ai/_view/www/src/components/MessageBand.tsx +2 -1
- inspect_ai/_view/www/src/components/NavPills.tsx +9 -8
- inspect_ai/_view/www/src/components/ProgressBar.tsx +2 -1
- inspect_ai/_view/www/src/components/TabSet.tsx +21 -15
- inspect_ai/_view/www/src/index.tsx +2 -2
- inspect_ai/_view/www/src/metadata/MetaDataGrid.tsx +11 -9
- inspect_ai/_view/www/src/metadata/MetaDataView.tsx +3 -2
- inspect_ai/_view/www/src/metadata/MetadataGrid.module.css +1 -0
- inspect_ai/_view/www/src/metadata/RenderedContent.tsx +16 -1
- inspect_ai/_view/www/src/plan/DatasetDetailView.tsx +3 -2
- inspect_ai/_view/www/src/plan/DetailStep.tsx +2 -1
- inspect_ai/_view/www/src/plan/PlanCard.tsx +2 -5
- inspect_ai/_view/www/src/plan/PlanDetailView.tsx +6 -9
- inspect_ai/_view/www/src/plan/ScorerDetailView.tsx +2 -1
- inspect_ai/_view/www/src/plan/SolverDetailView.tsx +3 -3
- inspect_ai/_view/www/src/samples/InlineSampleDisplay.tsx +2 -2
- inspect_ai/_view/www/src/samples/SampleDialog.tsx +3 -3
- inspect_ai/_view/www/src/samples/SampleDisplay.module.css +9 -1
- inspect_ai/_view/www/src/samples/SampleDisplay.tsx +30 -3
- inspect_ai/_view/www/src/samples/SampleSummaryView.module.css +4 -0
- inspect_ai/_view/www/src/samples/SampleSummaryView.tsx +25 -4
- inspect_ai/_view/www/src/samples/SamplesTools.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatMessage.tsx +3 -19
- inspect_ai/_view/www/src/samples/chat/ChatMessageRenderer.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatMessageRow.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatView.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatViewVirtualList.tsx +22 -7
- inspect_ai/_view/www/src/samples/chat/MessageContent.tsx +35 -6
- inspect_ai/_view/www/src/samples/chat/MessageContents.tsx +2 -2
- inspect_ai/_view/www/src/samples/chat/messages.ts +15 -2
- inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.tsx +13 -4
- inspect_ai/_view/www/src/samples/chat/tools/ToolInput.module.css +2 -2
- inspect_ai/_view/www/src/samples/chat/tools/ToolInput.tsx +18 -19
- inspect_ai/_view/www/src/samples/chat/tools/ToolOutput.module.css +1 -1
- inspect_ai/_view/www/src/samples/chat/tools/ToolOutput.tsx +4 -3
- inspect_ai/_view/www/src/samples/chat/tools/ToolTitle.tsx +2 -2
- inspect_ai/_view/www/src/samples/error/FlatSampleErrorView.tsx +2 -3
- inspect_ai/_view/www/src/samples/error/SampleErrorView.tsx +3 -2
- inspect_ai/_view/www/src/samples/list/SampleFooter.tsx +2 -1
- inspect_ai/_view/www/src/samples/list/SampleHeader.tsx +2 -1
- inspect_ai/_view/www/src/samples/list/SampleList.tsx +57 -45
- inspect_ai/_view/www/src/samples/list/SampleRow.tsx +2 -1
- inspect_ai/_view/www/src/samples/list/SampleSeparator.tsx +2 -1
- inspect_ai/_view/www/src/samples/sample-tools/EpochFilter.tsx +2 -2
- inspect_ai/_view/www/src/samples/sample-tools/SelectScorer.tsx +4 -3
- inspect_ai/_view/www/src/samples/sample-tools/SortFilter.tsx +2 -5
- inspect_ai/_view/www/src/samples/sample-tools/sample-filter/SampleFilter.tsx +2 -2
- inspect_ai/_view/www/src/samples/scores/SampleScoreView.tsx +2 -1
- inspect_ai/_view/www/src/samples/scores/SampleScores.tsx +2 -2
- inspect_ai/_view/www/src/samples/transcript/ApprovalEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/ErrorEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/InfoEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/InputEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/LoggerEventView.module.css +4 -0
- inspect_ai/_view/www/src/samples/transcript/LoggerEventView.tsx +12 -2
- inspect_ai/_view/www/src/samples/transcript/ModelEventView.module.css +1 -1
- inspect_ai/_view/www/src/samples/transcript/ModelEventView.tsx +25 -28
- inspect_ai/_view/www/src/samples/transcript/SampleInitEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/SampleLimitEventView.tsx +9 -4
- inspect_ai/_view/www/src/samples/transcript/SampleTranscript.tsx +2 -2
- inspect_ai/_view/www/src/samples/transcript/SandboxEventView.module.css +32 -0
- inspect_ai/_view/www/src/samples/transcript/SandboxEventView.tsx +153 -0
- inspect_ai/_view/www/src/samples/transcript/ScoreEventView.tsx +2 -2
- inspect_ai/_view/www/src/samples/transcript/StepEventView.tsx +12 -5
- inspect_ai/_view/www/src/samples/transcript/SubtaskEventView.tsx +18 -14
- inspect_ai/_view/www/src/samples/transcript/ToolEventView.tsx +5 -5
- inspect_ai/_view/www/src/samples/transcript/TranscriptView.tsx +53 -16
- inspect_ai/_view/www/src/samples/transcript/event/EventNav.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/event/EventNavs.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/event/EventPanel.tsx +6 -3
- inspect_ai/_view/www/src/samples/transcript/event/EventRow.tsx +3 -2
- inspect_ai/_view/www/src/samples/transcript/event/EventSection.tsx +2 -2
- inspect_ai/_view/www/src/samples/transcript/event/EventTimingPanel.module.css +28 -0
- inspect_ai/_view/www/src/samples/transcript/event/EventTimingPanel.tsx +115 -0
- inspect_ai/_view/www/src/samples/transcript/event/utils.ts +29 -0
- inspect_ai/_view/www/src/samples/transcript/state/StateDiffView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/state/StateEventRenderers.tsx +3 -3
- inspect_ai/_view/www/src/samples/transcript/state/StateEventView.tsx +11 -8
- inspect_ai/_view/www/src/samples/transcript/types.ts +3 -1
- inspect_ai/_view/www/src/types/log.d.ts +312 -137
- inspect_ai/_view/www/src/usage/ModelTokenTable.tsx +6 -10
- inspect_ai/_view/www/src/usage/ModelUsagePanel.module.css +4 -0
- inspect_ai/_view/www/src/usage/ModelUsagePanel.tsx +32 -9
- inspect_ai/_view/www/src/usage/TokenTable.tsx +4 -6
- inspect_ai/_view/www/src/usage/UsageCard.tsx +2 -1
- inspect_ai/_view/www/src/utils/format.ts +8 -5
- inspect_ai/_view/www/src/utils/json.ts +24 -0
- inspect_ai/_view/www/src/workspace/WorkSpace.tsx +6 -5
- inspect_ai/_view/www/src/workspace/WorkSpaceView.tsx +18 -8
- inspect_ai/_view/www/src/workspace/error/TaskErrorPanel.tsx +2 -1
- inspect_ai/_view/www/src/workspace/navbar/Navbar.tsx +2 -1
- inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.tsx +3 -3
- inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.tsx +4 -3
- inspect_ai/_view/www/src/workspace/navbar/SecondaryBar.tsx +5 -4
- inspect_ai/_view/www/src/workspace/navbar/StatusPanel.tsx +5 -8
- inspect_ai/_view/www/src/workspace/sidebar/EvalStatus.tsx +5 -4
- inspect_ai/_view/www/src/workspace/sidebar/LogDirectoryTitleView.tsx +2 -1
- inspect_ai/_view/www/src/workspace/sidebar/Sidebar.tsx +2 -1
- inspect_ai/_view/www/src/workspace/sidebar/SidebarLogEntry.tsx +2 -2
- inspect_ai/_view/www/src/workspace/sidebar/SidebarScoreView.tsx +2 -1
- inspect_ai/_view/www/src/workspace/sidebar/SidebarScoresView.tsx +2 -2
- inspect_ai/_view/www/src/workspace/tabs/InfoTab.tsx +2 -2
- inspect_ai/_view/www/src/workspace/tabs/JsonTab.tsx +2 -5
- inspect_ai/_view/www/src/workspace/tabs/SamplesTab.tsx +12 -11
- inspect_ai/_view/www/yarn.lock +241 -5
- inspect_ai/log/__init__.py +2 -0
- inspect_ai/log/_condense.py +4 -0
- inspect_ai/log/_log.py +72 -12
- inspect_ai/log/_recorders/eval.py +6 -1
- inspect_ai/log/_samples.py +5 -1
- inspect_ai/log/_transcript.py +89 -2
- inspect_ai/model/__init__.py +2 -0
- inspect_ai/model/_call_tools.py +8 -1
- inspect_ai/model/_chat_message.py +22 -7
- inspect_ai/model/_conversation.py +11 -9
- inspect_ai/model/_generate_config.py +25 -4
- inspect_ai/model/_model.py +164 -72
- inspect_ai/model/_model_call.py +10 -3
- inspect_ai/model/_model_output.py +3 -0
- inspect_ai/model/_openai.py +106 -40
- inspect_ai/model/_providers/anthropic.py +145 -26
- inspect_ai/model/_providers/bedrock.py +7 -0
- inspect_ai/model/_providers/cloudflare.py +20 -7
- inspect_ai/model/_providers/google.py +29 -8
- inspect_ai/model/_providers/groq.py +66 -27
- inspect_ai/model/_providers/hf.py +6 -0
- inspect_ai/model/_providers/mistral.py +78 -51
- inspect_ai/model/_providers/openai.py +66 -4
- inspect_ai/model/_providers/openai_o1.py +10 -0
- inspect_ai/model/_providers/providers.py +2 -2
- inspect_ai/model/_providers/util/tracker.py +92 -0
- inspect_ai/model/_providers/vllm.py +13 -5
- inspect_ai/model/_reasoning.py +15 -2
- inspect_ai/scorer/_model.py +23 -19
- inspect_ai/solver/_basic_agent.py +1 -3
- inspect_ai/solver/_bridge/patch.py +0 -2
- inspect_ai/solver/_human_agent/agent.py +14 -10
- inspect_ai/solver/_human_agent/commands/__init__.py +7 -3
- inspect_ai/solver/_human_agent/commands/submit.py +76 -30
- inspect_ai/solver/_limit.py +4 -4
- inspect_ai/solver/_plan.py +0 -3
- inspect_ai/solver/_task_state.py +7 -0
- inspect_ai/tool/__init__.py +2 -0
- inspect_ai/tool/_tool.py +3 -1
- inspect_ai/tool/_tools/_computer/_resources/tool/_run.py +1 -1
- inspect_ai/tool/_tools/_web_browser/_resources/.pylintrc +8 -0
- inspect_ai/tool/_tools/_web_browser/_resources/.vscode/launch.json +24 -0
- inspect_ai/tool/_tools/_web_browser/_resources/.vscode/settings.json +25 -0
- inspect_ai/tool/_tools/_web_browser/_resources/Dockerfile +5 -6
- inspect_ai/tool/_tools/_web_browser/_resources/README.md +10 -11
- inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree.py +71 -0
- inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree_node.py +323 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/__init__.py +5 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/a11y.py +279 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom.py +9 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom_snapshot.py +293 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/page.py +94 -0
- inspect_ai/tool/_tools/_web_browser/_resources/constants.py +2 -0
- inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.svg +2 -0
- inspect_ai/tool/_tools/_web_browser/_resources/playwright_browser.py +50 -0
- inspect_ai/tool/_tools/_web_browser/_resources/playwright_crawler.py +31 -359
- inspect_ai/tool/_tools/_web_browser/_resources/playwright_page_crawler.py +280 -0
- inspect_ai/tool/_tools/_web_browser/_resources/pyproject.toml +65 -0
- inspect_ai/tool/_tools/_web_browser/_resources/rectangle.py +64 -0
- inspect_ai/tool/_tools/_web_browser/_resources/rpc_client_helpers.py +146 -0
- inspect_ai/tool/_tools/_web_browser/_resources/scale_factor.py +64 -0
- inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_tree_node.py +180 -0
- inspect_ai/tool/_tools/_web_browser/_resources/test_playwright_crawler.py +15 -9
- inspect_ai/tool/_tools/_web_browser/_resources/test_rectangle.py +15 -0
- inspect_ai/tool/_tools/_web_browser/_resources/test_web_client.py +44 -0
- inspect_ai/tool/_tools/_web_browser/_resources/web_browser_rpc_types.py +39 -0
- inspect_ai/tool/_tools/_web_browser/_resources/web_client.py +198 -48
- inspect_ai/tool/_tools/_web_browser/_resources/web_client_new_session.py +26 -25
- inspect_ai/tool/_tools/_web_browser/_resources/web_server.py +178 -39
- inspect_ai/tool/_tools/_web_browser/_web_browser.py +38 -19
- inspect_ai/tool/_tools/_web_search.py +3 -3
- inspect_ai/util/__init__.py +2 -1
- inspect_ai/util/_concurrency.py +14 -8
- inspect_ai/util/_display.py +12 -0
- inspect_ai/util/_sandbox/context.py +15 -0
- inspect_ai/util/_sandbox/docker/docker.py +7 -5
- inspect_ai/util/_sandbox/environment.py +32 -1
- inspect_ai/util/_sandbox/events.py +183 -0
- inspect_ai/util/_sandbox/local.py +3 -3
- inspect_ai/util/_sandbox/self_check.py +131 -43
- inspect_ai/util/_subtask.py +11 -0
- {inspect_ai-0.3.69.dist-info → inspect_ai-0.3.71.dist-info}/METADATA +3 -3
- {inspect_ai-0.3.69.dist-info → inspect_ai-0.3.71.dist-info}/RECORD +233 -211
- {inspect_ai-0.3.69.dist-info → inspect_ai-0.3.71.dist-info}/WHEEL +1 -1
- inspect_ai/_view/www/src/components/VirtualList.module.css +0 -19
- inspect_ai/_view/www/src/components/VirtualList.tsx +0 -292
- inspect_ai/tool/_tools/_web_browser/_resources/accessibility_node.py +0 -312
- inspect_ai/tool/_tools/_web_browser/_resources/dm_env_servicer.py +0 -275
- inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.png +0 -0
- inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_node.py +0 -176
- inspect_ai/tool/_tools/_web_browser/_resources/test_dm_env_servicer.py +0 -135
- inspect_ai/tool/_tools/_web_browser/_resources/test_web_environment.py +0 -71
- inspect_ai/tool/_tools/_web_browser/_resources/web_environment.py +0 -184
- {inspect_ai-0.3.69.dist-info → inspect_ai-0.3.71.dist-info}/LICENSE +0 -0
- {inspect_ai-0.3.69.dist-info → inspect_ai-0.3.71.dist-info}/entry_points.txt +0 -0
- {inspect_ai-0.3.69.dist-info → inspect_ai-0.3.71.dist-info}/top_level.txt +0 -0
@@ -1,19 +0,0 @@
|
|
1
|
-
.container {
|
2
|
-
position: relative;
|
3
|
-
width: 100%;
|
4
|
-
min-height: 100%;
|
5
|
-
overflow: visible;
|
6
|
-
}
|
7
|
-
|
8
|
-
.container.hidden {
|
9
|
-
overflow: hidden;
|
10
|
-
}
|
11
|
-
|
12
|
-
.content {
|
13
|
-
position: absolute;
|
14
|
-
top: 0;
|
15
|
-
left: 0;
|
16
|
-
height: 100%;
|
17
|
-
width: 100%;
|
18
|
-
overflow: visible;
|
19
|
-
}
|
@@ -1,292 +0,0 @@
|
|
1
|
-
import clsx from "clsx";
|
2
|
-
import React, {
|
3
|
-
forwardRef,
|
4
|
-
useEffect,
|
5
|
-
useImperativeHandle,
|
6
|
-
useMemo,
|
7
|
-
useRef,
|
8
|
-
useState,
|
9
|
-
} from "react";
|
10
|
-
import styles from "./VirtualList.module.css";
|
11
|
-
|
12
|
-
export interface VirtualListRef {
|
13
|
-
focus: () => void;
|
14
|
-
scrollToIndex: (index: number, direction?: "up" | "down") => void;
|
15
|
-
}
|
16
|
-
|
17
|
-
interface VirtualListProps<T> {
|
18
|
-
data: T[];
|
19
|
-
renderRow: (item: T, index: number) => React.ReactNode;
|
20
|
-
overscanCount?: number;
|
21
|
-
initialEstimatedRowHeight?: number;
|
22
|
-
sync?: boolean;
|
23
|
-
scrollRef?: React.RefObject<HTMLElement | null>;
|
24
|
-
className?: string;
|
25
|
-
style?: React.CSSProperties;
|
26
|
-
tabIndex?: number;
|
27
|
-
onKeyDown?: (event: React.KeyboardEvent<HTMLDivElement>) => void;
|
28
|
-
}
|
29
|
-
|
30
|
-
interface ListMetrics {
|
31
|
-
rowHeights: Map<number, number>;
|
32
|
-
totalHeight: number;
|
33
|
-
estimatedRowHeight: number;
|
34
|
-
}
|
35
|
-
|
36
|
-
export const VirtualList = forwardRef(function VirtualList<T>(
|
37
|
-
{
|
38
|
-
data,
|
39
|
-
renderRow,
|
40
|
-
overscanCount = 15,
|
41
|
-
initialEstimatedRowHeight = 50,
|
42
|
-
sync = false,
|
43
|
-
scrollRef,
|
44
|
-
onKeyDown,
|
45
|
-
...props
|
46
|
-
}: VirtualListProps<T>,
|
47
|
-
ref: React.Ref<VirtualListRef>,
|
48
|
-
) {
|
49
|
-
const [height, setHeight] = useState(0);
|
50
|
-
const [offset, setOffset] = useState(0);
|
51
|
-
const [listMetrics, setListMetrics] = useState<ListMetrics>({
|
52
|
-
rowHeights: new Map(),
|
53
|
-
totalHeight: data.length * initialEstimatedRowHeight,
|
54
|
-
estimatedRowHeight: initialEstimatedRowHeight,
|
55
|
-
});
|
56
|
-
|
57
|
-
const baseRef = useRef<HTMLDivElement>(null);
|
58
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
59
|
-
const rowRefs = useRef<Map<number, HTMLElement>>(new Map());
|
60
|
-
|
61
|
-
const getRowHeight = (index: number): number => {
|
62
|
-
return listMetrics.rowHeights.get(index) || listMetrics.estimatedRowHeight;
|
63
|
-
};
|
64
|
-
|
65
|
-
// Calculate new estimated height based on measured rows
|
66
|
-
const calculateEstimatedHeight = (heights: Map<number, number>): number => {
|
67
|
-
if (heights.size === 0) return listMetrics.estimatedRowHeight;
|
68
|
-
|
69
|
-
// Calculate average of measured heights
|
70
|
-
let sum = 0;
|
71
|
-
heights.forEach((height) => {
|
72
|
-
sum += height;
|
73
|
-
});
|
74
|
-
|
75
|
-
// Use exponential moving average to smooth transitions
|
76
|
-
const alpha = 0.2; // Smoothing factor
|
77
|
-
const newEstimate = sum / heights.size;
|
78
|
-
return Math.round(
|
79
|
-
alpha * newEstimate + (1 - alpha) * listMetrics.estimatedRowHeight,
|
80
|
-
);
|
81
|
-
};
|
82
|
-
|
83
|
-
const rowPositions = useMemo(() => {
|
84
|
-
let currentPosition = 0;
|
85
|
-
const positions = new Map<number, number>();
|
86
|
-
|
87
|
-
for (let i = 0; i < data.length; i++) {
|
88
|
-
positions.set(i, currentPosition);
|
89
|
-
currentPosition += getRowHeight(i);
|
90
|
-
}
|
91
|
-
|
92
|
-
return positions;
|
93
|
-
}, [listMetrics.rowHeights, listMetrics.estimatedRowHeight, data.length]);
|
94
|
-
|
95
|
-
// Measure rendered rows and update heights if needed
|
96
|
-
const measureRows = () => {
|
97
|
-
let updates: [number, number][] = [];
|
98
|
-
|
99
|
-
rowRefs.current.forEach((element, index) => {
|
100
|
-
if (element) {
|
101
|
-
const measuredHeight = element.offsetHeight;
|
102
|
-
if (
|
103
|
-
measuredHeight &&
|
104
|
-
measuredHeight !== listMetrics.rowHeights.get(index)
|
105
|
-
) {
|
106
|
-
updates.push([index, measuredHeight]);
|
107
|
-
}
|
108
|
-
}
|
109
|
-
});
|
110
|
-
|
111
|
-
if (updates.length === 0) return;
|
112
|
-
|
113
|
-
const newHeights = new Map(listMetrics.rowHeights);
|
114
|
-
updates.forEach(([index, height]) => {
|
115
|
-
newHeights.set(index, height);
|
116
|
-
});
|
117
|
-
|
118
|
-
// Calculate new estimated height
|
119
|
-
const newEstimatedHeight = calculateEstimatedHeight(newHeights);
|
120
|
-
|
121
|
-
let newTotalHeight = 0;
|
122
|
-
for (let i = 0; i < data.length; i++) {
|
123
|
-
newTotalHeight += newHeights.get(i) || newEstimatedHeight;
|
124
|
-
}
|
125
|
-
|
126
|
-
setListMetrics({
|
127
|
-
rowHeights: newHeights,
|
128
|
-
totalHeight: newTotalHeight,
|
129
|
-
estimatedRowHeight: newEstimatedHeight,
|
130
|
-
});
|
131
|
-
};
|
132
|
-
|
133
|
-
useImperativeHandle(
|
134
|
-
ref,
|
135
|
-
() => ({
|
136
|
-
focus: () => {
|
137
|
-
baseRef.current?.focus();
|
138
|
-
},
|
139
|
-
scrollToIndex: (index: number, direction?: "up" | "down") => {
|
140
|
-
const scrollElement = scrollRef?.current || baseRef.current;
|
141
|
-
if (!scrollElement || index < 0 || index >= data.length) return;
|
142
|
-
|
143
|
-
const currentScrollTop = scrollElement.scrollTop;
|
144
|
-
const viewportHeight = scrollElement.offsetHeight;
|
145
|
-
|
146
|
-
const rowTop = rowPositions.get(index) || 0;
|
147
|
-
const rowHeight = getRowHeight(index);
|
148
|
-
const rowBottom = rowTop + rowHeight;
|
149
|
-
|
150
|
-
const isVisible =
|
151
|
-
rowTop >= currentScrollTop &&
|
152
|
-
rowBottom <= currentScrollTop + viewportHeight;
|
153
|
-
if (isVisible) return;
|
154
|
-
|
155
|
-
let newScrollTop: number;
|
156
|
-
if (direction === "up") {
|
157
|
-
newScrollTop = rowTop;
|
158
|
-
} else {
|
159
|
-
newScrollTop = rowBottom - viewportHeight;
|
160
|
-
}
|
161
|
-
|
162
|
-
newScrollTop = Math.max(
|
163
|
-
0,
|
164
|
-
Math.min(newScrollTop, listMetrics.totalHeight - viewportHeight),
|
165
|
-
);
|
166
|
-
scrollElement.scrollTop = newScrollTop;
|
167
|
-
},
|
168
|
-
}),
|
169
|
-
[rowPositions, data.length],
|
170
|
-
);
|
171
|
-
|
172
|
-
const resize = () => {
|
173
|
-
const scrollElement = scrollRef?.current || baseRef.current;
|
174
|
-
if (scrollElement && height !== scrollElement.offsetHeight) {
|
175
|
-
setHeight(scrollElement.offsetHeight);
|
176
|
-
}
|
177
|
-
};
|
178
|
-
|
179
|
-
const handleScroll = throttle(() => {
|
180
|
-
const scrollElement = scrollRef?.current || baseRef.current;
|
181
|
-
if (scrollElement) {
|
182
|
-
setOffset(scrollElement.scrollTop);
|
183
|
-
}
|
184
|
-
if (sync) {
|
185
|
-
setOffset((prev) => prev);
|
186
|
-
}
|
187
|
-
}, 100);
|
188
|
-
|
189
|
-
useEffect(() => {
|
190
|
-
resize();
|
191
|
-
const scrollElement = scrollRef?.current || baseRef.current;
|
192
|
-
|
193
|
-
if (scrollElement) {
|
194
|
-
scrollElement.addEventListener("scroll", handleScroll);
|
195
|
-
window.addEventListener("resize", resize);
|
196
|
-
|
197
|
-
return () => {
|
198
|
-
scrollElement.removeEventListener("scroll", handleScroll);
|
199
|
-
window.removeEventListener("resize", resize);
|
200
|
-
};
|
201
|
-
}
|
202
|
-
}, [scrollRef?.current]);
|
203
|
-
|
204
|
-
useEffect(() => {
|
205
|
-
measureRows();
|
206
|
-
});
|
207
|
-
|
208
|
-
const findRowAtOffset = (targetOffset: number): number => {
|
209
|
-
if (targetOffset <= 0) return 0;
|
210
|
-
if (targetOffset >= listMetrics.totalHeight) return data.length - 1;
|
211
|
-
|
212
|
-
let low = 0;
|
213
|
-
let high = data.length - 1;
|
214
|
-
let lastValid = 0;
|
215
|
-
|
216
|
-
while (low <= high) {
|
217
|
-
const mid = Math.floor((low + high) / 2);
|
218
|
-
const rowStart = rowPositions.get(mid) || 0;
|
219
|
-
|
220
|
-
if (rowStart <= targetOffset) {
|
221
|
-
lastValid = mid;
|
222
|
-
low = mid + 1;
|
223
|
-
} else {
|
224
|
-
high = mid - 1;
|
225
|
-
}
|
226
|
-
}
|
227
|
-
return lastValid;
|
228
|
-
};
|
229
|
-
|
230
|
-
const firstVisibleIdx = findRowAtOffset(offset);
|
231
|
-
const lastVisibleIdx = findRowAtOffset(offset + height);
|
232
|
-
|
233
|
-
const start = Math.max(0, firstVisibleIdx - overscanCount);
|
234
|
-
const end = Math.min(data.length, lastVisibleIdx + overscanCount);
|
235
|
-
|
236
|
-
const renderedRows = useMemo(() => {
|
237
|
-
const selection = data.slice(start, end);
|
238
|
-
return selection.map((item, index) => {
|
239
|
-
const actualIndex = start + index;
|
240
|
-
return (
|
241
|
-
<div
|
242
|
-
key={`list-item-${actualIndex}`}
|
243
|
-
ref={(el) => {
|
244
|
-
if (el) {
|
245
|
-
rowRefs.current.set(actualIndex, el);
|
246
|
-
} else {
|
247
|
-
rowRefs.current.delete(actualIndex);
|
248
|
-
}
|
249
|
-
}}
|
250
|
-
>
|
251
|
-
{renderRow(item, actualIndex)}
|
252
|
-
</div>
|
253
|
-
);
|
254
|
-
});
|
255
|
-
}, [data, start, end, renderRow]);
|
256
|
-
|
257
|
-
const top = rowPositions.get(start) || 0;
|
258
|
-
const scrollProps = scrollRef ? {} : { onScroll: handleScroll };
|
259
|
-
|
260
|
-
return (
|
261
|
-
<div ref={baseRef} {...props} {...scrollProps} onKeyDown={onKeyDown}>
|
262
|
-
<div
|
263
|
-
className={clsx(
|
264
|
-
styles.container,
|
265
|
-
!scrollRef?.current ? styles.hidden : undefined,
|
266
|
-
)}
|
267
|
-
style={{ height: `${listMetrics.totalHeight}px` }}
|
268
|
-
>
|
269
|
-
<div
|
270
|
-
className={styles.content}
|
271
|
-
style={{ transform: `translateY(${top}px)` }}
|
272
|
-
ref={containerRef}
|
273
|
-
>
|
274
|
-
{renderedRows}
|
275
|
-
</div>
|
276
|
-
</div>
|
277
|
-
</div>
|
278
|
-
);
|
279
|
-
}) as <T>(
|
280
|
-
props: VirtualListProps<T> & { ref?: React.Ref<VirtualListRef> },
|
281
|
-
) => React.ReactElement;
|
282
|
-
|
283
|
-
const throttle = (func: (...args: any[]) => void, limit: number) => {
|
284
|
-
let inThrottle: boolean;
|
285
|
-
return function (this: any, ...args: any[]) {
|
286
|
-
if (!inThrottle) {
|
287
|
-
func.apply(this, args);
|
288
|
-
inThrottle = true;
|
289
|
-
setTimeout(() => (inThrottle = false), limit);
|
290
|
-
}
|
291
|
-
};
|
292
|
-
};
|
@@ -1,312 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from typing import Any
|
4
|
-
|
5
|
-
# Properities to ignore when printing out the accessibility tree.
|
6
|
-
_IGNORED_ACTREE_PROPERTIES = (
|
7
|
-
"focusable",
|
8
|
-
"readonly",
|
9
|
-
"level",
|
10
|
-
"settable",
|
11
|
-
"multiline",
|
12
|
-
"invalid",
|
13
|
-
)
|
14
|
-
|
15
|
-
_IGNORED_AT_ROLES = (
|
16
|
-
"generic",
|
17
|
-
"list",
|
18
|
-
"strong",
|
19
|
-
"paragraph",
|
20
|
-
"banner",
|
21
|
-
"navigation",
|
22
|
-
"Section",
|
23
|
-
"LabelText",
|
24
|
-
"Legend",
|
25
|
-
"listitem",
|
26
|
-
)
|
27
|
-
|
28
|
-
# Typically we use the bounding rect provided by the DOM in order to track
|
29
|
-
# if an element is visible or not. However, sometimes it can be useful to
|
30
|
-
# instead take the union of the elements bounds and all its children's bounds.
|
31
|
-
# This process is a little slower, and is probably not needed, but can be
|
32
|
-
# enabled using this flag.
|
33
|
-
USE_UNION_BOUNDS = False
|
34
|
-
|
35
|
-
# The maximum length of the description 'tag' for each element.
|
36
|
-
# Tags longer than this will be truncated with an '...' appended.
|
37
|
-
MAX_NODE_TEXT_LENGTH = 120
|
38
|
-
|
39
|
-
# Used for debugging, include the elements bounding rect during output.
|
40
|
-
_INCLUDE_BOUNDS_IN_OUTPUT = False
|
41
|
-
|
42
|
-
|
43
|
-
class NodeBounds:
|
44
|
-
"""Class to hold bounding rect for notes."""
|
45
|
-
|
46
|
-
def __init__(self, x: int, y: int, width: int, height: int):
|
47
|
-
self.x = int(x)
|
48
|
-
self.y = int(y)
|
49
|
-
self.width = int(width)
|
50
|
-
self.height = int(height)
|
51
|
-
|
52
|
-
@classmethod
|
53
|
-
def from_lrtb(cls, left: int, right: int, top: int, bottom: int) -> NodeBounds:
|
54
|
-
return cls(left, top, right - left, bottom - top)
|
55
|
-
|
56
|
-
@property
|
57
|
-
def left(self) -> int:
|
58
|
-
return self.x
|
59
|
-
|
60
|
-
@property
|
61
|
-
def top(self) -> int:
|
62
|
-
return self.y
|
63
|
-
|
64
|
-
@property
|
65
|
-
def right(self) -> int:
|
66
|
-
return self.x + self.width
|
67
|
-
|
68
|
-
@property
|
69
|
-
def bottom(self) -> int:
|
70
|
-
return self.y + self.height
|
71
|
-
|
72
|
-
@property
|
73
|
-
def center_x(self) -> int:
|
74
|
-
return self.x + self.width // 2
|
75
|
-
|
76
|
-
@property
|
77
|
-
def center_y(self) -> int:
|
78
|
-
return self.y + self.height // 2
|
79
|
-
|
80
|
-
@property
|
81
|
-
def area(self) -> int:
|
82
|
-
return self.width * self.height
|
83
|
-
|
84
|
-
def __str__(self):
|
85
|
-
return f"({self.left}, {self.top}, {self.width}, {self.height})"
|
86
|
-
|
87
|
-
def union(self, other: NodeBounds) -> NodeBounds:
|
88
|
-
"""Return the (convex) union of two bounds."""
|
89
|
-
# Special cases when one or the other is an empty bound.
|
90
|
-
if self.area == 0:
|
91
|
-
return other
|
92
|
-
if other.area == 0:
|
93
|
-
return self
|
94
|
-
|
95
|
-
return self.from_lrtb(
|
96
|
-
min(self.left, other.left),
|
97
|
-
max(self.right, other.right),
|
98
|
-
min(self.top, other.top),
|
99
|
-
max(self.bottom, other.bottom),
|
100
|
-
)
|
101
|
-
|
102
|
-
def is_inside(self, x: int, y: int) -> bool:
|
103
|
-
"""Returns if given point is inside the bounds or not."""
|
104
|
-
return x >= self.left and y >= self.top and x < self.right and y < self.bottom
|
105
|
-
|
106
|
-
def overlaps(self, other: NodeBounds) -> bool:
|
107
|
-
"""Returns if the two bounds intersect."""
|
108
|
-
return (
|
109
|
-
other.left < self.right
|
110
|
-
and other.right > self.left
|
111
|
-
and other.top < self.bottom
|
112
|
-
and other.bottom > self.top
|
113
|
-
)
|
114
|
-
|
115
|
-
|
116
|
-
class AccessibilityNode:
|
117
|
-
"""A class for an accessibility node.
|
118
|
-
|
119
|
-
Accessibility properties are specified here:
|
120
|
-
https://chromedevtools.github.io/devtools-protocol/tot/Accessibility/#type-AXNode
|
121
|
-
"""
|
122
|
-
|
123
|
-
def __init__(self, node: dict[str, Any], parent=None, bounds=None):
|
124
|
-
self._node = node
|
125
|
-
self._parent: AccessibilityNode | None = parent
|
126
|
-
self.bounds = bounds or NodeBounds(0, 0, 0, 0)
|
127
|
-
|
128
|
-
def __getitem__(self, key: str) -> Any:
|
129
|
-
"""Access the underlying node fields."""
|
130
|
-
return self._node.get(key)
|
131
|
-
|
132
|
-
def __setitem__(self, key: str, value: Any):
|
133
|
-
"""Set the underlying node fields."""
|
134
|
-
self._node[key] = value
|
135
|
-
|
136
|
-
def __str__(self):
|
137
|
-
if _INCLUDE_BOUNDS_IN_OUTPUT:
|
138
|
-
bounds_string = " " + str(
|
139
|
-
self.get_union_bounds() if USE_UNION_BOUNDS else self.bounds
|
140
|
-
)
|
141
|
-
else:
|
142
|
-
bounds_string = ""
|
143
|
-
|
144
|
-
node_input = self.current_input
|
145
|
-
if node_input:
|
146
|
-
input_string = f' Current input: "{node_input}"'
|
147
|
-
else:
|
148
|
-
input_string = ""
|
149
|
-
|
150
|
-
# Without bounds we can't click on the element, and therefore the ID
|
151
|
-
# can not be used. Still useful to show these elements though (e.g.)
|
152
|
-
# to let user know about options.
|
153
|
-
id_string = "*" if self.bounds.area == 0 else self.node_id
|
154
|
-
|
155
|
-
url_string = ""
|
156
|
-
if self.role == "image" and self._node.get("src", ""):
|
157
|
-
url_string = " " + self._node["src"]
|
158
|
-
|
159
|
-
return (
|
160
|
-
f'[{id_string}] {self.role} "{self.short_name}"'
|
161
|
-
+ url_string
|
162
|
-
+ input_string
|
163
|
-
+ self.property_string()
|
164
|
-
+ bounds_string
|
165
|
-
)
|
166
|
-
|
167
|
-
@property
|
168
|
-
def node_id(self) -> str:
|
169
|
-
return self._node["nodeId"]
|
170
|
-
|
171
|
-
@property
|
172
|
-
def value(self) -> str:
|
173
|
-
value_field = self._node.get("value", "{}")
|
174
|
-
if isinstance(value_field, dict):
|
175
|
-
return value_field.get("value", "")
|
176
|
-
else:
|
177
|
-
return ""
|
178
|
-
|
179
|
-
@property
|
180
|
-
def parent(self) -> Any | None:
|
181
|
-
"""Returns the first visible parent."""
|
182
|
-
parent = self._parent
|
183
|
-
while parent and parent.is_ignored:
|
184
|
-
parent = parent._parent
|
185
|
-
return parent
|
186
|
-
|
187
|
-
@property
|
188
|
-
def role(self) -> str:
|
189
|
-
if "role" in self._node:
|
190
|
-
return self._node["role"].get("value", "")
|
191
|
-
else:
|
192
|
-
return ""
|
193
|
-
|
194
|
-
@property
|
195
|
-
def name(self) -> str:
|
196
|
-
if "name" in self._node:
|
197
|
-
return self._node["name"].get("value", "")
|
198
|
-
else:
|
199
|
-
return ""
|
200
|
-
|
201
|
-
@property
|
202
|
-
def short_name(self) -> str:
|
203
|
-
short_name = self.name[:MAX_NODE_TEXT_LENGTH]
|
204
|
-
if short_name != self.name:
|
205
|
-
short_name = short_name[: MAX_NODE_TEXT_LENGTH - 3] + "..."
|
206
|
-
return short_name
|
207
|
-
|
208
|
-
@property
|
209
|
-
def is_ignored(self) -> bool:
|
210
|
-
return (
|
211
|
-
self._node["ignored"]
|
212
|
-
or self.role in _IGNORED_AT_ROLES
|
213
|
-
or not self.is_visible
|
214
|
-
or (not self.name and self.role != "textbox")
|
215
|
-
or (self.parent and self.parent.name == self.name)
|
216
|
-
)
|
217
|
-
|
218
|
-
@property
|
219
|
-
def children(self) -> list[Any]:
|
220
|
-
return self._node.get("children", [])
|
221
|
-
|
222
|
-
@property
|
223
|
-
def is_visible(self) -> bool:
|
224
|
-
return self._node.get("is_visible", True)
|
225
|
-
|
226
|
-
def is_selected(self) -> bool:
|
227
|
-
return self._node.get("selected", False)
|
228
|
-
|
229
|
-
@property
|
230
|
-
def is_editable(self) -> bool:
|
231
|
-
for node_property in self.properties:
|
232
|
-
if node_property["name"] == "editable":
|
233
|
-
return True
|
234
|
-
return False
|
235
|
-
|
236
|
-
@property
|
237
|
-
def is_expanded(self) -> bool:
|
238
|
-
for node_property in self.properties:
|
239
|
-
if node_property["name"] == "expanded":
|
240
|
-
return bool(node_property["value"].get("value"))
|
241
|
-
return False
|
242
|
-
|
243
|
-
@property
|
244
|
-
def properties(self) -> list[Any]:
|
245
|
-
return self._node.get("properties", [])
|
246
|
-
|
247
|
-
@property
|
248
|
-
def dom_id(self) -> str:
|
249
|
-
"""Returns the backend DOM id associated with this node."""
|
250
|
-
return self._node.get("backendDOMNodeId", -1)
|
251
|
-
|
252
|
-
@property
|
253
|
-
def current_input(self) -> str:
|
254
|
-
# For some reason text edit boxes get 'input' but comboboxs get 'values'
|
255
|
-
if self.role == "combobox":
|
256
|
-
value = self.value
|
257
|
-
else:
|
258
|
-
value = self.get_property("input")
|
259
|
-
return value
|
260
|
-
|
261
|
-
def get_union_bounds(self) -> list[int]:
|
262
|
-
"""Returns the union of the bounds for this element all children."""
|
263
|
-
bounds = self.bounds
|
264
|
-
for child in self.children:
|
265
|
-
child_union_bounds = child.get_union_bounds()
|
266
|
-
bounds = bounds.union(child_union_bounds)
|
267
|
-
return bounds
|
268
|
-
|
269
|
-
def link_children(self, node_lookup: dict[str, Any]) -> None:
|
270
|
-
"""Creates links to children nodes.
|
271
|
-
|
272
|
-
Nodes are referenced by id, but we want to add references to the
|
273
|
-
AccessibilityNode. We also want add a reference to the parent node.
|
274
|
-
|
275
|
-
To perform this task we need access to the complete list of nodes, and
|
276
|
-
therefore it must be done on a second pass after initializing the nodes.
|
277
|
-
|
278
|
-
Args:
|
279
|
-
node_lookup: A dictionary mapping from node_id to node instances.
|
280
|
-
"""
|
281
|
-
self._node["children"] = []
|
282
|
-
for idx in self._node.get("childIds", []):
|
283
|
-
self._node["children"].append(node_lookup[idx])
|
284
|
-
node_lookup[idx]._parent = self
|
285
|
-
|
286
|
-
def get_property(self, property_name: str) -> Any:
|
287
|
-
"""Query the property value of the underlying accessibility tree node."""
|
288
|
-
return self._node.get(property_name)
|
289
|
-
|
290
|
-
def to_string(self, indent: int = 0, include_children: bool = True) -> str:
|
291
|
-
"""Returns the string representation of the node."""
|
292
|
-
result = ""
|
293
|
-
if not self.is_ignored:
|
294
|
-
result = " " * indent + str(self) + "\n"
|
295
|
-
indent += 1
|
296
|
-
if include_children:
|
297
|
-
children_strings = [child.to_string(indent) for child in self.children]
|
298
|
-
result = result + "".join([s for s in children_strings if s])
|
299
|
-
return result
|
300
|
-
|
301
|
-
def property_string(self) -> str:
|
302
|
-
properties = []
|
303
|
-
for node_property in self.properties:
|
304
|
-
try:
|
305
|
-
if node_property["name"] in _IGNORED_ACTREE_PROPERTIES:
|
306
|
-
continue
|
307
|
-
properties.append(
|
308
|
-
f"{node_property['name']}: {node_property['value'].get('value')}"
|
309
|
-
)
|
310
|
-
except KeyError:
|
311
|
-
pass
|
312
|
-
return " [" + ", ".join(properties) + "]" if properties else ""
|