@data-navigator/bokeh-wrapper 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,747 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+
21
+ // src/index.ts
22
+ import dataNavigator from "data-navigator";
23
+
24
+ // src/structure-builder.ts
25
+ function resolveEl(ref) {
26
+ if (typeof ref === "string")
27
+ return document.getElementById(ref);
28
+ return ref instanceof HTMLElement ? ref : null;
29
+ }
30
+ function safeId(s) {
31
+ return String(s).replace(/\s+/g, "-").replace(/[^\w-]/g, "");
32
+ }
33
+ function isNumericField(data, field) {
34
+ return data.some((d) => d[field] != null && !isNaN(Number(d[field])));
35
+ }
36
+ function hasMultiplePerX(data, xField) {
37
+ var _a;
38
+ const counts = /* @__PURE__ */ new Map();
39
+ for (const d of data) {
40
+ const v = d[xField];
41
+ counts.set(v, ((_a = counts.get(v)) != null ? _a : 0) + 1);
42
+ }
43
+ return Array.from(counts.values()).some((c) => c > 1);
44
+ }
45
+ function resolveChartType(type, data, xField, yField, groupField) {
46
+ if (type && type !== "auto")
47
+ return type;
48
+ if (!data.length)
49
+ return "bar";
50
+ if (groupField && xField && yField) {
51
+ return hasMultiplePerX(data, xField) ? "stacked_bar" : "multiline";
52
+ }
53
+ if (xField && yField) {
54
+ if (isNumericField(data, xField) && isNumericField(data, yField))
55
+ return "cartesian";
56
+ if (!isNumericField(data, xField))
57
+ return "bar";
58
+ return "line";
59
+ }
60
+ if (xField) {
61
+ return isNumericField(data, xField) ? "line" : "bar";
62
+ }
63
+ return "bar";
64
+ }
65
+ var exitEdge = {
66
+ edgeId: "dn-exit",
67
+ edge: {
68
+ source: (_d, c) => c,
69
+ target: () => "",
70
+ navigationRules: ["exit"]
71
+ }
72
+ };
73
+ var baseNavRules = {
74
+ // Primary (x-axis) navigation
75
+ left: { key: "ArrowLeft", direction: "source" },
76
+ right: { key: "ArrowRight", direction: "target" },
77
+ // Secondary encoding (y-axis / group) navigation
78
+ up: { key: "ArrowUp", direction: "source" },
79
+ down: { key: "ArrowDown", direction: "target" },
80
+ // Tertiary encoding navigation ([ / ])
81
+ backward: { key: "BracketLeft", direction: "source" },
82
+ forward: { key: "BracketRight", direction: "target" },
83
+ // Hierarchy traversal
84
+ child: { key: "Enter", direction: "target" },
85
+ parent: { key: "Backspace", direction: "source" },
86
+ undo: { key: "Delete", direction: "source" },
87
+ // Encoding-specific parent shortcuts (W = x-axis parent, J = y-axis parent, \ = tertiary parent)
88
+ xParent: { key: "KeyW", direction: "source" },
89
+ yParent: { key: "KeyJ", direction: "source" },
90
+ zParent: { key: "Backslash", direction: "source" },
91
+ // Exit and help
92
+ exit: { key: "Escape", direction: "target" },
93
+ help: { key: "KeyY", direction: "target" }
94
+ };
95
+ function buildFlatStructure(data, xField, idField) {
96
+ const sorted = xField ? [...data].sort((a, b) => {
97
+ const av = a[xField];
98
+ const bv = b[xField];
99
+ if (typeof av === "number" && typeof bv === "number")
100
+ return av - bv;
101
+ return String(av) < String(bv) ? -1 : 1;
102
+ }) : [...data];
103
+ const ids = sorted.map(
104
+ (d, i) => idField && d[idField] != null ? safeId(d[idField]) : `dn-item-${i}`
105
+ );
106
+ const augmented = sorted.map((d, i) => __spreadProps(__spreadValues({}, d), { _dnId: ids[i] }));
107
+ return {
108
+ data: augmented,
109
+ idKey: "_dnId",
110
+ navigationRules: baseNavRules,
111
+ genericEdges: [
112
+ {
113
+ edgeId: "dn-right",
114
+ edge: {
115
+ source: (_d, c) => c,
116
+ target: (_d, c) => {
117
+ const i = ids.indexOf(c);
118
+ return ids[(i + 1) % ids.length];
119
+ },
120
+ navigationRules: ["right"]
121
+ }
122
+ },
123
+ {
124
+ edgeId: "dn-left",
125
+ edge: {
126
+ source: (_d, c) => {
127
+ const i = ids.indexOf(c);
128
+ return ids[(i - 1 + ids.length) % ids.length];
129
+ },
130
+ target: (_d, c) => c,
131
+ navigationRules: ["left"]
132
+ }
133
+ },
134
+ exitEdge
135
+ ]
136
+ };
137
+ }
138
+ function buildCrosslineStructure(data, xField, groupField) {
139
+ const augNavRules = __spreadValues({}, baseNavRules);
140
+ augNavRules.up = { key: "ArrowUp", direction: "target" };
141
+ augNavRules.down = { key: "ArrowDown", direction: "source" };
142
+ return {
143
+ data,
144
+ idKey: "id",
145
+ addIds: true,
146
+ navigationRules: augNavRules,
147
+ dimensions: {
148
+ values: [
149
+ {
150
+ dimensionKey: xField,
151
+ type: "categorical",
152
+ behavior: {
153
+ extents: "circular",
154
+ childmostNavigation: "across"
155
+ },
156
+ // left/right siblings at this dimension level; W drills back up
157
+ navigationRules: {
158
+ sibling_sibling: ["left", "right"],
159
+ parent_child: ["xParent", "child"]
160
+ }
161
+ },
162
+ {
163
+ dimensionKey: groupField,
164
+ type: "categorical",
165
+ behavior: {
166
+ extents: "circular",
167
+ childmostNavigation: "across"
168
+ },
169
+ // up/down siblings at this dimension level; J drills back up
170
+ navigationRules: {
171
+ sibling_sibling: ["up", "down"],
172
+ parent_child: ["yParent", "child"]
173
+ }
174
+ }
175
+ ]
176
+ },
177
+ genericEdges: [exitEdge]
178
+ };
179
+ }
180
+ function buildCartesianStructure(data, xField, yField, idField) {
181
+ const augmented = data.map((d, i) => {
182
+ const baseId = idField && d[idField] != null ? safeId(String(d[idField])) : d["id"] != null ? safeId(String(d["id"])) : `dn-pt-${i}`;
183
+ return __spreadProps(__spreadValues({}, d), { id: baseId });
184
+ });
185
+ const autoSubdivs = (_key, values) => Math.min(12, Math.max(3, Math.ceil(Math.sqrt(Object.keys(values).length))));
186
+ const augNavRules = __spreadValues({}, baseNavRules);
187
+ augNavRules.up = { key: "ArrowUp", direction: "target" };
188
+ augNavRules.down = { key: "ArrowDown", direction: "source" };
189
+ return {
190
+ data: augmented,
191
+ idKey: "id",
192
+ navigationRules: augNavRules,
193
+ dimensions: {
194
+ values: [
195
+ {
196
+ dimensionKey: xField,
197
+ type: "numerical",
198
+ behavior: {
199
+ extents: "terminal"
200
+ },
201
+ operations: { createNumericalSubdivisions: autoSubdivs },
202
+ navigationRules: {
203
+ sibling_sibling: ["left", "right"],
204
+ parent_child: ["xParent", "child"]
205
+ }
206
+ },
207
+ {
208
+ dimensionKey: yField,
209
+ type: "numerical",
210
+ behavior: {
211
+ extents: "terminal"
212
+ },
213
+ operations: {
214
+ createNumericalSubdivisions: autoSubdivs
215
+ // sortFunction: (a:any,b:any) => {
216
+ // console.log("sorting dim",yField,a,b)
217
+ // return b[yField] - a[yField]
218
+ // }
219
+ },
220
+ // divisionOptions: {
221
+ // sortFunction: (a:any,b:any) => {
222
+ // console.log("sorting div",yField,a,b)
223
+ // return b[yField] - a[yField]
224
+ // }
225
+ // },
226
+ navigationRules: {
227
+ sibling_sibling: ["down", "up"],
228
+ parent_child: ["yParent", "child"]
229
+ }
230
+ }
231
+ ]
232
+ },
233
+ genericEdges: [exitEdge]
234
+ };
235
+ }
236
+ function buildCartesianGroupedStructure(data, xField, yField, groupField, idField) {
237
+ const augmented = data.map((d, i) => {
238
+ const baseId = idField && d[idField] != null ? safeId(String(d[idField])) : d["id"] != null ? safeId(String(d["id"])) : `dn-pt-${i}`;
239
+ return __spreadProps(__spreadValues({}, d), { id: baseId });
240
+ });
241
+ const autoSubdivs = (_key, values) => Math.min(12, Math.max(3, Math.ceil(Math.sqrt(Object.keys(values).length))));
242
+ const augNavRules = __spreadValues({}, baseNavRules);
243
+ augNavRules.up = { key: "ArrowUp", direction: "target" };
244
+ augNavRules.down = { key: "ArrowDown", direction: "source" };
245
+ return {
246
+ data: augmented,
247
+ idKey: "id",
248
+ navigationRules: augNavRules,
249
+ dimensions: {
250
+ values: [
251
+ {
252
+ dimensionKey: xField,
253
+ type: "numerical",
254
+ behavior: { extents: "terminal" },
255
+ operations: { createNumericalSubdivisions: autoSubdivs },
256
+ navigationRules: {
257
+ sibling_sibling: ["left", "right"],
258
+ parent_child: ["xParent", "child"]
259
+ }
260
+ },
261
+ {
262
+ dimensionKey: yField,
263
+ type: "numerical",
264
+ behavior: { extents: "terminal" },
265
+ operations: { createNumericalSubdivisions: autoSubdivs },
266
+ navigationRules: {
267
+ sibling_sibling: ["down", "up"],
268
+ parent_child: ["yParent", "child"]
269
+ }
270
+ },
271
+ {
272
+ dimensionKey: groupField,
273
+ type: "categorical",
274
+ behavior: { extents: "circular" },
275
+ navigationRules: {
276
+ sibling_sibling: ["backward", "forward"],
277
+ parent_child: ["zParent", "child"]
278
+ }
279
+ }
280
+ ]
281
+ },
282
+ genericEdges: [exitEdge]
283
+ };
284
+ }
285
+ function buildDimensionStructure(data, dimensionKey, idField, compositeKey, compressSparseDivisions) {
286
+ let idKey;
287
+ let augmented = data;
288
+ if (compositeKey) {
289
+ augmented = data.map((d) => __spreadProps(__spreadValues({}, d), {
290
+ _dnId: `${safeId(d[dimensionKey])}-${safeId(d[compositeKey])}`
291
+ }));
292
+ idKey = "_dnId";
293
+ } else if (idField) {
294
+ idKey = idField;
295
+ } else {
296
+ idKey = dimensionKey;
297
+ }
298
+ return {
299
+ data: augmented,
300
+ idKey,
301
+ navigationRules: baseNavRules,
302
+ dimensions: {
303
+ values: [
304
+ {
305
+ dimensionKey,
306
+ type: "categorical",
307
+ behavior: { extents: "circular" },
308
+ // Explicitly name rules so edge tags match keys in baseNavRules.
309
+ // Without this, data-navigator auto-generates 'parent_<dimensionKey>'
310
+ // (e.g. 'parent_city'), which won't match the 'parent' rule.
311
+ operations: {
312
+ compressSparseDivisions: compressSparseDivisions || false
313
+ },
314
+ navigationRules: {
315
+ sibling_sibling: ["left", "right"],
316
+ parent_child: ["parent", "child"]
317
+ }
318
+ }
319
+ ]
320
+ },
321
+ genericEdges: [exitEdge]
322
+ };
323
+ }
324
+ var humanChartType = {
325
+ bar: "bar chart",
326
+ hbar: "horizontal bar chart",
327
+ scatter: "scatter plot",
328
+ cartesian: "scatter plot",
329
+ line: "line chart",
330
+ multiline: "multi-line chart",
331
+ crossline: "cross-navigable multi-line chart",
332
+ stacked_bar: "stacked bar chart"
333
+ };
334
+ function buildChartDescription(options, dimensionCount = 1) {
335
+ var _a;
336
+ const { data, type, xField, yField, groupField, title } = options;
337
+ const chartType = resolveChartType(type, data, xField, yField, groupField);
338
+ const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
339
+ const parts = [];
340
+ if (title) {
341
+ parts.push(title);
342
+ } else if (xField && yField) {
343
+ parts.push(`${cap(xField)} and ${cap(yField)}`);
344
+ } else if (xField) {
345
+ parts.push(cap(xField));
346
+ }
347
+ parts.push((_a = humanChartType[chartType]) != null ? _a : chartType);
348
+ if (xField) {
349
+ const axisType = isNumericField(data, xField) ? "numerical" : "categorical";
350
+ parts.push(`${xField} along x axis (${axisType})`);
351
+ }
352
+ if (yField) {
353
+ const axisType = isNumericField(data, yField) ? "numerical" : "categorical";
354
+ parts.push(`${yField} along y axis (${axisType})`);
355
+ }
356
+ if (groupField) {
357
+ parts.push(`grouped by ${groupField}`);
358
+ }
359
+ const total = data.length;
360
+ if (dimensionCount > 1) {
361
+ parts.push(`contains ${dimensionCount} dimensions and ${total} total data points`);
362
+ } else {
363
+ let divisionKey;
364
+ if (chartType === "bar" || chartType === "hbar")
365
+ divisionKey = xField;
366
+ else if (chartType === "stacked_bar")
367
+ divisionKey = xField;
368
+ else if (chartType === "multiline")
369
+ divisionKey = groupField;
370
+ if (divisionKey) {
371
+ const divisions = new Set(data.map((d) => d[divisionKey])).size;
372
+ if (divisions !== total) {
373
+ parts.push(`contains ${divisions} divisions and ${total} total data points`);
374
+ } else {
375
+ parts.push(`contains ${total} total data points`);
376
+ }
377
+ } else {
378
+ parts.push(`contains ${total} total data points`);
379
+ }
380
+ }
381
+ return parts.map(cap).join(". ") + ".";
382
+ }
383
+ function buildStructureOptions(options) {
384
+ var _a;
385
+ const { data, type, xField, yField, groupField, idField, structureOptions = {} } = options;
386
+ const chartType = resolveChartType(type, data, xField, yField, groupField);
387
+ let base;
388
+ switch (chartType) {
389
+ case "scatter":
390
+ case "line":
391
+ base = buildFlatStructure(data, xField, idField);
392
+ break;
393
+ case "bar":
394
+ case "hbar": {
395
+ const catField = xField || Object.keys((_a = data[0]) != null ? _a : {}).find((k) => !isNumericField(data, k)) || "x";
396
+ base = buildDimensionStructure(data, catField, idField, "", options.compressSparseDivisions);
397
+ break;
398
+ }
399
+ case "stacked_bar": {
400
+ const stackX = xField || "x";
401
+ const stackGroup = groupField || "group";
402
+ base = buildCrosslineStructure(data, stackX, stackGroup);
403
+ break;
404
+ }
405
+ case "multiline": {
406
+ const lineGroup = groupField || "series";
407
+ base = buildDimensionStructure(data, lineGroup, idField, xField, options.compressSparseDivisions);
408
+ break;
409
+ }
410
+ case "cartesian": {
411
+ const cartX = xField || "x";
412
+ const cartY = yField || "y";
413
+ if (groupField) {
414
+ base = buildCartesianGroupedStructure(data, cartX, cartY, groupField, idField);
415
+ } else {
416
+ base = buildCartesianStructure(data, cartX, cartY, idField);
417
+ }
418
+ break;
419
+ }
420
+ case "crossline": {
421
+ const crossX = xField || "x";
422
+ const crossGroup = groupField || "series";
423
+ base = buildCrosslineStructure(data, crossX, crossGroup);
424
+ break;
425
+ }
426
+ default:
427
+ base = buildFlatStructure(data, xField, idField);
428
+ }
429
+ return __spreadValues(__spreadValues({}, base), structureOptions);
430
+ }
431
+ function buildCommandLabels(options) {
432
+ const { data, type, xField, yField, groupField, commandLabels = {} } = options;
433
+ const chartType = resolveChartType(type, data, xField, yField, groupField);
434
+ const auto = {};
435
+ switch (chartType) {
436
+ case "bar":
437
+ case "hbar":
438
+ auto.left = `Move to previous ${xField != null ? xField : "category"}`;
439
+ auto.right = `Move to next ${xField != null ? xField : "category"}`;
440
+ auto.child = `Drill into ${xField != null ? xField : "category"} detail`;
441
+ auto.parent = "Go back up";
442
+ auto.undo = "Go back up";
443
+ break;
444
+ case "scatter":
445
+ case "line":
446
+ auto.left = `Move to previous ${xField != null ? xField : "point"}`;
447
+ auto.right = `Move to next ${xField != null ? xField : "point"}`;
448
+ break;
449
+ case "stacked_bar":
450
+ auto.left = `Move to previous ${xField != null ? xField : "category"}`;
451
+ auto.right = `Move to next ${xField != null ? xField : "category"}`;
452
+ auto.up = `Move to previous ${groupField != null ? groupField : "group"}`;
453
+ auto.down = `Move to next ${groupField != null ? groupField : "group"}`;
454
+ auto.child = "Drill in";
455
+ auto.xParent = `Go up to ${xField != null ? xField : "category"} level`;
456
+ auto.yParent = `Go up to ${groupField != null ? groupField : "group"} level`;
457
+ break;
458
+ case "multiline":
459
+ auto.left = `Move to previous ${groupField != null ? groupField : "series"}`;
460
+ auto.right = `Move to next ${groupField != null ? groupField : "series"}`;
461
+ auto.child = `Drill into ${groupField != null ? groupField : "series"} data`;
462
+ auto.parent = "Go back up";
463
+ auto.undo = "Go back up";
464
+ break;
465
+ case "cartesian":
466
+ auto.left = `Move to previous ${xField != null ? xField : "x"} range`;
467
+ auto.right = `Move to next ${xField != null ? xField : "x"} range`;
468
+ auto.up = `Move to higher ${yField != null ? yField : "y"} range`;
469
+ auto.down = `Move to lower ${yField != null ? yField : "y"} range`;
470
+ auto.child = "Drill in";
471
+ auto.xParent = `Go up to ${xField != null ? xField : "x"} level`;
472
+ auto.yParent = `Go up to ${yField != null ? yField : "y"} level`;
473
+ if (groupField) {
474
+ auto.backward = `Move to previous ${groupField} group`;
475
+ auto.forward = `Move to next ${groupField} group`;
476
+ auto.zParent = `Go up to ${groupField} level`;
477
+ }
478
+ break;
479
+ case "crossline":
480
+ auto.left = `Move to previous ${xField != null ? xField : "x-axis point"}`;
481
+ auto.right = `Move to next ${xField != null ? xField : "x-axis point"}`;
482
+ auto.up = `Move to previous ${groupField != null ? groupField : "series"}`;
483
+ auto.down = `Move to next ${groupField != null ? groupField : "series"}`;
484
+ auto.child = "Drill in";
485
+ auto.xParent = `Go up to ${xField != null ? xField : "x-axis"} level`;
486
+ auto.yParent = `Go up to ${groupField != null ? groupField : "series"} level`;
487
+ break;
488
+ }
489
+ return __spreadValues(__spreadValues({}, auto), commandLabels);
490
+ }
491
+ function buildNodeLabel(node) {
492
+ const data = node.data;
493
+ if (!data)
494
+ return String(node.id);
495
+ if (typeof data.dimensionKey === "string" && data.divisions != null) {
496
+ const divCount = Object.keys(data.divisions).length;
497
+ return `${data.dimensionKey} dimension. Contains ${divCount} division${divCount !== 1 ? "s" : ""}.`;
498
+ }
499
+ if (data.values != null && typeof data.values === "object" && !Array.isArray(data.values)) {
500
+ const dimKey = node.derivedNode;
501
+ const dimValue = dimKey ? data[dimKey] : null;
502
+ if (dimValue != null) {
503
+ const childCount = Object.keys(data.values).length;
504
+ return `${dimValue}. Contains ${childCount} data point${childCount !== 1 ? "s" : ""}.`;
505
+ }
506
+ return String(node.id);
507
+ }
508
+ const parts = Object.entries(data).filter(
509
+ ([k, v]) => !k.startsWith("_") && v != null && typeof v !== "object" && typeof v !== "function"
510
+ ).map(([k, v]) => `${k}: ${v}`);
511
+ return parts.length > 0 ? parts.join(". ") + "." : String(node.id);
512
+ }
513
+ function prepareNodeSemantics(structure) {
514
+ var _a, _b;
515
+ for (const node of Object.values(structure.nodes)) {
516
+ const n = node;
517
+ if (!((_a = n.semantics) == null ? void 0 : _a.label)) {
518
+ n.semantics = __spreadProps(__spreadValues({}, (_b = n.semantics) != null ? _b : {}), { label: buildNodeLabel(n) });
519
+ }
520
+ }
521
+ }
522
+
523
+ // src/index.ts
524
+ function resolveChatContainer(options, plotEl) {
525
+ var _a, _b;
526
+ if (options.chatContainer) {
527
+ const el = resolveEl(options.chatContainer);
528
+ if (el)
529
+ return { el, owned: false };
530
+ }
531
+ const div = document.createElement("div");
532
+ div.className = "dn-bokeh-wrapper";
533
+ (_b = (_a = plotEl.parentElement) == null ? void 0 : _a.insertBefore(div, plotEl.nextSibling)) != null ? _b : document.body.appendChild(div);
534
+ return { el: div, owned: true };
535
+ }
536
+ function setupKeyboardMode(structure, plotEl, options, entryPoint) {
537
+ var _a, _b;
538
+ const { onNavigate, onExit, renderingOptions = {} } = options;
539
+ const width = plotEl.clientWidth || 400;
540
+ const height = plotEl.clientHeight || 300;
541
+ let current = null;
542
+ let previous = null;
543
+ const resolvedEntry = entryPoint != null ? entryPoint : Object.keys(structure.nodes)[0];
544
+ const suffixId = `dn-bokeh-${Math.random().toString(36).slice(2, 6)}`;
545
+ const rendering = dataNavigator.rendering({
546
+ elementData: structure.nodes,
547
+ defaults: __spreadValues({
548
+ cssClass: "dn-node",
549
+ spatialProperties: { x: 0, y: 0, width, height }
550
+ }, renderingOptions.defaults),
551
+ suffixId,
552
+ root: {
553
+ id: typeof options.plotContainer === "string" ? options.plotContainer : options.plotContainer.id || "dn-bokeh-root",
554
+ description: "Accessible data navigation",
555
+ width: "100%",
556
+ height: 0
557
+ },
558
+ entryButton: { include: true, callbacks: { click: enter } },
559
+ exitElement: { include: true }
560
+ });
561
+ rendering.initialize();
562
+ if (rendering.entryButton) {
563
+ rendering.entryButton.addEventListener("keydown", (e) => {
564
+ if (e.key === "Enter" || e.key === " ") {
565
+ e.preventDefault();
566
+ if (!current)
567
+ enter();
568
+ }
569
+ });
570
+ }
571
+ const input = dataNavigator.input({
572
+ structure,
573
+ navigationRules: (_a = structure.navigationRules) != null ? _a : {},
574
+ entryPoint: resolvedEntry,
575
+ exitPoint: (_b = rendering.exitElement) == null ? void 0 : _b.id
576
+ });
577
+ function enter() {
578
+ const node = input.enter();
579
+ if (!node)
580
+ return;
581
+ navigate(node);
582
+ }
583
+ function navigate(node) {
584
+ if (!node.renderId)
585
+ node.renderId = node.id;
586
+ if (!node.spatialProperties) {
587
+ node.spatialProperties = { x: 0, y: 0, width, height };
588
+ }
589
+ if (previous)
590
+ rendering.remove(previous);
591
+ const el = rendering.render({ renderId: node.renderId, datum: node });
592
+ if (!el)
593
+ return;
594
+ el.style.width = "100%";
595
+ el.style.height = "100%";
596
+ el.style.top = "0";
597
+ el.style.left = "0";
598
+ el.addEventListener("keydown", (e) => {
599
+ const direction = input.keydownValidator(e);
600
+ if (direction) {
601
+ e.preventDefault();
602
+ if (direction === "exit") {
603
+ if (rendering.exitElement) {
604
+ rendering.exitElement.style.display = "block";
605
+ input.focus(rendering.exitElement.id);
606
+ }
607
+ } else {
608
+ const next = input.move(current, direction);
609
+ if (next)
610
+ navigate(next);
611
+ }
612
+ }
613
+ });
614
+ el.addEventListener("focus", () => {
615
+ if (onNavigate)
616
+ onNavigate(node);
617
+ });
618
+ input.focus(node.renderId);
619
+ previous = current;
620
+ current = node.id;
621
+ }
622
+ if (rendering.exitElement) {
623
+ rendering.exitElement.addEventListener("focus", () => {
624
+ if (current)
625
+ rendering.remove(current);
626
+ current = null;
627
+ if (onExit)
628
+ onExit();
629
+ });
630
+ }
631
+ return {
632
+ destroy() {
633
+ var _a2, _b2, _c, _d;
634
+ try {
635
+ (_b2 = (_a2 = rendering.wrapper) == null ? void 0 : _a2.remove) == null ? void 0 : _b2.call(_a2);
636
+ } catch (_) {
637
+ }
638
+ try {
639
+ (_d = (_c = rendering.exitElement) == null ? void 0 : _c.remove) == null ? void 0 : _d.call(_c);
640
+ } catch (_) {
641
+ }
642
+ },
643
+ getCurrentNode() {
644
+ var _a2;
645
+ return current ? (_a2 = structure.nodes[current]) != null ? _a2 : null : null;
646
+ }
647
+ };
648
+ }
649
+ function addDataNavigator(options) {
650
+ var _a, _b, _c, _d, _e;
651
+ const { mode = "text", onNavigate, onExit, onClick, onHover, llm } = options;
652
+ const plotEl = resolveEl(options.plotContainer);
653
+ if (!plotEl) {
654
+ throw new Error(
655
+ `@data-navigator/bokeh-wrapper: plotContainer "${options.plotContainer}" not found.`
656
+ );
657
+ }
658
+ const didSetInert = mode === "text";
659
+ if (didSetInert) {
660
+ plotEl.setAttribute("inert", "true");
661
+ }
662
+ const structOpts = buildStructureOptions(options);
663
+ const dimCount = (_c = (_b = (_a = structOpts.dimensions) == null ? void 0 : _a.values) == null ? void 0 : _b.length) != null ? _c : 0;
664
+ const resolveDescription = () => {
665
+ if (typeof options.describeRoot === "function")
666
+ return options.describeRoot(options);
667
+ if (typeof options.describeRoot === "string")
668
+ return options.describeRoot;
669
+ return buildChartDescription(options, dimCount);
670
+ };
671
+ if (dimCount > 1) {
672
+ const dims = structOpts.dimensions;
673
+ if (!dims.parentOptions)
674
+ dims.parentOptions = {};
675
+ if (!dims.parentOptions.addLevel0) {
676
+ const plotId = typeof options.plotContainer === "string" ? options.plotContainer.replace(/[^a-zA-Z0-9_-]/g, "") : "dn";
677
+ dims.parentOptions.addLevel0 = {
678
+ id: `${plotId}-chart-root`,
679
+ edges: [],
680
+ semantics: { label: resolveDescription() }
681
+ };
682
+ }
683
+ }
684
+ const structure = dataNavigator.structure(structOpts);
685
+ if (dimCount === 1 && structure.dimensions) {
686
+ const desc = resolveDescription();
687
+ for (const dim of Object.values(structure.dimensions)) {
688
+ const node = structure.nodes[dim.nodeId];
689
+ if (node)
690
+ node.semantics = { label: desc };
691
+ }
692
+ }
693
+ prepareNodeSemantics(structure);
694
+ const level0Node = Object.values(structure.nodes).find((n) => n.dimensionLevel === 0);
695
+ const resolvedEntryPoint = (_e = level0Node == null ? void 0 : level0Node.id) != null ? _e : structure.dimensions ? (_d = structure.dimensions[Object.keys(structure.dimensions)[0]]) == null ? void 0 : _d.nodeId : void 0;
696
+ const cleanups = [];
697
+ let textChatInstance = null;
698
+ if (mode === "text" || mode === "both") {
699
+ const { el: chatEl, owned } = resolveChatContainer(options, plotEl);
700
+ textChatInstance = dataNavigator.textChat({
701
+ structure,
702
+ container: chatEl,
703
+ entryPoint: resolvedEntryPoint,
704
+ data: options.data,
705
+ commandLabels: buildCommandLabels(options),
706
+ onNavigate,
707
+ onExit,
708
+ onClick,
709
+ onHover,
710
+ llm
711
+ });
712
+ if (owned) {
713
+ cleanups.push(() => {
714
+ var _a2;
715
+ try {
716
+ (_a2 = chatEl.parentElement) == null ? void 0 : _a2.removeChild(chatEl);
717
+ } catch (_) {
718
+ }
719
+ });
720
+ }
721
+ }
722
+ let keyboardMode = null;
723
+ if (mode === "keyboard" || mode === "both") {
724
+ keyboardMode = setupKeyboardMode(structure, plotEl, options, resolvedEntryPoint);
725
+ cleanups.push(() => keyboardMode == null ? void 0 : keyboardMode.destroy());
726
+ }
727
+ return {
728
+ destroy() {
729
+ textChatInstance == null ? void 0 : textChatInstance.destroy();
730
+ for (const cleanup of cleanups)
731
+ cleanup();
732
+ if (didSetInert)
733
+ plotEl.removeAttribute("inert");
734
+ },
735
+ getCurrentNode() {
736
+ var _a2, _b2;
737
+ return (_b2 = (_a2 = textChatInstance == null ? void 0 : textChatInstance.getCurrentNode()) != null ? _a2 : keyboardMode == null ? void 0 : keyboardMode.getCurrentNode()) != null ? _b2 : null;
738
+ },
739
+ structure
740
+ };
741
+ }
742
+ export {
743
+ addDataNavigator,
744
+ buildChartDescription,
745
+ buildNodeLabel,
746
+ prepareNodeSemantics
747
+ };