@k2b/ui 0.27.0 → 0.28.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 +1 -1
- package/dist/base.css +147 -2
- package/dist/browser/actions/ContextMenu.js +30 -4
- package/dist/browser/actions/ContextMenu.js.map +1 -1
- package/dist/browser/actions/Dropdown.js +184 -158
- package/dist/browser/actions/Dropdown.js.map +1 -1
- package/dist/browser/actions/GestureMenu.js +71 -138
- package/dist/browser/actions/GestureMenu.js.map +1 -1
- package/dist/browser/actions/Toolbar.js +379 -48
- package/dist/browser/actions/Toolbar.js.map +1 -1
- package/dist/browser/actions/index.js.map +1 -1
- package/dist/browser/actions/menu-rows.js +15 -0
- package/dist/browser/actions/menu-rows.js.map +1 -0
- package/dist/browser/actions/menu-sheet.js +154 -0
- package/dist/browser/actions/menu-sheet.js.map +1 -0
- package/dist/browser/actions/toolbar-messages.js +20 -0
- package/dist/browser/actions/toolbar-messages.js.map +1 -0
- package/dist/browser/chart-inspection.js +3 -0
- package/dist/browser/chart-inspection.js.map +1 -0
- package/dist/browser/chat/ChatComposer.js +581 -397
- package/dist/browser/chat/ChatComposer.js.map +1 -1
- package/dist/browser/chat/chat-messages.js +19 -1
- package/dist/browser/chat/chat-messages.js.map +1 -1
- package/dist/browser/chat/command-search.js +3 -0
- package/dist/browser/chat/command-search.js.map +1 -1
- package/dist/browser/chat/index.js +2 -0
- package/dist/browser/chat/index.js.map +1 -1
- package/dist/browser/chat/reaction-people.js +15 -0
- package/dist/browser/chat/reaction-people.js.map +1 -0
- package/dist/browser/content/Chart.js +6 -15
- package/dist/browser/content/Chart.js.map +1 -1
- package/dist/browser/content/ChartSnapshotView.js +7 -10
- package/dist/browser/content/ChartSnapshotView.js.map +1 -1
- package/dist/browser/content/MessageRow.js +330 -448
- package/dist/browser/content/MessageRow.js.map +1 -1
- package/dist/browser/content/ReactionBar.js +419 -0
- package/dist/browser/content/ReactionBar.js.map +1 -0
- package/dist/browser/content/chart-inspection-core.js +557 -0
- package/dist/browser/content/chart-inspection-core.js.map +1 -0
- package/dist/browser/content/chart-inspection.js +10 -445
- package/dist/browser/content/chart-inspection.js.map +1 -1
- package/dist/browser/content/chart-line-gaps.js +1 -1
- package/dist/browser/content/chart-line-gaps.js.map +1 -1
- package/dist/browser/content/chart-snapshot.js +1 -1
- package/dist/browser/content/chart-snapshot.js.map +1 -1
- package/dist/browser/content/index.js +1 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/feedback/dialog-core.js +135 -23
- package/dist/browser/feedback/dialog-core.js.map +1 -1
- package/dist/browser/index.js +1 -0
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/input-mode.js +40 -0
- package/dist/browser/input-mode.js.map +1 -0
- package/dist/browser/inputs/FileDropTarget.js +24 -9
- package/dist/browser/inputs/FileDropTarget.js.map +1 -1
- package/dist/browser/inputs/FileInputs.js +2 -2
- package/dist/browser/inputs/FileInputs.js.map +1 -1
- package/dist/browser/inputs/SelectChip.js +9 -0
- package/dist/browser/inputs/SelectChip.js.map +1 -1
- package/dist/browser/inputs/emoji/EmojiPicker.js +13 -0
- package/dist/browser/inputs/emoji/EmojiPicker.js.map +1 -1
- package/dist/browser/inputs/file-drop.js.map +1 -1
- package/dist/browser/layout/DetailPanel.js +283 -197
- package/dist/browser/layout/DetailPanel.js.map +1 -1
- package/dist/browser/layout/Discussion.js +233 -185
- package/dist/browser/layout/Discussion.js.map +1 -1
- package/dist/browser/layout/TableOfContents.js +10 -2
- package/dist/browser/layout/TableOfContents.js.map +1 -1
- package/dist/browser/layout/detail-panel-messages.js +35 -0
- package/dist/browser/layout/detail-panel-messages.js.map +1 -0
- package/dist/browser/surfaces/Avatar.js +50 -20
- package/dist/browser/surfaces/Avatar.js.map +1 -1
- package/dist/browser/surfaces/AvatarStack.js +406 -0
- package/dist/browser/surfaces/AvatarStack.js.map +1 -0
- package/dist/browser/surfaces/avatar-messages.js +8 -2
- package/dist/browser/surfaces/avatar-messages.js.map +1 -1
- package/dist/browser/surfaces/index.js +1 -0
- package/dist/browser/surfaces/index.js.map +1 -1
- package/dist/ssr/actions/ContextMenu.js +21 -3
- package/dist/ssr/actions/ContextMenu.js.map +1 -1
- package/dist/ssr/actions/Dropdown.js +140 -72
- package/dist/ssr/actions/Dropdown.js.map +1 -1
- package/dist/ssr/actions/GestureMenu.js +52 -106
- package/dist/ssr/actions/GestureMenu.js.map +1 -1
- package/dist/ssr/actions/Toolbar.js +294 -14
- package/dist/ssr/actions/Toolbar.js.map +1 -1
- package/dist/ssr/actions/index.js.map +1 -1
- package/dist/ssr/actions/menu-rows.js +15 -0
- package/dist/ssr/actions/menu-rows.js.map +1 -0
- package/dist/ssr/actions/menu-sheet.js +138 -0
- package/dist/ssr/actions/menu-sheet.js.map +1 -0
- package/dist/ssr/actions/toolbar-messages.js +20 -0
- package/dist/ssr/actions/toolbar-messages.js.map +1 -0
- package/dist/ssr/chart-inspection.js +3 -0
- package/dist/ssr/chart-inspection.js.map +1 -0
- package/dist/ssr/chat/ChatComposer.js +263 -89
- package/dist/ssr/chat/ChatComposer.js.map +1 -1
- package/dist/ssr/chat/chat-messages.js +19 -1
- package/dist/ssr/chat/chat-messages.js.map +1 -1
- package/dist/ssr/chat/command-search.js +3 -0
- package/dist/ssr/chat/command-search.js.map +1 -1
- package/dist/ssr/chat/index.js +2 -0
- package/dist/ssr/chat/index.js.map +1 -1
- package/dist/ssr/chat/reaction-people.js +15 -0
- package/dist/ssr/chat/reaction-people.js.map +1 -0
- package/dist/ssr/content/Chart.js +6 -15
- package/dist/ssr/content/Chart.js.map +1 -1
- package/dist/ssr/content/ChartSnapshotView.js +7 -10
- package/dist/ssr/content/ChartSnapshotView.js.map +1 -1
- package/dist/ssr/content/MessageRow.js +98 -152
- package/dist/ssr/content/MessageRow.js.map +1 -1
- package/dist/ssr/content/ReactionBar.js +304 -0
- package/dist/ssr/content/ReactionBar.js.map +1 -0
- package/dist/ssr/content/chart-inspection-core.js +557 -0
- package/dist/ssr/content/chart-inspection-core.js.map +1 -0
- package/dist/ssr/content/chart-inspection.js +10 -445
- package/dist/ssr/content/chart-inspection.js.map +1 -1
- package/dist/ssr/content/chart-line-gaps.js +1 -1
- package/dist/ssr/content/chart-line-gaps.js.map +1 -1
- package/dist/ssr/content/chart-snapshot.js +1 -1
- package/dist/ssr/content/chart-snapshot.js.map +1 -1
- package/dist/ssr/content/index.js +1 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/feedback/dialog-core.js +135 -23
- package/dist/ssr/feedback/dialog-core.js.map +1 -1
- package/dist/ssr/index.js +1 -0
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/input-mode.js +40 -0
- package/dist/ssr/input-mode.js.map +1 -0
- package/dist/ssr/inputs/FileDropTarget.js +17 -5
- package/dist/ssr/inputs/FileDropTarget.js.map +1 -1
- package/dist/ssr/inputs/FileInputs.js +2 -2
- package/dist/ssr/inputs/FileInputs.js.map +1 -1
- package/dist/ssr/inputs/SelectChip.js +9 -0
- package/dist/ssr/inputs/SelectChip.js.map +1 -1
- package/dist/ssr/inputs/emoji/EmojiPicker.js +12 -0
- package/dist/ssr/inputs/emoji/EmojiPicker.js.map +1 -1
- package/dist/ssr/inputs/file-drop.js.map +1 -1
- package/dist/ssr/layout/DetailPanel.js +112 -58
- package/dist/ssr/layout/DetailPanel.js.map +1 -1
- package/dist/ssr/layout/Discussion.js +63 -36
- package/dist/ssr/layout/Discussion.js.map +1 -1
- package/dist/ssr/layout/TableOfContents.js +10 -2
- package/dist/ssr/layout/TableOfContents.js.map +1 -1
- package/dist/ssr/layout/detail-panel-messages.js +35 -0
- package/dist/ssr/layout/detail-panel-messages.js.map +1 -0
- package/dist/ssr/surfaces/Avatar.js +20 -2
- package/dist/ssr/surfaces/Avatar.js.map +1 -1
- package/dist/ssr/surfaces/AvatarStack.js +286 -0
- package/dist/ssr/surfaces/AvatarStack.js.map +1 -0
- package/dist/ssr/surfaces/avatar-messages.js +8 -2
- package/dist/ssr/surfaces/avatar-messages.js.map +1 -1
- package/dist/ssr/surfaces/index.js +1 -0
- package/dist/ssr/surfaces/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/actions/ContextMenu.d.ts +8 -0
- package/dist/types/actions/Dropdown.d.ts +12 -5
- package/dist/types/actions/GestureMenu.d.ts +6 -4
- package/dist/types/actions/Toolbar.d.ts +20 -0
- package/dist/types/actions/index.d.ts +1 -1
- package/dist/types/actions/menu-rows.d.ts +5 -0
- package/dist/types/actions/menu-sheet.d.ts +22 -0
- package/dist/types/actions/toolbar-messages.d.ts +8 -0
- package/dist/types/chart-inspection.d.ts +2 -0
- package/dist/types/chat/ChatComposer.d.ts +7 -0
- package/dist/types/chat/chat-messages.d.ts +10 -2
- package/dist/types/chat/command-search.d.ts +2 -0
- package/dist/types/chat/index.d.ts +2 -0
- package/dist/types/chat/reaction-people.d.ts +9 -0
- package/dist/types/content/Chart.d.ts +2 -2
- package/dist/types/content/MessageRow.d.ts +3 -10
- package/dist/types/content/ReactionBar.d.ts +32 -0
- package/dist/types/content/chart-inspection-core.d.ts +61 -0
- package/dist/types/content/chart-inspection.d.ts +4 -20
- package/dist/types/content/chart-snapshot.d.ts +1 -1
- package/dist/types/content/index.d.ts +2 -1
- package/dist/types/feedback/dialog-core.d.ts +16 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/input-mode.d.ts +7 -0
- package/dist/types/inputs/FileDropTarget.d.ts +6 -3
- package/dist/types/inputs/SelectChip.d.ts +2 -0
- package/dist/types/inputs/file-drop.d.ts +2 -0
- package/dist/types/layout/DetailPanel.d.ts +11 -0
- package/dist/types/layout/Discussion.d.ts +8 -0
- package/dist/types/layout/TableOfContents.d.ts +2 -0
- package/dist/types/layout/detail-panel-messages.d.ts +15 -0
- package/dist/types/surfaces/Avatar.d.ts +12 -0
- package/dist/types/surfaces/AvatarStack.d.ts +41 -0
- package/dist/types/surfaces/avatar-messages.d.ts +3 -0
- package/dist/types/surfaces/index.d.ts +3 -1
- package/package.json +6 -1
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import { positionTooltipSurface } from "../feedback/tooltip-position";
|
|
2
|
+
export const sameChartDatum = (a, b) => a.role === b.role && a.index === b.index && a.seriesIndex === b.seriesIndex;
|
|
3
|
+
const object = value => typeof value === "object" && value !== null;
|
|
4
|
+
const pair = value => Array.isArray(value) && value.length === 2 && value.every(n => typeof n === "number" && Number.isFinite(n));
|
|
5
|
+
const roles = new Set(["point", "item", "bin", "box", "outlier", "value", "cell", "interval"]);
|
|
6
|
+
export function isChartDatum(value) {
|
|
7
|
+
return object(value) && Number.isInteger(value.index) && typeof value.index === "number" && value.index >= 0 && (value.seriesIndex === undefined || typeof value.seriesIndex === "number" && Number.isInteger(value.seriesIndex) && value.seriesIndex >= 0) && typeof value.role === "string" && roles.has(value.role) && pair(value.anchor) && (value.grid === undefined || pair(value.grid)) && (value.label === undefined || typeof value.label === "string") && Array.isArray(value.values) && value.values.every(field => object(field) && typeof field.key === "string" && (typeof field.value === "string" || typeof field.value === "number" && Number.isFinite(field.value)) && (field.formatted === undefined || typeof field.formatted === "string"));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Adds or removes only the tooltip id, keeping descriptions the host put on the container. */
|
|
11
|
+
const describe = (container, id, on) => {
|
|
12
|
+
const ids = (container.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(token => token && token !== id);
|
|
13
|
+
if (on) ids.push(id);
|
|
14
|
+
if (ids.length) container.setAttribute("aria-describedby", ids.join(" "));else container.removeAttribute("aria-describedby");
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Enhances existing SVG nodes. Pointer/focus changes never rebuild the chart. */
|
|
18
|
+
export function createChartInspector(options) {
|
|
19
|
+
const source = Symbol("chart cursor");
|
|
20
|
+
let entries;
|
|
21
|
+
let sortedPoints = [];
|
|
22
|
+
const byElement = new Map();
|
|
23
|
+
let active;
|
|
24
|
+
let highlighted = [];
|
|
25
|
+
let pinned = false;
|
|
26
|
+
let pointerStart;
|
|
27
|
+
let moved = false;
|
|
28
|
+
let pending;
|
|
29
|
+
let frame;
|
|
30
|
+
// The cursor this chart is subscribed to. Closing reads it instead of the cursor prop: a chart also closes
|
|
31
|
+
// while it unmounts, when a parent `<Show>` may already have dropped the value the prop came from.
|
|
32
|
+
let linkedCursor;
|
|
33
|
+
const records = () => {
|
|
34
|
+
if (!entries || entries.length > 0 && !entries[0].element.isConnected) {
|
|
35
|
+
entries = [];
|
|
36
|
+
byElement.clear();
|
|
37
|
+
sortedPoints = [];
|
|
38
|
+
for (const element of Array.from(options.container()?.querySelectorAll("[data-chart-datum]") ?? [])) {
|
|
39
|
+
try {
|
|
40
|
+
const value = JSON.parse(element.getAttribute("data-chart-datum") ?? "null");
|
|
41
|
+
if (isChartDatum(value)) {
|
|
42
|
+
const entry = {
|
|
43
|
+
element,
|
|
44
|
+
datum: value
|
|
45
|
+
};
|
|
46
|
+
entries.push(entry);
|
|
47
|
+
byElement.set(element, entry);
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
/* Only valid renderer metadata participates in inspection. */
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
sortedPoints = entries.filter(entry => entry.datum.role === "point").sort((a, b) => a.datum.anchor[0] - b.datum.anchor[0]);
|
|
54
|
+
}
|
|
55
|
+
return entries;
|
|
56
|
+
};
|
|
57
|
+
const highlightColor = item => {
|
|
58
|
+
// Read the actual paint before applying inspection styles, including
|
|
59
|
+
// custom series and threshold colors. Invisible points use their line.
|
|
60
|
+
const point = item.element.querySelector(".stdlib-chart-inspection-point");
|
|
61
|
+
const source = point ? options.container()?.querySelector(options.kind() === "line" ? `.stdlib-chart-line.stdlib-chart-series-${(item.datum.seriesIndex ?? 0) % 8}` : ".stdlib-chart-sparkline, .stdlib-chart-stat-sparkline") : item.element.querySelector(".stdlib-chart-bar-gauge-fill, .stdlib-chart-gauge-fill, .stdlib-chart-stat-value") ?? item.element.querySelector("rect, path, circle");
|
|
62
|
+
const paint = source ? source.ownerDocument.defaultView?.getComputedStyle(source) : undefined;
|
|
63
|
+
const color = (point || item.element.hasAttribute("data-chart-reference") ? paint?.stroke : paint?.fill) || `var(--stdlib-chart-c${(item.datum.seriesIndex ?? 0) % 8 + 1})`;
|
|
64
|
+
return `color-mix(in srgb, ${color} 72%, var(--k2b-chart-highlight-mix))`;
|
|
65
|
+
};
|
|
66
|
+
const selectData = selected => {
|
|
67
|
+
for (const entry of records()) {
|
|
68
|
+
const matches = selected.some(datum => sameChartDatum(datum, entry.datum));
|
|
69
|
+
if (matches) entry.element.style.setProperty("--k2b-chart-selection-color", highlightColor(entry));else entry.element.style.removeProperty("--k2b-chart-selection-color");
|
|
70
|
+
entry.element.toggleAttribute("data-selected", matches);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const screenPoint = entry => {
|
|
74
|
+
const matrix = entry.element.getScreenCTM?.();
|
|
75
|
+
const [x, y] = entry.datum.anchor;
|
|
76
|
+
if (matrix) return {
|
|
77
|
+
x: matrix.a * x + matrix.c * y + matrix.e,
|
|
78
|
+
y: matrix.b * x + matrix.d * y + matrix.f
|
|
79
|
+
};
|
|
80
|
+
const rect = entry.element.getBoundingClientRect();
|
|
81
|
+
return {
|
|
82
|
+
x: rect.x + rect.width / 2,
|
|
83
|
+
y: rect.y + rect.height / 2
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
const visibleRecords = () => {
|
|
87
|
+
const nodes = records();
|
|
88
|
+
if (options.kind() !== "map") return nodes;
|
|
89
|
+
const viewport = options.container()?.querySelector(".stdlib-chart-map-viewport");
|
|
90
|
+
if (!viewport?.getScreenCTM?.()) return nodes;
|
|
91
|
+
const root = viewport.ownerSVGElement?.getBoundingClientRect();
|
|
92
|
+
if (!root?.width || !root.height) return nodes;
|
|
93
|
+
const width = viewport?.width?.baseVal.value;
|
|
94
|
+
const height = viewport?.height?.baseVal.value;
|
|
95
|
+
if (!width || !height) return nodes;
|
|
96
|
+
const left = root.left + viewport.x.baseVal.value;
|
|
97
|
+
const top = root.top + viewport.y.baseVal.value;
|
|
98
|
+
const clip = {
|
|
99
|
+
left,
|
|
100
|
+
top,
|
|
101
|
+
right: left + width,
|
|
102
|
+
bottom: top + height
|
|
103
|
+
};
|
|
104
|
+
return nodes.filter(entry => {
|
|
105
|
+
const point = screenPoint(entry);
|
|
106
|
+
return point.x >= clip.left && point.x <= clip.right && point.y >= clip.top && point.y <= clip.bottom;
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
const rawX = entry => entry.datum.values.find(field => field.key === "x")?.value;
|
|
110
|
+
const seriesChart = () => options.kind() === "line" || options.kind() === "sparkline";
|
|
111
|
+
const clearHighlight = () => {
|
|
112
|
+
for (const entry of highlighted) {
|
|
113
|
+
entry.element.removeAttribute("data-inspected");
|
|
114
|
+
entry.element.style.removeProperty("--k2b-chart-inspection-color");
|
|
115
|
+
if (!entry.element.getAttribute("style")) entry.element.removeAttribute("style");
|
|
116
|
+
}
|
|
117
|
+
highlighted = [];
|
|
118
|
+
};
|
|
119
|
+
const close = (broadcast = true, cancelPending = true) => {
|
|
120
|
+
const state = linkedCursor?.read();
|
|
121
|
+
if (broadcast && state && state.source !== source) return;
|
|
122
|
+
if (broadcast) linkedCursor?.clear(source);
|
|
123
|
+
options.container()?.removeAttribute("data-cursor-source");
|
|
124
|
+
pinned = false;
|
|
125
|
+
active = undefined;
|
|
126
|
+
clearHighlight();
|
|
127
|
+
const container = options.container(),
|
|
128
|
+
id = options.tooltip()?.id;
|
|
129
|
+
if (container && id) describe(container, id, false);
|
|
130
|
+
options.container()?.removeAttribute("data-inspecting");
|
|
131
|
+
const tooltip = options.tooltip();
|
|
132
|
+
if (tooltip) {
|
|
133
|
+
try {
|
|
134
|
+
if (tooltip.matches(":popover-open")) tooltip.hidePopover();
|
|
135
|
+
} catch {
|
|
136
|
+
/* Detached during navigation. */
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (cancelPending) {
|
|
140
|
+
if (frame !== undefined) cancelAnimationFrame(frame);
|
|
141
|
+
frame = undefined;
|
|
142
|
+
pending = undefined;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const invalidate = () => {
|
|
146
|
+
options.cursor?.()?.clear(source);
|
|
147
|
+
close(false);
|
|
148
|
+
entries = undefined;
|
|
149
|
+
sortedPoints = [];
|
|
150
|
+
byElement.clear();
|
|
151
|
+
receive(options.cursor?.()?.read() ?? null);
|
|
152
|
+
};
|
|
153
|
+
const show = (entry, broadcast = true) => {
|
|
154
|
+
const x = rawX(entry);
|
|
155
|
+
if (broadcast && options.kind() === "line" && typeof x === "number") options.cursor?.()?.move(source, x);
|
|
156
|
+
const tooltip = options.tooltip(),
|
|
157
|
+
anchor = options.anchor(),
|
|
158
|
+
container = options.container();
|
|
159
|
+
if (!tooltip || !anchor || !container || !options.enabled()) return;
|
|
160
|
+
if (active === entry && tooltip.matches(":popover-open")) return;
|
|
161
|
+
clearHighlight();
|
|
162
|
+
active = entry;
|
|
163
|
+
highlighted = options.kind() === "line" ? records().filter(candidate => rawX(candidate) === rawX(entry)) : [entry];
|
|
164
|
+
for (const item of highlighted) {
|
|
165
|
+
item.element.style.setProperty("--k2b-chart-inspection-color", highlightColor(item));
|
|
166
|
+
item.element.setAttribute("data-inspected", "true");
|
|
167
|
+
}
|
|
168
|
+
const document = tooltip.ownerDocument;
|
|
169
|
+
const contents = highlighted.map(item => ({
|
|
170
|
+
item,
|
|
171
|
+
content: options.format({
|
|
172
|
+
kind: options.kind(),
|
|
173
|
+
datum: item.datum
|
|
174
|
+
}),
|
|
175
|
+
missing: false
|
|
176
|
+
}));
|
|
177
|
+
if (options.cursor?.() && options.kind() === "line") {
|
|
178
|
+
const present = new Set(highlighted.map(item => item.datum.seriesIndex));
|
|
179
|
+
for (const item of records()) {
|
|
180
|
+
if (present.has(item.datum.seriesIndex)) continue;
|
|
181
|
+
present.add(item.datum.seriesIndex);
|
|
182
|
+
contents.push({
|
|
183
|
+
item,
|
|
184
|
+
content: options.format({
|
|
185
|
+
kind: options.kind(),
|
|
186
|
+
datum: item.datum
|
|
187
|
+
}),
|
|
188
|
+
missing: true
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
contents.sort((a, b) => (a.item.datum.seriesIndex ?? 0) - (b.item.datum.seriesIndex ?? 0));
|
|
192
|
+
}
|
|
193
|
+
tooltip.replaceChildren();
|
|
194
|
+
const sharedTitle = typeof x === "number" ? options.cursor?.()?.formatX?.(x) : undefined;
|
|
195
|
+
const commonTitle = sharedTitle ?? (contents.every(({
|
|
196
|
+
content
|
|
197
|
+
}) => content.title === contents[0]?.content.title) ? contents[0]?.content.title : undefined);
|
|
198
|
+
if (commonTitle) {
|
|
199
|
+
const title = document.createElement("strong");
|
|
200
|
+
title.className = "k2b-chart__tooltip-title";
|
|
201
|
+
title.textContent = commonTitle;
|
|
202
|
+
tooltip.append(title);
|
|
203
|
+
}
|
|
204
|
+
for (const {
|
|
205
|
+
item,
|
|
206
|
+
content,
|
|
207
|
+
missing
|
|
208
|
+
} of contents) {
|
|
209
|
+
if (content.title && !commonTitle) {
|
|
210
|
+
const title = document.createElement("strong");
|
|
211
|
+
title.className = "k2b-chart__tooltip-title";
|
|
212
|
+
title.textContent = content.title;
|
|
213
|
+
tooltip.append(title);
|
|
214
|
+
}
|
|
215
|
+
for (const row of content.rows) {
|
|
216
|
+
const line = document.createElement("span");
|
|
217
|
+
line.className = "k2b-chart__tooltip-row";
|
|
218
|
+
if (options.kind() === "line") {
|
|
219
|
+
const swatch = document.createElement("i");
|
|
220
|
+
swatch.setAttribute("aria-hidden", "true");
|
|
221
|
+
const series = container.querySelector(`.stdlib-chart-line.stdlib-chart-series-${(item.datum.seriesIndex ?? 0) % 8}`);
|
|
222
|
+
swatch.style.background = series ? getComputedStyle(series).stroke : `var(--stdlib-chart-c${(item.datum.seriesIndex ?? 0) % 8 + 1})`;
|
|
223
|
+
line.append(swatch);
|
|
224
|
+
}
|
|
225
|
+
const label = document.createElement("span");
|
|
226
|
+
label.textContent = `${row.label}: `;
|
|
227
|
+
const value = document.createElement("b");
|
|
228
|
+
value.textContent = missing ? "—" : row.value;
|
|
229
|
+
line.append(label, value);
|
|
230
|
+
tooltip.append(line);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const rect = container.getBoundingClientRect(),
|
|
234
|
+
point = screenPoint(entry);
|
|
235
|
+
anchor.style.left = `${point.x - rect.left}px`;
|
|
236
|
+
anchor.style.top = `${point.y - rect.top}px`;
|
|
237
|
+
updatePlotBounds();
|
|
238
|
+
container.style.setProperty("--k2b-chart-inspection-x", `${point.x - rect.left}px`);
|
|
239
|
+
container.setAttribute("data-inspecting", seriesChart() ? "series" : "datum");
|
|
240
|
+
describe(container, tooltip.id, true);
|
|
241
|
+
try {
|
|
242
|
+
if (!tooltip.matches(":popover-open")) tooltip.showPopover();
|
|
243
|
+
positionTooltipSurface(tooltip, anchor);
|
|
244
|
+
} catch {
|
|
245
|
+
/* SVG titles remain available without the Popover API. */
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
const updatePlotBounds = () => {
|
|
249
|
+
const container = options.container();
|
|
250
|
+
if (!container) return;
|
|
251
|
+
const axes = Array.from(container.querySelectorAll(".stdlib-chart-axis")).map(axis => axis.getBoundingClientRect());
|
|
252
|
+
if (axes.length < 2) return;
|
|
253
|
+
const left = Math.min(...axes.map(a => a.left)),
|
|
254
|
+
right = Math.max(...axes.map(a => a.right));
|
|
255
|
+
const top = Math.min(...axes.map(a => a.top)),
|
|
256
|
+
bottom = Math.max(...axes.map(a => a.bottom));
|
|
257
|
+
if (right <= left || bottom <= top) return;
|
|
258
|
+
const rect = container.getBoundingClientRect();
|
|
259
|
+
for (const [name, value] of Object.entries({
|
|
260
|
+
left: left - rect.left,
|
|
261
|
+
right: rect.right - right,
|
|
262
|
+
top: top - rect.top,
|
|
263
|
+
bottom: rect.bottom - bottom
|
|
264
|
+
})) container.style.setProperty(`--k2b-chart-plot-${name}`, `${value}px`);
|
|
265
|
+
return {
|
|
266
|
+
left,
|
|
267
|
+
right,
|
|
268
|
+
top,
|
|
269
|
+
bottom
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
const receive = state => {
|
|
273
|
+
if (state?.source === source) return;
|
|
274
|
+
close(false, false);
|
|
275
|
+
if (!state || options.kind() !== "line") return;
|
|
276
|
+
const entry = records().find(item => rawX(item) === state.x);
|
|
277
|
+
if (entry) show(entry, false);
|
|
278
|
+
};
|
|
279
|
+
const linkCursor = cursor => {
|
|
280
|
+
linkedCursor = cursor;
|
|
281
|
+
const unsubscribe = cursor?.subscribe(receive);
|
|
282
|
+
return () => {
|
|
283
|
+
unsubscribe?.();
|
|
284
|
+
cursor?.clear(source);
|
|
285
|
+
linkedCursor = undefined;
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
const nearest = (x, y, target) => {
|
|
289
|
+
if (target?.closest("button,a") && !target.closest("[data-chart-datum]")) return;
|
|
290
|
+
const nodes = visibleRecords();
|
|
291
|
+
// A mark wins before the plot bounds: the first and last points of a line sit on the plot's edges, so half of
|
|
292
|
+
// their hit area lies outside it.
|
|
293
|
+
const direct = target?.closest("[data-chart-datum]");
|
|
294
|
+
const hit = direct ? byElement.get(direct) : undefined;
|
|
295
|
+
if (hit && nodes.includes(hit)) return hit;
|
|
296
|
+
if (seriesChart()) {
|
|
297
|
+
const plot = updatePlotBounds();
|
|
298
|
+
if (plot && (x < plot.left || x > plot.right || y < plot.top || y > plot.bottom)) return;
|
|
299
|
+
}
|
|
300
|
+
// Exact shapes win. A small physical hit radius also reaches tiny or
|
|
301
|
+
// zero-sized marks; distant whitespace leaves the chart unselected.
|
|
302
|
+
const points = seriesChart() ? sortedPoints : nodes;
|
|
303
|
+
if (seriesChart() && points.length) {
|
|
304
|
+
const matrix = points[0].element.getScreenCTM?.();
|
|
305
|
+
if (matrix) {
|
|
306
|
+
const determinant = matrix.a * matrix.d - matrix.b * matrix.c;
|
|
307
|
+
if (!determinant) return;
|
|
308
|
+
const targetX = ((x - matrix.e) * matrix.d - (y - matrix.f) * matrix.c) / determinant;
|
|
309
|
+
let low = 0,
|
|
310
|
+
high = points.length - 1;
|
|
311
|
+
while (low < high) {
|
|
312
|
+
const mid = Math.floor((low + high) / 2);
|
|
313
|
+
if (points[mid].datum.anchor[0] < targetX) low = mid + 1;else high = mid;
|
|
314
|
+
}
|
|
315
|
+
const previous = Math.max(0, low - 1);
|
|
316
|
+
return Math.abs(points[previous].datum.anchor[0] - targetX) <= Math.abs(points[low].datum.anchor[0] - targetX) ? points[previous] : points[low];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const matrices = new Map();
|
|
320
|
+
let best,
|
|
321
|
+
distance = Infinity;
|
|
322
|
+
for (const entry of points) {
|
|
323
|
+
const svg = entry.element.ownerSVGElement;
|
|
324
|
+
if (svg && !matrices.has(svg)) matrices.set(svg, svg.getScreenCTM?.() ?? null);
|
|
325
|
+
const matrix = svg ? matrices.get(svg) : null;
|
|
326
|
+
const [ax, ay] = entry.datum.anchor;
|
|
327
|
+
const point = matrix ? {
|
|
328
|
+
x: matrix.a * ax + matrix.c * ay + matrix.e,
|
|
329
|
+
y: matrix.b * ax + matrix.d * ay + matrix.f
|
|
330
|
+
} : screenPoint(entry);
|
|
331
|
+
const dx = point.x - x,
|
|
332
|
+
dy = point.y - y;
|
|
333
|
+
const next = seriesChart() ? Math.abs(dx) : Math.hypot(dx, dy);
|
|
334
|
+
if (next < distance) {
|
|
335
|
+
distance = next;
|
|
336
|
+
best = entry;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return seriesChart() || distance <= 24 ? best : undefined;
|
|
340
|
+
};
|
|
341
|
+
const move = event => {
|
|
342
|
+
if (event.target instanceof Node && options.tooltip()?.contains(event.target)) return;
|
|
343
|
+
if (pointerStart && Math.hypot(event.clientX - pointerStart.x, event.clientY - pointerStart.y) > 6) moved = true;
|
|
344
|
+
if (!options.enabled() || pinned || event.pointerType === "touch" || event.buttons) return;
|
|
345
|
+
pending = {
|
|
346
|
+
x: event.clientX,
|
|
347
|
+
y: event.clientY,
|
|
348
|
+
target: event.target instanceof Element ? event.target : null
|
|
349
|
+
};
|
|
350
|
+
if (frame !== undefined) return;
|
|
351
|
+
frame = requestAnimationFrame(() => {
|
|
352
|
+
frame = undefined;
|
|
353
|
+
const sample = pending;
|
|
354
|
+
pending = undefined;
|
|
355
|
+
if (!sample) return;
|
|
356
|
+
const entry = nearest(sample.x, sample.y, sample.target);
|
|
357
|
+
if (entry) {
|
|
358
|
+
show(entry);
|
|
359
|
+
if (seriesChart()) {
|
|
360
|
+
const container = options.container();
|
|
361
|
+
container?.setAttribute("data-cursor-source", "true");
|
|
362
|
+
container?.style.setProperty("--k2b-chart-inspection-y", `${sample.y - container.getBoundingClientRect().top}px`);
|
|
363
|
+
}
|
|
364
|
+
} else close();
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
const down = event => {
|
|
368
|
+
pointerStart = {
|
|
369
|
+
x: event.clientX,
|
|
370
|
+
y: event.clientY
|
|
371
|
+
};
|
|
372
|
+
moved = false;
|
|
373
|
+
};
|
|
374
|
+
const click = event => {
|
|
375
|
+
if (!options.enabled() || moved || event.target instanceof Element && event.target.closest("button,a")) return;
|
|
376
|
+
const entry = nearest(event.clientX, event.clientY, event.target instanceof Element ? event.target : null);
|
|
377
|
+
if (!entry) {
|
|
378
|
+
close();
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
pinned = true;
|
|
382
|
+
show(entry);
|
|
383
|
+
options.select({
|
|
384
|
+
kind: options.kind(),
|
|
385
|
+
datum: entry.datum
|
|
386
|
+
});
|
|
387
|
+
};
|
|
388
|
+
const focus = event => {
|
|
389
|
+
if (event.target !== options.container() || !options.enabled()) return;
|
|
390
|
+
const nodes = visibleRecords();
|
|
391
|
+
const first = options.kind() === "line" ? sortedPoints.at(-1) : nodes[0];
|
|
392
|
+
if (first) show(first);
|
|
393
|
+
};
|
|
394
|
+
const key = event => {
|
|
395
|
+
if (!options.enabled() || event.target !== options.container()) return false;
|
|
396
|
+
if (event.key === "Escape") {
|
|
397
|
+
close();
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
if (event.key === "Enter" && active) {
|
|
401
|
+
if (options.kind() === "map" && !visibleRecords().includes(active)) {
|
|
402
|
+
close();
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
event.preventDefault();
|
|
406
|
+
pinned = true;
|
|
407
|
+
const link = active.element.closest("a");
|
|
408
|
+
if (link) link.dispatchEvent(new MouseEvent("click", {
|
|
409
|
+
bubbles: true,
|
|
410
|
+
cancelable: true
|
|
411
|
+
}));else options.select({
|
|
412
|
+
kind: options.kind(),
|
|
413
|
+
datum: active.datum
|
|
414
|
+
});
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
const navigationChart = options.kind() === "map" || options.kind() === "stateTimeline";
|
|
418
|
+
if (navigationChart && !event.altKey) return false;
|
|
419
|
+
if (!["Home", "End", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)) return false;
|
|
420
|
+
let nodes = visibleRecords();
|
|
421
|
+
if (seriesChart()) nodes = [...nodes].sort((a, b) => a.datum.anchor[0] - b.datum.anchor[0]);
|
|
422
|
+
if (options.kind() === "line") {
|
|
423
|
+
const seen = new Set();
|
|
424
|
+
nodes = nodes.filter(entry => {
|
|
425
|
+
const x = rawX(entry);
|
|
426
|
+
if (seen.has(x)) return false;
|
|
427
|
+
seen.add(x);
|
|
428
|
+
return true;
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
if (!nodes.length) return false;
|
|
432
|
+
event.preventDefault();
|
|
433
|
+
pinned = false;
|
|
434
|
+
let index = active ? options.kind() === "line" ? nodes.findIndex(entry => rawX(entry) === rawX(active)) : nodes.indexOf(active) : -1;
|
|
435
|
+
if (event.key === "Home") index = 0;else if (event.key === "End") index = nodes.length - 1;else if (active?.datum.grid && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)) {
|
|
436
|
+
const [x, y] = active.datum.grid;
|
|
437
|
+
const dx = event.key === "ArrowLeft" ? -1 : event.key === "ArrowRight" ? 1 : 0;
|
|
438
|
+
const dy = event.key === "ArrowUp" ? -1 : event.key === "ArrowDown" ? 1 : 0;
|
|
439
|
+
const next = nodes.filter(entry => entry.datum.grid && (dx ? entry.datum.grid[1] === y && (entry.datum.grid[0] - x) * dx > 0 : entry.datum.grid[0] === x && (entry.datum.grid[1] - y) * dy > 0)).sort((a, b) => Math.abs(a.datum.grid[0] - x) + Math.abs(a.datum.grid[1] - y) - Math.abs(b.datum.grid[0] - x) - Math.abs(b.datum.grid[1] - y))[0];
|
|
440
|
+
if (next) index = nodes.indexOf(next);
|
|
441
|
+
} else index += event.key === "ArrowLeft" || event.key === "ArrowUp" ? -1 : 1;
|
|
442
|
+
show(nodes[Math.max(0, Math.min(nodes.length - 1, index))]);
|
|
443
|
+
return true;
|
|
444
|
+
};
|
|
445
|
+
return {
|
|
446
|
+
selectDatum: selected => selectData(selected ? [selected] : []),
|
|
447
|
+
selectData,
|
|
448
|
+
linkCursor,
|
|
449
|
+
destroy: () => close(false),
|
|
450
|
+
move,
|
|
451
|
+
down,
|
|
452
|
+
click,
|
|
453
|
+
focus,
|
|
454
|
+
key,
|
|
455
|
+
close: () => close(),
|
|
456
|
+
invalidate,
|
|
457
|
+
leave: () => {
|
|
458
|
+
if (!pinned) close();
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** Dismiss inspection on outside interaction, but allow the tooltip itself to scroll. */
|
|
464
|
+
export function closeChartInspectionOnOutside(options) {
|
|
465
|
+
const outside = event => {
|
|
466
|
+
if (event.target instanceof Node && !options.container()?.contains(event.target)) options.close();
|
|
467
|
+
};
|
|
468
|
+
const scroll = event => {
|
|
469
|
+
if (event.target instanceof Node && options.tooltip()?.contains(event.target)) return;
|
|
470
|
+
options.close();
|
|
471
|
+
};
|
|
472
|
+
window.addEventListener("pointerdown", outside);
|
|
473
|
+
window.addEventListener("scroll", scroll, true);
|
|
474
|
+
window.addEventListener("resize", options.close);
|
|
475
|
+
return () => {
|
|
476
|
+
window.removeEventListener("pointerdown", outside);
|
|
477
|
+
window.removeEventListener("scroll", scroll, true);
|
|
478
|
+
window.removeEventListener("resize", options.close);
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
let tooltipId = 0;
|
|
482
|
+
|
|
483
|
+
/** Attach inspection to stdlib SVG rendered with inspect: true, without a framework. */
|
|
484
|
+
export function inspectChart(container, options) {
|
|
485
|
+
const document = container.ownerDocument;
|
|
486
|
+
const anchor = document.createElement("span");
|
|
487
|
+
anchor.className = "k2b-chart__anchor";
|
|
488
|
+
anchor.setAttribute("aria-hidden", "true");
|
|
489
|
+
const tooltip = document.createElement("span");
|
|
490
|
+
tooltip.className = "k2b-tooltip k2b-chart__tooltip";
|
|
491
|
+
tooltip.setAttribute("role", "tooltip");
|
|
492
|
+
tooltip.setAttribute("popover", "manual");
|
|
493
|
+
tooltip.setAttribute("data-instant", "true");
|
|
494
|
+
do {
|
|
495
|
+
tooltip.id = `k2b-chart-tooltip-${++tooltipId}`;
|
|
496
|
+
} while (document.getElementById(tooltip.id));
|
|
497
|
+
container.append(anchor, tooltip);
|
|
498
|
+
const addedTabindex = !container.hasAttribute("tabindex");
|
|
499
|
+
if (addedTabindex) container.setAttribute("tabindex", "0");
|
|
500
|
+
const inspector = createChartInspector({
|
|
501
|
+
container: () => container,
|
|
502
|
+
cursor: () => options.cursor,
|
|
503
|
+
tooltip: () => tooltip,
|
|
504
|
+
anchor: () => anchor,
|
|
505
|
+
kind: () => options.kind,
|
|
506
|
+
enabled: () => true,
|
|
507
|
+
format: options.format,
|
|
508
|
+
select: selection => options.select?.(selection)
|
|
509
|
+
});
|
|
510
|
+
container.addEventListener("pointerdown", inspector.down);
|
|
511
|
+
container.addEventListener("pointermove", inspector.move);
|
|
512
|
+
container.addEventListener("click", inspector.click);
|
|
513
|
+
container.addEventListener("pointerleave", inspector.leave);
|
|
514
|
+
container.addEventListener("focusin", inspector.focus);
|
|
515
|
+
container.addEventListener("focusout", inspector.close);
|
|
516
|
+
container.addEventListener("keydown", inspector.key);
|
|
517
|
+
const removeOutside = closeChartInspectionOnOutside({
|
|
518
|
+
container: () => container,
|
|
519
|
+
tooltip: () => tooltip,
|
|
520
|
+
close: inspector.close
|
|
521
|
+
});
|
|
522
|
+
const unlinkCursor = inspector.linkCursor(options.cursor);
|
|
523
|
+
let destroyed = false;
|
|
524
|
+
return {
|
|
525
|
+
invalidate: () => {
|
|
526
|
+
if (!destroyed) inspector.invalidate();
|
|
527
|
+
},
|
|
528
|
+
close: () => {
|
|
529
|
+
if (!destroyed) inspector.close();
|
|
530
|
+
},
|
|
531
|
+
destroy() {
|
|
532
|
+
if (destroyed) return;
|
|
533
|
+
destroyed = true;
|
|
534
|
+
container.removeEventListener("pointerdown", inspector.down);
|
|
535
|
+
container.removeEventListener("pointermove", inspector.move);
|
|
536
|
+
container.removeEventListener("click", inspector.click);
|
|
537
|
+
container.removeEventListener("pointerleave", inspector.leave);
|
|
538
|
+
container.removeEventListener("focusin", inspector.focus);
|
|
539
|
+
container.removeEventListener("focusout", inspector.close);
|
|
540
|
+
container.removeEventListener("keydown", inspector.key);
|
|
541
|
+
removeOutside();
|
|
542
|
+
unlinkCursor();
|
|
543
|
+
inspector.destroy();
|
|
544
|
+
for (const element of container.querySelectorAll("[data-chart-datum]")) {
|
|
545
|
+
element.removeAttribute("data-inspected");
|
|
546
|
+
element.style.removeProperty("--k2b-chart-inspection-color");
|
|
547
|
+
if (!element.getAttribute("style")) element.removeAttribute("style");
|
|
548
|
+
}
|
|
549
|
+
// Inspection geometry the inspector wrote onto the host container.
|
|
550
|
+
for (const name of ["inspection-x", "inspection-y", "plot-left", "plot-right", "plot-top", "plot-bottom"]) container.style.removeProperty(`--k2b-chart-${name}`);
|
|
551
|
+
anchor.remove();
|
|
552
|
+
tooltip.remove();
|
|
553
|
+
if (addedTabindex) container.removeAttribute("tabindex");
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
//# sourceMappingURL=chart-inspection-core.js.map
|