@bicharts/chart-host 0.6.75 → 0.6.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/card.mjs ADDED
@@ -0,0 +1,234 @@
1
+ /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
+ import {
3
+ computeSelectionCard
4
+ } from "./chunk-QOKQUXVV.mjs";
5
+ import "./chunk-H2P63UMG.mjs";
6
+ import "./chunk-GHODWOAL.mjs";
7
+
8
+ // src/selectionCardChrome.ts
9
+ var CARD_CLASS = "lch-selcard";
10
+ var MAX_TILE_FRACTION = 0.3;
11
+ function createSelectionCards(deps) {
12
+ const { container } = deps;
13
+ const log = deps.log ?? (() => {
14
+ });
15
+ let transient = null;
16
+ const pinned = [];
17
+ let anchor = { x: 0, y: 0 };
18
+ let disposed = false;
19
+ const onPointer = (ev) => {
20
+ try {
21
+ const r = container.getBoundingClientRect();
22
+ anchor = { x: ev.clientX - r.left, y: ev.clientY - r.top };
23
+ } catch {
24
+ }
25
+ };
26
+ container.addEventListener("click", onPointer, true);
27
+ const onKey = (ev) => {
28
+ if (ev.key !== "Escape") return;
29
+ clearAll();
30
+ };
31
+ document.addEventListener("keydown", onKey);
32
+ function removeCard(c) {
33
+ if (!c) return;
34
+ try {
35
+ c.el.remove();
36
+ } catch {
37
+ }
38
+ }
39
+ function clearTransient() {
40
+ removeCard(transient);
41
+ transient = null;
42
+ }
43
+ function clearAll() {
44
+ clearTransient();
45
+ while (pinned.length) removeCard(pinned.pop());
46
+ }
47
+ function place(el, at) {
48
+ const cw = container.clientWidth || 0;
49
+ const ch = container.clientHeight || 0;
50
+ if (!cw || !ch) return;
51
+ const w = el.offsetWidth || 0;
52
+ const h = el.offsetHeight || 0;
53
+ const gap = 12, edge = 6;
54
+ let left = at.x + gap;
55
+ if (left + w > cw - edge) left = at.x - gap - w;
56
+ if (left < edge) left = edge;
57
+ if (left + w > cw - edge) left = Math.max(edge, cw - w - edge);
58
+ let top = at.y + gap;
59
+ if (top + h > ch - edge) top = at.y - gap - h;
60
+ if (top < edge) top = edge;
61
+ if (top + h > ch - edge) top = Math.max(edge, ch - h - edge);
62
+ el.style.left = `${Math.round(left)}px`;
63
+ el.style.top = `${Math.round(top)}px`;
64
+ }
65
+ function makeDraggable(el, grip) {
66
+ let startX = 0, startY = 0, baseX = 0, baseY = 0, dragging = false;
67
+ const down = (ev) => {
68
+ dragging = true;
69
+ startX = ev.clientX;
70
+ startY = ev.clientY;
71
+ baseX = parseFloat(el.style.left || "0");
72
+ baseY = parseFloat(el.style.top || "0");
73
+ ev.preventDefault();
74
+ document.addEventListener("mousemove", move);
75
+ document.addEventListener("mouseup", up);
76
+ };
77
+ const move = (ev) => {
78
+ if (!dragging) return;
79
+ const cw = container.clientWidth || 0, ch = container.clientHeight || 0;
80
+ const w = el.offsetWidth || 0, h = el.offsetHeight || 0;
81
+ const nx = Math.min(Math.max(4, baseX + (ev.clientX - startX)), Math.max(4, cw - w - 4));
82
+ const ny = Math.min(Math.max(4, baseY + (ev.clientY - startY)), Math.max(4, ch - h - 4));
83
+ el.style.left = `${Math.round(nx)}px`;
84
+ el.style.top = `${Math.round(ny)}px`;
85
+ };
86
+ const up = () => {
87
+ dragging = false;
88
+ document.removeEventListener("mousemove", move);
89
+ document.removeEventListener("mouseup", up);
90
+ };
91
+ grip.addEventListener("mousedown", down);
92
+ }
93
+ function build(model, compact) {
94
+ const el = document.createElement("div");
95
+ el.className = CARD_CLASS;
96
+ const head = document.createElement("div");
97
+ head.className = `${CARD_CLASS}-head`;
98
+ const title = document.createElement("span");
99
+ title.className = `${CARD_CLASS}-title`;
100
+ title.textContent = model.header;
101
+ head.append(title);
102
+ const pin = document.createElement("button");
103
+ pin.type = "button";
104
+ pin.className = `${CARD_CLASS}-btn`;
105
+ pin.title = "Keep this card while you click other marks";
106
+ pin.setAttribute("aria-label", "Pin this card");
107
+ pin.textContent = "Pin";
108
+ head.append(pin);
109
+ const close = document.createElement("button");
110
+ close.type = "button";
111
+ close.className = `${CARD_CLASS}-btn`;
112
+ close.title = "Dismiss";
113
+ close.setAttribute("aria-label", "Dismiss this card");
114
+ close.textContent = "\xD7";
115
+ head.append(close);
116
+ el.append(head);
117
+ const appendLine = (line, cls) => {
118
+ const row = document.createElement("div");
119
+ row.className = cls;
120
+ const lbl = document.createElement("span");
121
+ lbl.className = `${CARD_CLASS}-label`;
122
+ lbl.textContent = `${line.label}:`;
123
+ const val = document.createElement("span");
124
+ val.className = `${CARD_CLASS}-value`;
125
+ val.textContent = line.shareText ? `${line.valueText} (${line.shareText})` : line.valueText;
126
+ row.append(lbl, val);
127
+ el.append(row);
128
+ };
129
+ if (!compact) {
130
+ for (const line of model.lines) appendLine(line, `${CARD_CLASS}-line`);
131
+ if (model.hiddenMeasures > 0) {
132
+ const more = document.createElement("div");
133
+ more.className = `${CARD_CLASS}-more`;
134
+ more.textContent = `+${model.hiddenMeasures} more measure${model.hiddenMeasures === 1 ? "" : "s"}`;
135
+ el.append(more);
136
+ }
137
+ for (const line of model.dimensionLines) appendLine(line, `${CARD_CLASS}-line ${CARD_CLASS}-dim`);
138
+ }
139
+ const rows = document.createElement("div");
140
+ rows.className = `${CARD_CLASS}-rows`;
141
+ rows.textContent = model.rowsText;
142
+ el.append(rows);
143
+ makeDraggable(el, head);
144
+ return { el, pin, close };
145
+ }
146
+ function show(model) {
147
+ clearTransient();
148
+ let built = build(model, false);
149
+ let el = built.el;
150
+ el.style.visibility = "hidden";
151
+ container.append(el);
152
+ const cw = container.clientWidth || 0, ch = container.clientHeight || 0;
153
+ const area = (el.offsetWidth || 0) * (el.offsetHeight || 0);
154
+ const tile = cw * ch;
155
+ if (tile > 0 && area > tile * MAX_TILE_FRACTION) {
156
+ el.remove();
157
+ built = build(model, true);
158
+ el = built.el;
159
+ el.style.visibility = "hidden";
160
+ container.append(el);
161
+ log("selcard-compact", { area, tile });
162
+ }
163
+ place(el, anchor);
164
+ el.style.visibility = "visible";
165
+ const handle = { el, pinned: false };
166
+ transient = handle;
167
+ built.close.addEventListener("click", (ev) => {
168
+ ev.stopPropagation();
169
+ if (handle.pinned) {
170
+ const i = pinned.indexOf(handle);
171
+ if (i >= 0) pinned.splice(i, 1);
172
+ removeCard(handle);
173
+ } else {
174
+ clearTransient();
175
+ }
176
+ });
177
+ built.pin.addEventListener("click", (ev) => {
178
+ ev.stopPropagation();
179
+ if (handle.pinned) return;
180
+ handle.pinned = true;
181
+ transient = null;
182
+ built.pin.textContent = "Pinned";
183
+ built.pin.disabled = true;
184
+ el.classList.add(`${CARD_CLASS}--pinned`);
185
+ pinned.push(handle);
186
+ const limit = Math.max(1, deps.getPinLimit() || 3);
187
+ while (pinned.length > limit) removeCard(pinned.shift());
188
+ log("selcard-pinned", { pins: pinned.length, limit });
189
+ });
190
+ }
191
+ return {
192
+ onSelect(rowIdxs) {
193
+ if (disposed) return;
194
+ if (!deps.getEnabled()) {
195
+ clearTransient();
196
+ return;
197
+ }
198
+ if (!rowIdxs || !rowIdxs.length) {
199
+ clearTransient();
200
+ return;
201
+ }
202
+ let model = null;
203
+ try {
204
+ model = computeSelectionCard(deps.getPayload(), rowIdxs, {
205
+ aggregation: deps.getAggregation(),
206
+ cultureCode: deps.cultureCode,
207
+ sourceFormats: deps.getSourceFormats?.() ?? null
208
+ });
209
+ } catch (e) {
210
+ log("selcard-failed", { error: e?.message ?? String(e) });
211
+ clearTransient();
212
+ return;
213
+ }
214
+ if (!model) {
215
+ clearTransient();
216
+ return;
217
+ }
218
+ if (model.suppressedColumns.length) {
219
+ log("selcard-not-aggregatable", { columns: model.suppressedColumns.join(", ") });
220
+ }
221
+ show(model);
222
+ },
223
+ clear: clearAll,
224
+ destroy() {
225
+ disposed = true;
226
+ clearAll();
227
+ container.removeEventListener("click", onPointer, true);
228
+ document.removeEventListener("keydown", onKey);
229
+ }
230
+ };
231
+ }
232
+ export {
233
+ createSelectionCards
234
+ };