@unofficialbox/box-open-elements 0.6.0 → 0.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/README.md +26 -72
- package/dist/components/collections/table.d.ts +56 -0
- package/dist/components/collections/table.js +219 -35
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +2 -0
- package/dist/core/offset-window.d.ts +70 -0
- package/dist/core/offset-window.js +134 -0
- package/dist/core/virtualize.d.ts +71 -0
- package/dist/core/virtualize.js +67 -0
- package/dist/patterns/audit/audit-log.d.ts +104 -3
- package/dist/patterns/audit/audit-log.js +295 -7
- package/dist/patterns/audit/window.d.ts +116 -0
- package/dist/patterns/audit/window.js +132 -0
- package/package.json +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row windowing for lists whose rows are *not* all the same height.
|
|
3
|
+
*
|
|
4
|
+
* `resolveRowWindow` (./virtualize.ts) multiplies a row count by one row
|
|
5
|
+
* height. That is exact and O(1), and it is the right answer for a flat table
|
|
6
|
+
* of uniform rows — but it cannot describe a surface where the items differ:
|
|
7
|
+
* a grouped audit log alternates short headings with taller event rows, a
|
|
8
|
+
* collapsed group contributes only its heading, and an expanded detail row adds
|
|
9
|
+
* height the count knows nothing about. Feeding those to the fixed-height
|
|
10
|
+
* engine does not degrade gracefully; the spacers under-report the real height,
|
|
11
|
+
* the scroll range stops matching the scrollbar, and `scrollTop` maps to the
|
|
12
|
+
* wrong item. That is why `box-table` refuses to window expandable rows rather
|
|
13
|
+
* than approximating them.
|
|
14
|
+
*
|
|
15
|
+
* The general answer is a cumulative offset index: the running pixel offset of
|
|
16
|
+
* every item, built once, then binary-searched per scroll. Building is O(n) and
|
|
17
|
+
* searching is O(log n), which is the right split — heights change when the
|
|
18
|
+
* data or the collapse state changes, not on every scroll event.
|
|
19
|
+
*
|
|
20
|
+
* Heights are still *estimates* until something measures them. This engine
|
|
21
|
+
* takes whatever heights it is given and is exact with respect to them; keeping
|
|
22
|
+
* them honest is the caller's job, the same bargain `row-height` strikes.
|
|
23
|
+
*/
|
|
24
|
+
import type { RowWindow } from "./virtualize.js";
|
|
25
|
+
export interface OffsetIndex {
|
|
26
|
+
/**
|
|
27
|
+
* Running offsets, length `count + 1`. `offsets[i]` is the top of item `i`;
|
|
28
|
+
* `offsets[count]` is the total height, so the height of item `i` is
|
|
29
|
+
* `offsets[i + 1] - offsets[i]` with no special case for the last one.
|
|
30
|
+
*/
|
|
31
|
+
readonly offsets: readonly number[];
|
|
32
|
+
readonly count: number;
|
|
33
|
+
readonly totalHeight: number;
|
|
34
|
+
}
|
|
35
|
+
export interface OffsetWindowMetrics {
|
|
36
|
+
/** Height of the scrolling viewport, in pixels. */
|
|
37
|
+
viewportHeight: number;
|
|
38
|
+
/** Current scroll offset of the viewport, in pixels. */
|
|
39
|
+
scrollTop: number;
|
|
40
|
+
/** Extra items rendered above and below the visible band. Default 4. */
|
|
41
|
+
overscan?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The cumulative offset index for a list of item heights.
|
|
45
|
+
*
|
|
46
|
+
* A non-finite or negative height contributes zero rather than poisoning every
|
|
47
|
+
* subsequent offset with `NaN`. Heights arrive from measurement and from host
|
|
48
|
+
* estimates; one bad value should cost that item its space, not break the
|
|
49
|
+
* scrollbar for the whole collection.
|
|
50
|
+
*/
|
|
51
|
+
export declare const buildOffsetIndex: (heights: readonly number[]) => OffsetIndex;
|
|
52
|
+
/**
|
|
53
|
+
* The rendered slice for a scroll position, plus the spacer heights.
|
|
54
|
+
*
|
|
55
|
+
* Same contract as `resolveRowWindow`, so the two engines are interchangeable
|
|
56
|
+
* from a component's point of view:
|
|
57
|
+
* - `paddingTop + (height of the rendered items) + paddingBottom` always equals
|
|
58
|
+
* `totalHeight`, so the scroll range never drifts as items scroll by.
|
|
59
|
+
* - The window always covers the visible band; overscan only ever adds items.
|
|
60
|
+
* - Degenerate geometry yields an empty window rather than a guess.
|
|
61
|
+
*/
|
|
62
|
+
export declare const resolveOffsetWindow: (index: OffsetIndex, metrics: OffsetWindowMetrics) => RowWindow;
|
|
63
|
+
/**
|
|
64
|
+
* The pixel offset of an item, for scrolling it into view.
|
|
65
|
+
*
|
|
66
|
+
* Out-of-range indices clamp to the ends rather than returning `undefined`:
|
|
67
|
+
* callers are keyboard handlers reaching for "the last one", and a clamp is
|
|
68
|
+
* what they would write anyway.
|
|
69
|
+
*/
|
|
70
|
+
export declare const offsetOfItem: (index: OffsetIndex, item: number) => number;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row windowing for lists whose rows are *not* all the same height.
|
|
3
|
+
*
|
|
4
|
+
* `resolveRowWindow` (./virtualize.ts) multiplies a row count by one row
|
|
5
|
+
* height. That is exact and O(1), and it is the right answer for a flat table
|
|
6
|
+
* of uniform rows — but it cannot describe a surface where the items differ:
|
|
7
|
+
* a grouped audit log alternates short headings with taller event rows, a
|
|
8
|
+
* collapsed group contributes only its heading, and an expanded detail row adds
|
|
9
|
+
* height the count knows nothing about. Feeding those to the fixed-height
|
|
10
|
+
* engine does not degrade gracefully; the spacers under-report the real height,
|
|
11
|
+
* the scroll range stops matching the scrollbar, and `scrollTop` maps to the
|
|
12
|
+
* wrong item. That is why `box-table` refuses to window expandable rows rather
|
|
13
|
+
* than approximating them.
|
|
14
|
+
*
|
|
15
|
+
* The general answer is a cumulative offset index: the running pixel offset of
|
|
16
|
+
* every item, built once, then binary-searched per scroll. Building is O(n) and
|
|
17
|
+
* searching is O(log n), which is the right split — heights change when the
|
|
18
|
+
* data or the collapse state changes, not on every scroll event.
|
|
19
|
+
*
|
|
20
|
+
* Heights are still *estimates* until something measures them. This engine
|
|
21
|
+
* takes whatever heights it is given and is exact with respect to them; keeping
|
|
22
|
+
* them honest is the caller's job, the same bargain `row-height` strikes.
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_OVERSCAN = 4;
|
|
25
|
+
const EMPTY_INDEX = { offsets: [0], count: 0, totalHeight: 0 };
|
|
26
|
+
/**
|
|
27
|
+
* The cumulative offset index for a list of item heights.
|
|
28
|
+
*
|
|
29
|
+
* A non-finite or negative height contributes zero rather than poisoning every
|
|
30
|
+
* subsequent offset with `NaN`. Heights arrive from measurement and from host
|
|
31
|
+
* estimates; one bad value should cost that item its space, not break the
|
|
32
|
+
* scrollbar for the whole collection.
|
|
33
|
+
*/
|
|
34
|
+
export const buildOffsetIndex = (heights) => {
|
|
35
|
+
if (heights.length === 0)
|
|
36
|
+
return EMPTY_INDEX;
|
|
37
|
+
const offsets = new Array(heights.length + 1);
|
|
38
|
+
offsets[0] = 0;
|
|
39
|
+
let running = 0;
|
|
40
|
+
for (let index = 0; index < heights.length; index++) {
|
|
41
|
+
const height = heights[index] ?? 0;
|
|
42
|
+
running += Number.isFinite(height) && height > 0 ? height : 0;
|
|
43
|
+
offsets[index + 1] = running;
|
|
44
|
+
}
|
|
45
|
+
return { offsets, count: heights.length, totalHeight: running };
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The index of the last item whose top is at or before `offset`.
|
|
49
|
+
*
|
|
50
|
+
* Binary search rather than a scan: this runs per scroll frame, and a linear
|
|
51
|
+
* walk over ten thousand offsets is the cost windowing exists to avoid.
|
|
52
|
+
* Zero-height items make offsets non-strictly-increasing, so ties resolve to
|
|
53
|
+
* the *first* of a run — the alternative is a window that starts inside a
|
|
54
|
+
* cluster of collapsed items and leaves the one above it unrendered.
|
|
55
|
+
*/
|
|
56
|
+
const findItemAt = (offsets, count, offset) => {
|
|
57
|
+
let low = 0;
|
|
58
|
+
let high = count - 1;
|
|
59
|
+
let found = 0;
|
|
60
|
+
while (low <= high) {
|
|
61
|
+
const mid = (low + high) >> 1;
|
|
62
|
+
if ((offsets[mid] ?? 0) <= offset) {
|
|
63
|
+
found = mid;
|
|
64
|
+
low = mid + 1;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
high = mid - 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Walk back over a run of equal offsets (zero-height items) so the window
|
|
71
|
+
// starts at the first item sharing this offset, not an arbitrary one.
|
|
72
|
+
const at = offsets[found] ?? 0;
|
|
73
|
+
while (found > 0 && (offsets[found - 1] ?? 0) === at)
|
|
74
|
+
found--;
|
|
75
|
+
return found;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* The rendered slice for a scroll position, plus the spacer heights.
|
|
79
|
+
*
|
|
80
|
+
* Same contract as `resolveRowWindow`, so the two engines are interchangeable
|
|
81
|
+
* from a component's point of view:
|
|
82
|
+
* - `paddingTop + (height of the rendered items) + paddingBottom` always equals
|
|
83
|
+
* `totalHeight`, so the scroll range never drifts as items scroll by.
|
|
84
|
+
* - The window always covers the visible band; overscan only ever adds items.
|
|
85
|
+
* - Degenerate geometry yields an empty window rather than a guess.
|
|
86
|
+
*/
|
|
87
|
+
export const resolveOffsetWindow = (index, metrics) => {
|
|
88
|
+
const { offsets, count, totalHeight } = index;
|
|
89
|
+
const overscan = Math.max(0, Math.floor(metrics.overscan ?? DEFAULT_OVERSCAN));
|
|
90
|
+
if (count <= 0 || totalHeight <= 0) {
|
|
91
|
+
return { startIndex: 0, endIndex: 0, paddingTop: 0, paddingBottom: 0, totalHeight };
|
|
92
|
+
}
|
|
93
|
+
if (metrics.viewportHeight <= 0) {
|
|
94
|
+
// An unmeasured viewport is the normal state during first paint and under
|
|
95
|
+
// jsdom. Rendering everything there would defeat the purpose exactly when
|
|
96
|
+
// it matters most.
|
|
97
|
+
return { startIndex: 0, endIndex: 0, paddingTop: 0, paddingBottom: totalHeight, totalHeight };
|
|
98
|
+
}
|
|
99
|
+
// Clamp a bounced or past-the-end scrollTop: rubber-banding reports both, and
|
|
100
|
+
// neither may produce a window outside the collection.
|
|
101
|
+
const maxScroll = Math.max(0, totalHeight - metrics.viewportHeight);
|
|
102
|
+
const top = Math.min(Math.max(0, metrics.scrollTop), maxScroll);
|
|
103
|
+
const bottom = top + metrics.viewportHeight;
|
|
104
|
+
const firstVisible = findItemAt(offsets, count, top);
|
|
105
|
+
// Last item that starts before the viewport's bottom edge. Scanning forward
|
|
106
|
+
// from firstVisible is bounded by what fits on screen plus overscan, which is
|
|
107
|
+
// the point of windowing — no walk over the whole collection.
|
|
108
|
+
let lastVisible = firstVisible;
|
|
109
|
+
while (lastVisible + 1 < count && (offsets[lastVisible + 1] ?? 0) < bottom) {
|
|
110
|
+
lastVisible++;
|
|
111
|
+
}
|
|
112
|
+
const startIndex = Math.max(0, firstVisible - overscan);
|
|
113
|
+
const endIndex = Math.min(count, lastVisible + 1 + overscan);
|
|
114
|
+
return {
|
|
115
|
+
startIndex,
|
|
116
|
+
endIndex,
|
|
117
|
+
paddingTop: offsets[startIndex] ?? 0,
|
|
118
|
+
paddingBottom: totalHeight - (offsets[endIndex] ?? totalHeight),
|
|
119
|
+
totalHeight,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* The pixel offset of an item, for scrolling it into view.
|
|
124
|
+
*
|
|
125
|
+
* Out-of-range indices clamp to the ends rather than returning `undefined`:
|
|
126
|
+
* callers are keyboard handlers reaching for "the last one", and a clamp is
|
|
127
|
+
* what they would write anyway.
|
|
128
|
+
*/
|
|
129
|
+
export const offsetOfItem = (index, item) => {
|
|
130
|
+
if (index.count <= 0)
|
|
131
|
+
return 0;
|
|
132
|
+
const clamped = Math.min(Math.max(0, Math.floor(item)), index.count);
|
|
133
|
+
return index.offsets[clamped] ?? 0;
|
|
134
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row windowing for long lists.
|
|
3
|
+
*
|
|
4
|
+
* A ten-thousand-row audit log rendered whole is ten thousand DOM rows: the
|
|
5
|
+
* first paint stalls, every subsequent patch walks the lot, and the browser
|
|
6
|
+
* holds layout boxes nobody will ever scroll to. Windowing renders only the
|
|
7
|
+
* rows near the viewport and pads the rest with spacer height, so the scrollbar
|
|
8
|
+
* still describes the whole collection.
|
|
9
|
+
*
|
|
10
|
+
* The arithmetic lives here, pure, for the usual reason: jsdom has no layout,
|
|
11
|
+
* so a DOM-level test of a windowed surface can only ever observe zeroes. It is
|
|
12
|
+
* shared rather than per-component so `box-table`, `box-audit-log`, and
|
|
13
|
+
* `box-tree-grid` cannot drift into three different definitions of "near".
|
|
14
|
+
*
|
|
15
|
+
* Fixed row height only. Measured/variable heights are a different algorithm
|
|
16
|
+
* (a cumulative offset index that reflows as rows are measured); pretending one
|
|
17
|
+
* function covers both is how windowing engines end up subtly wrong at the
|
|
18
|
+
* boundaries.
|
|
19
|
+
*/
|
|
20
|
+
export interface WindowMetrics {
|
|
21
|
+
/** Total rows in the collection, not the rendered subset. */
|
|
22
|
+
totalRows: number;
|
|
23
|
+
/** Fixed height of one row, in pixels. */
|
|
24
|
+
rowHeight: number;
|
|
25
|
+
/** Height of the scrolling viewport, in pixels. */
|
|
26
|
+
viewportHeight: number;
|
|
27
|
+
/** Current scroll offset of the viewport, in pixels. */
|
|
28
|
+
scrollTop: number;
|
|
29
|
+
/**
|
|
30
|
+
* Extra rows rendered above and below the visible band, so a fast scroll
|
|
31
|
+
* does not expose blank space before the next frame. Default 4.
|
|
32
|
+
*/
|
|
33
|
+
overscan?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface RowWindow {
|
|
36
|
+
/** First rendered row index, inclusive. */
|
|
37
|
+
startIndex: number;
|
|
38
|
+
/** Last rendered row index, exclusive — safe for `slice(startIndex, endIndex)`. */
|
|
39
|
+
endIndex: number;
|
|
40
|
+
/** Spacer height above the rendered rows, in pixels. */
|
|
41
|
+
paddingTop: number;
|
|
42
|
+
/** Spacer height below the rendered rows, in pixels. */
|
|
43
|
+
paddingBottom: number;
|
|
44
|
+
/** Height of the whole collection, for the scroll range. */
|
|
45
|
+
totalHeight: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The rendered slice for a scroll position, plus the spacer heights that keep
|
|
49
|
+
* the scrollbar honest.
|
|
50
|
+
*
|
|
51
|
+
* Guarantees callers can rely on:
|
|
52
|
+
* - `paddingTop + (endIndex - startIndex) * rowHeight + paddingBottom` always
|
|
53
|
+
* equals `totalHeight`, so the scroll range never drifts as rows scroll by —
|
|
54
|
+
* a windowed list whose height wobbles makes the scrollbar jump under the
|
|
55
|
+
* pointer.
|
|
56
|
+
* - The window always covers the visible band; overscan only ever adds rows.
|
|
57
|
+
* - Degenerate geometry (no rows, unmeasured viewport, non-positive row height)
|
|
58
|
+
* yields an empty window rather than a guess. An unmeasured viewport is the
|
|
59
|
+
* normal state during first paint and under jsdom, and rendering *everything*
|
|
60
|
+
* there would defeat the purpose at exactly the moment it matters most.
|
|
61
|
+
*/
|
|
62
|
+
export declare const resolveRowWindow: (metrics: WindowMetrics) => RowWindow;
|
|
63
|
+
/**
|
|
64
|
+
* Whether a scroll moved far enough to need a re-render.
|
|
65
|
+
*
|
|
66
|
+
* Scroll fires continuously; re-rendering on every event would rebuild the row
|
|
67
|
+
* markup dozens of times per second to show the same rows. Comparing the
|
|
68
|
+
* resolved window is cheaper and exact — the render is needed precisely when
|
|
69
|
+
* the slice changes.
|
|
70
|
+
*/
|
|
71
|
+
export declare const sameRowWindow: (a: RowWindow, b: RowWindow) => boolean;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row windowing for long lists.
|
|
3
|
+
*
|
|
4
|
+
* A ten-thousand-row audit log rendered whole is ten thousand DOM rows: the
|
|
5
|
+
* first paint stalls, every subsequent patch walks the lot, and the browser
|
|
6
|
+
* holds layout boxes nobody will ever scroll to. Windowing renders only the
|
|
7
|
+
* rows near the viewport and pads the rest with spacer height, so the scrollbar
|
|
8
|
+
* still describes the whole collection.
|
|
9
|
+
*
|
|
10
|
+
* The arithmetic lives here, pure, for the usual reason: jsdom has no layout,
|
|
11
|
+
* so a DOM-level test of a windowed surface can only ever observe zeroes. It is
|
|
12
|
+
* shared rather than per-component so `box-table`, `box-audit-log`, and
|
|
13
|
+
* `box-tree-grid` cannot drift into three different definitions of "near".
|
|
14
|
+
*
|
|
15
|
+
* Fixed row height only. Measured/variable heights are a different algorithm
|
|
16
|
+
* (a cumulative offset index that reflows as rows are measured); pretending one
|
|
17
|
+
* function covers both is how windowing engines end up subtly wrong at the
|
|
18
|
+
* boundaries.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_OVERSCAN = 4;
|
|
21
|
+
/**
|
|
22
|
+
* The rendered slice for a scroll position, plus the spacer heights that keep
|
|
23
|
+
* the scrollbar honest.
|
|
24
|
+
*
|
|
25
|
+
* Guarantees callers can rely on:
|
|
26
|
+
* - `paddingTop + (endIndex - startIndex) * rowHeight + paddingBottom` always
|
|
27
|
+
* equals `totalHeight`, so the scroll range never drifts as rows scroll by —
|
|
28
|
+
* a windowed list whose height wobbles makes the scrollbar jump under the
|
|
29
|
+
* pointer.
|
|
30
|
+
* - The window always covers the visible band; overscan only ever adds rows.
|
|
31
|
+
* - Degenerate geometry (no rows, unmeasured viewport, non-positive row height)
|
|
32
|
+
* yields an empty window rather than a guess. An unmeasured viewport is the
|
|
33
|
+
* normal state during first paint and under jsdom, and rendering *everything*
|
|
34
|
+
* there would defeat the purpose at exactly the moment it matters most.
|
|
35
|
+
*/
|
|
36
|
+
export const resolveRowWindow = (metrics) => {
|
|
37
|
+
const { totalRows, rowHeight, viewportHeight, scrollTop } = metrics;
|
|
38
|
+
const overscan = Math.max(0, Math.floor(metrics.overscan ?? DEFAULT_OVERSCAN));
|
|
39
|
+
if (totalRows <= 0 || rowHeight <= 0) {
|
|
40
|
+
return { startIndex: 0, endIndex: 0, paddingTop: 0, paddingBottom: 0, totalHeight: 0 };
|
|
41
|
+
}
|
|
42
|
+
const totalHeight = totalRows * rowHeight;
|
|
43
|
+
if (viewportHeight <= 0) {
|
|
44
|
+
return { startIndex: 0, endIndex: 0, paddingTop: 0, paddingBottom: totalHeight, totalHeight };
|
|
45
|
+
}
|
|
46
|
+
// Clamp the scroll offset: a bounced/rubber-banded scrollTop (negative, or
|
|
47
|
+
// past the end) must not produce a window outside the collection.
|
|
48
|
+
const maxScroll = Math.max(0, totalHeight - viewportHeight);
|
|
49
|
+
const offset = Math.min(Math.max(0, scrollTop), maxScroll);
|
|
50
|
+
const firstVisible = Math.floor(offset / rowHeight);
|
|
51
|
+
// ceil of the band, +1 for the row straddling the bottom edge.
|
|
52
|
+
const visibleCount = Math.ceil(viewportHeight / rowHeight) + 1;
|
|
53
|
+
const startIndex = Math.max(0, firstVisible - overscan);
|
|
54
|
+
const endIndex = Math.min(totalRows, firstVisible + visibleCount + overscan);
|
|
55
|
+
const paddingTop = startIndex * rowHeight;
|
|
56
|
+
const paddingBottom = totalHeight - endIndex * rowHeight;
|
|
57
|
+
return { startIndex, endIndex, paddingTop, paddingBottom, totalHeight };
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Whether a scroll moved far enough to need a re-render.
|
|
61
|
+
*
|
|
62
|
+
* Scroll fires continuously; re-rendering on every event would rebuild the row
|
|
63
|
+
* markup dozens of times per second to show the same rows. Comparing the
|
|
64
|
+
* resolved window is cheaper and exact — the render is needed precisely when
|
|
65
|
+
* the slice changes.
|
|
66
|
+
*/
|
|
67
|
+
export const sameRowWindow = (a, b) => a.startIndex === b.startIndex && a.endIndex === b.endIndex;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { AuditEvent, AuditFacets, AuditGroupBy } from "./types.js";
|
|
1
|
+
import type { AuditEvent, AuditFacets, AuditGroup, AuditGroupBy } from "./types.js";
|
|
2
2
|
import { BaseElement } from "../../core/index.js";
|
|
3
|
+
import type { RowWindow } from "../../core/index.js";
|
|
3
4
|
/**
|
|
4
5
|
* The aggregation, faceting, and export layer over the `box-timeline` event
|
|
5
6
|
* contract (opportunity 3 of `plans/component-opportunities.md`): the same
|
|
@@ -7,8 +8,9 @@ import { BaseElement } from "../../core/index.js";
|
|
|
7
8
|
* sections with counts, narrowed by facets and a date range, drillable to one
|
|
8
9
|
* workflow run by correlation id, and exportable as CSV.
|
|
9
10
|
*
|
|
10
|
-
* Aggregation is client-side and pure.
|
|
11
|
-
*
|
|
11
|
+
* Aggregation is client-side and pure. `virtualize` windows the log for
|
|
12
|
+
* production-scale volumes; server-side paging remains a tracked depth
|
|
13
|
+
* limitation.
|
|
12
14
|
*/
|
|
13
15
|
export declare class AuditLog extends BaseElement {
|
|
14
16
|
static readonly tagName: string;
|
|
@@ -29,6 +31,15 @@ export declare class AuditLog extends BaseElement {
|
|
|
29
31
|
private titleEl;
|
|
30
32
|
/** Section keys the reader has collapsed. Absent means expanded. */
|
|
31
33
|
private collapsedGroups;
|
|
34
|
+
/** The row window the last render used; null when it rendered everything. */
|
|
35
|
+
private lastWindow;
|
|
36
|
+
private scrollFrame;
|
|
37
|
+
private measuredHeights;
|
|
38
|
+
private measureFrame;
|
|
39
|
+
/** Row set the adoption budget below is counted against. */
|
|
40
|
+
private adoptedFor;
|
|
41
|
+
private adoptionCount;
|
|
42
|
+
private viewportObserver;
|
|
32
43
|
private contentSignature;
|
|
33
44
|
private optionsSignature;
|
|
34
45
|
/** Raw `events` attribute the parse cache was built from. */
|
|
@@ -58,6 +69,14 @@ export declare class AuditLog extends BaseElement {
|
|
|
58
69
|
set referenceTime(value: string);
|
|
59
70
|
/** The events currently on screen: the full set narrowed by the facets. */
|
|
60
71
|
get visibleEvents(): AuditEvent[];
|
|
72
|
+
/**
|
|
73
|
+
* The sections the current facets and grouping produce.
|
|
74
|
+
*
|
|
75
|
+
* Public because windowing takes away the alternative: a host used to be
|
|
76
|
+
* able to read the sections off the DOM, and once only a slice is rendered,
|
|
77
|
+
* that stops being the whole picture.
|
|
78
|
+
*/
|
|
79
|
+
get groups(): AuditGroup[];
|
|
61
80
|
toggleGroup(key: string, expanded?: boolean): void;
|
|
62
81
|
expandAll(): void;
|
|
63
82
|
collapseAll(): void;
|
|
@@ -68,8 +87,90 @@ export declare class AuditLog extends BaseElement {
|
|
|
68
87
|
* archive API).
|
|
69
88
|
*/
|
|
70
89
|
exportCsv(): string;
|
|
90
|
+
/**
|
|
91
|
+
* Watch the scroller so a measurable — or resized — viewport re-plans.
|
|
92
|
+
*
|
|
93
|
+
* Separate from `setupListeners` because that runs *once*, on the element's
|
|
94
|
+
* first connection, while `disconnectedCallback` tears the observer down
|
|
95
|
+
* every time. A log removed and re-inserted would otherwise come back with
|
|
96
|
+
* its listeners intact and its observer gone, so resizing its container
|
|
97
|
+
* would silently stop re-planning. Idempotent, so both callers are safe.
|
|
98
|
+
*/
|
|
99
|
+
private observeViewport;
|
|
100
|
+
connectedCallback(): void;
|
|
101
|
+
disconnectedCallback(): void;
|
|
71
102
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
103
|
+
/**
|
|
104
|
+
* Render only the sections near the viewport. Opt-in for the same reason
|
|
105
|
+
* `box-table`'s is: a windowed surface needs a bounded height to scroll
|
|
106
|
+
* within, and silently windowing an unbounded one would render nothing.
|
|
107
|
+
*
|
|
108
|
+
* Unlike the table, this *can* window a grouped, variable-height surface —
|
|
109
|
+
* it plans over a cumulative offset index rather than a row count times a
|
|
110
|
+
* row height. Grouping is the whole point of an audit log, so refusing to
|
|
111
|
+
* window it would mean refusing the only shape it comes in.
|
|
112
|
+
*/
|
|
113
|
+
get virtualize(): boolean;
|
|
114
|
+
set virtualize(value: boolean);
|
|
115
|
+
/**
|
|
116
|
+
* Starting height estimates, in pixels — not contracts. The real heights
|
|
117
|
+
* depend on density, font, and how much an event's summary wraps; after the
|
|
118
|
+
* first paint the element measures one of each and uses that instead.
|
|
119
|
+
*/
|
|
120
|
+
get rowHeight(): number;
|
|
121
|
+
get headingHeight(): number;
|
|
122
|
+
set rowHeight(value: number);
|
|
123
|
+
set headingHeight(value: number);
|
|
124
|
+
private declaredHeight;
|
|
125
|
+
/** The window the last render used, or null when it rendered every section. */
|
|
126
|
+
get renderedWindow(): RowWindow | null;
|
|
127
|
+
/**
|
|
128
|
+
* Adopt the real rendered heights, once, when they differ from the ones the
|
|
129
|
+
* last plan used. Re-planning with the measured heights settles immediately —
|
|
130
|
+
* the next measurement matches — so this cannot loop.
|
|
131
|
+
*/
|
|
132
|
+
/** Mean rendered height of the elements matching a part, or 0 if none. */
|
|
133
|
+
private meanHeight;
|
|
134
|
+
/**
|
|
135
|
+
* Adopt the real rendered heights when they differ from the ones in use.
|
|
136
|
+
*
|
|
137
|
+
* Two things here exist because the obvious version ran away. Sampling *one*
|
|
138
|
+
* event row and adopting its height looks fine and is not: event rows are not
|
|
139
|
+
* uniform — `summary`, `evidence` and `correlationId` are each optional — so
|
|
140
|
+
* adopting the first rendered row's height moves the window, which renders a
|
|
141
|
+
* different first row, which measures differently, which moves the window.
|
|
142
|
+
* Measured in Chromium with every third event several lines taller: **55 full
|
|
143
|
+
* rebuilds in 0.9 seconds while nothing was scrolling**, the sampled height
|
|
144
|
+
* flipping between 33px and 166.5px.
|
|
145
|
+
*
|
|
146
|
+
* So: average across every rendered row rather than sampling one, which makes
|
|
147
|
+
* the estimate both more representative and far less sensitive to which rows
|
|
148
|
+
* happen to be on screen. And bound the adoptions per row set anyway, because
|
|
149
|
+
* "the average is stable" is an argument about typical data, and this is the
|
|
150
|
+
* code path where being wrong costs a frame-rate collapse rather than a
|
|
151
|
+
* slightly wrong scrollbar.
|
|
152
|
+
*/
|
|
153
|
+
private measureRowHeights;
|
|
154
|
+
/**
|
|
155
|
+
* What counts as "the same row set" for the adoption budget: change the data,
|
|
156
|
+
* the grouping, the filters or the collapse state and the rows genuinely
|
|
157
|
+
* differ, so measuring again is warranted.
|
|
158
|
+
*/
|
|
159
|
+
private heightSignature;
|
|
160
|
+
private currentPlan;
|
|
72
161
|
private now;
|
|
162
|
+
/**
|
|
163
|
+
* Grouping, flattening and indexing for the current data, computed once and
|
|
164
|
+
* reused until something that affects them changes.
|
|
165
|
+
*
|
|
166
|
+
* The scroll path asks "did the window move?" on every frame that is not
|
|
167
|
+
* skipped, and answering it used to re-parse the whole `events` attribute,
|
|
168
|
+
* re-filter, re-group, re-flatten and rebuild the offset array — then
|
|
169
|
+
* `update()` did all of it again. That is O(n) twice per frame on a surface
|
|
170
|
+
* whose entire purpose is not being O(n) at scale.
|
|
171
|
+
*/
|
|
172
|
+
private rowCache;
|
|
173
|
+
private cachedRows;
|
|
73
174
|
private currentGroups;
|
|
74
175
|
private patchFacets;
|
|
75
176
|
private eventHtml;
|