data-navigator 2.3.1 → 2.4.1

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.
@@ -0,0 +1,265 @@
1
+ type StructureOptions = {
2
+ data: GenericDataset;
3
+ idKey: DynamicNodeIdKey;
4
+ renderIdKey?: DynamicRenderIdKey;
5
+ dimensions?: DimensionOptions;
6
+ genericEdges?: EdgeOptions;
7
+ useDirectedEdges?: boolean;
8
+ dataType?: DataType;
9
+ addIds?: boolean;
10
+ keysForIdGeneration?: KeyList;
11
+ navigationRules?: NavigationRules;
12
+ };
13
+ type InputOptions = {
14
+ structure: Structure;
15
+ navigationRules: NavigationRules;
16
+ entryPoint?: NodeId;
17
+ exitPoint?: RenderId;
18
+ };
19
+ type RenderingOptions = {
20
+ elementData: ElementData | Nodes;
21
+ suffixId: string;
22
+ root: RootObject;
23
+ defaults?: RenderObject;
24
+ entryButton?: EntryObject;
25
+ exitElement?: ExitObject;
26
+ };
27
+ type DimensionOptions = {
28
+ values: DimensionList;
29
+ parentOptions?: {
30
+ level1Options?: {
31
+ order: AddOrReferenceNodeList;
32
+ behavior?: Level1Behavior;
33
+ navigationRules?: DimensionNavigationRules;
34
+ };
35
+ addLevel0?: NodeObject;
36
+ };
37
+ adjustDimensions?: AdjustingFunction;
38
+ };
39
+ type Structure = {
40
+ nodes: Nodes;
41
+ edges: Edges;
42
+ dimensions?: Dimensions;
43
+ navigationRules?: NavigationRules;
44
+ elementData?: ElementData;
45
+ };
46
+ type Nodes = Record<NodeId, NodeObject>;
47
+ type Edges = Record<EdgeId, EdgeObject>;
48
+ type Dimensions = Record<DimensionKey, DimensionObject>;
49
+ type NavigationRules = Record<NavId, NavObject>;
50
+ type ElementData = Record<RenderId, RenderObject>;
51
+ type DimensionDivisions = Record<NodeId, DivisionObject>;
52
+ type AddOrReferenceNodeList = Array<NodeToAddOrReference>;
53
+ type EdgeList = Array<EdgeId>;
54
+ type GenericDataset = Array<DatumObject>;
55
+ type NavigationList = Array<NavId>;
56
+ type DimensionNavigationPair = [NavId, NavId];
57
+ type NumericalExtentsPair = [number, number];
58
+ type DimensionList = Array<DimensionDatum>;
59
+ type EdgeOptions = Array<EdgeDatum>;
60
+ type KeyList = Array<string>;
61
+ type Semantics = ((RenderObject?: any, DatumObject?: any) => SemanticsObject) | SemanticsObject;
62
+ type SpatialProperties = ((RenderObject?: any, DatumObject?: any) => SpatialObject) | SpatialObject;
63
+ type Attributes = ((RenderObject?: any, DatumObject?: any) => AttributesObject) | AttributesObject;
64
+ type NodeObject = {
65
+ id: NodeId;
66
+ edges: EdgeList;
67
+ renderId?: RenderId;
68
+ renderingStrategy?: RenderingStrategy;
69
+ derivedNode?: DerivedNode;
70
+ dimensionLevel?: DimensionLevel;
71
+ [key: string | number]: any;
72
+ };
73
+ type EdgeObject = {
74
+ source: ((d: DatumObject, currentFocus: NodeId) => NodeId) | NodeId;
75
+ target: ((d: DatumObject, currentFocus: NodeId) => NodeId) | NodeId;
76
+ navigationRules: NavigationList;
77
+ edgeId?: EdgeId;
78
+ };
79
+ type EdgeDatum = {
80
+ edgeId: EdgeId;
81
+ edge: EdgeObject;
82
+ conditional?: ConditionalFunction;
83
+ };
84
+ type DimensionObject = {
85
+ nodeId: NodeId;
86
+ dimensionKey: DimensionKey;
87
+ divisions: DimensionDivisions;
88
+ operations: {
89
+ compressSparseDivisions: boolean;
90
+ sortFunction?: SortingFunction;
91
+ };
92
+ behavior?: DimensionBehavior;
93
+ navigationRules?: DimensionNavigationRules;
94
+ type?: DimensionType;
95
+ numericalExtents?: NumericalExtentsPair;
96
+ subdivisions?: NumericallySubdivide;
97
+ divisionOptions?: DivisionOptions;
98
+ };
99
+ type DimensionDatum = {
100
+ dimensionKey: DimensionKey;
101
+ behavior?: DimensionBehavior;
102
+ navigationRules?: DimensionNavigationRules;
103
+ type?: DimensionType;
104
+ operations?: DimensionOperations;
105
+ nodeId?: DynamicDimensionId;
106
+ renderId?: DynamicDimensionRenderId;
107
+ renderingStrategy?: RenderingStrategy;
108
+ divisionOptions?: DivisionOptions;
109
+ };
110
+ type DimensionNavigationRules = {
111
+ sibling_sibling: DimensionNavigationPair;
112
+ parent_child: DimensionNavigationPair;
113
+ };
114
+ type DivisionOptions = {
115
+ sortFunction?: SortingFunction;
116
+ divisionNodeIds?: (dimensionKey: DimensionKey, keyValue: any, i: number) => string;
117
+ divisionRenderIds?: (dimensionKey: DimensionKey, keyValue: any, i: number) => string;
118
+ renderingStrategy?: RenderingStrategy;
119
+ };
120
+ type DimensionOperations = {
121
+ filterFunction?: FilteringFunction;
122
+ sortFunction?: SortingFunction;
123
+ createNumericalSubdivisions?: NumericallySubdivide;
124
+ compressSparseDivisions?: boolean;
125
+ };
126
+ type DivisionObject = {
127
+ id: NodeId;
128
+ values: Nodes;
129
+ sortFunction?: SortingFunction;
130
+ numericalExtents?: NumericalExtentsPair;
131
+ };
132
+ type NavObject = {
133
+ direction: Direction;
134
+ key?: string;
135
+ };
136
+ type RenderObject = {
137
+ cssClass?: DynamicString;
138
+ spatialProperties?: SpatialProperties;
139
+ semantics?: Semantics;
140
+ parentSemantics?: Semantics;
141
+ existingElement?: ExistingElement;
142
+ showText?: boolean;
143
+ };
144
+ type RootObject = {
145
+ id: string;
146
+ cssClass?: string;
147
+ description?: string;
148
+ width?: string | number;
149
+ height?: string | number;
150
+ };
151
+ type EntryObject = {
152
+ include: boolean;
153
+ callbacks?: EntryCallbacks;
154
+ };
155
+ type ExitObject = {
156
+ include: boolean;
157
+ callbacks?: ExitCallbacks;
158
+ };
159
+ type SemanticsObject = {
160
+ label?: DynamicString;
161
+ elementType?: DynamicString;
162
+ role?: DynamicString;
163
+ attributes?: Attributes;
164
+ };
165
+ type SpatialObject = {
166
+ x?: DynamicNumber;
167
+ y?: DynamicNumber;
168
+ width?: DynamicNumber;
169
+ height?: DynamicNumber;
170
+ path?: DynamicString;
171
+ };
172
+ type DimensionBehavior = {
173
+ extents: ExtentType;
174
+ customBridgePrevious?: NodeId;
175
+ customBridgePost?: NodeId;
176
+ childmostNavigation?: ChildmostNavigationStrategy;
177
+ childmostMatching?: ChildmostMatchingStrategy;
178
+ };
179
+ type Level1Behavior = {
180
+ extents: Level0ExtentType;
181
+ customBridgePrevious?: NodeId;
182
+ customBridgePost?: NodeId;
183
+ };
184
+ type DescriptionOptions = {
185
+ omitKeyNames?: boolean;
186
+ semanticLabel?: string;
187
+ };
188
+ type ExistingElement = {
189
+ useForSpatialProperties: boolean;
190
+ spatialProperties?: SpatialProperties;
191
+ };
192
+ type EntryCallbacks = {
193
+ focus?: Function;
194
+ click?: Function;
195
+ };
196
+ type ExitCallbacks = {
197
+ focus?: Function;
198
+ blur?: Function;
199
+ };
200
+ type DatumObject = {
201
+ [key: string | number]: any;
202
+ };
203
+ type AttributesObject = {
204
+ [key: string]: string;
205
+ };
206
+ type DynamicNumber = ((r?: RenderObject, d?: DatumObject) => number) | number;
207
+ type DynamicString = ((r?: RenderObject, d?: DatumObject) => string) | string;
208
+ type DynamicNodeId = ((d?: DatumObject, dim?: DimensionDatum) => NodeId) | NodeId;
209
+ type DynamicRenderId = ((d?: DatumObject) => RenderId) | RenderId;
210
+ type DynamicNodeIdKey = ((d?: DatumObject) => string) | string;
211
+ type DynamicRenderIdKey = ((d?: DatumObject) => string) | string;
212
+ type DynamicDimensionId = ((d?: DimensionDatum, a?: GenericDataset) => NodeId) | NodeId;
213
+ type DynamicDimensionRenderId = ((d?: DimensionDatum, a?: GenericDataset) => RenderId) | RenderId;
214
+ type NumericallySubdivide = ((d?: DimensionKey, n?: Nodes) => number) | number;
215
+ type ChildmostMatchingStrategy = (index?: number, currentDivisionChild?: DatumObject, currentDivision?: DivisionObject, nextDivision?: DivisionObject) => DatumObject | undefined;
216
+ type AdjustingFunction = (d: Dimensions) => Dimensions;
217
+ type SortingFunction = (a: DatumObject, b: DatumObject, c?: any) => number;
218
+ type FilteringFunction = (a: DatumObject, b?: any) => boolean;
219
+ type ConditionalFunction = (n: NodeObject, d: EdgeDatum) => boolean;
220
+ type NodeId = string;
221
+ type EdgeId = string;
222
+ type RenderId = string;
223
+ type NavId = string;
224
+ type DimensionId = string;
225
+ type DimensionKey = string;
226
+ type NodeToAddOrReference = NodeObject | NodeId;
227
+ type Direction = 'target' | 'source';
228
+ type RenderingStrategy = 'outlineEach' | 'convexHull' | 'singleSquare' | 'custom';
229
+ type DimensionType = 'numerical' | 'categorical';
230
+ type ExtentType = 'circular' | 'terminal' | 'bridgedCousins' | 'bridgedCustom';
231
+ type ChildmostNavigationStrategy = 'within' | 'across';
232
+ type Level0ExtentType = 'circular' | 'terminal' | 'bridgedCustom';
233
+ type DataType = 'vega-lite' | 'vl' | 'Vega-Lite' | 'generic' | 'default';
234
+ type DimensionLevel = 0 | 1 | 2 | 3;
235
+ type DerivedNode = string;
236
+ type LLMMessage = {
237
+ role: 'user' | 'assistant' | 'system';
238
+ content: string;
239
+ };
240
+ type TextChatOptions = {
241
+ structure: Structure;
242
+ container: string | HTMLElement;
243
+ entryPoint?: NodeId;
244
+ describeNode?: (node: NodeObject) => string;
245
+ commandLabels?: Record<string, string>;
246
+ onNavigate?: (node: NodeObject) => void;
247
+ onExit?: () => void;
248
+ onClick?: (node: NodeObject) => void;
249
+ onHover?: (node: NodeObject) => void;
250
+ llm?: (messages: LLMMessage[]) => Promise<string | null>;
251
+ data?: Record<string, unknown>[];
252
+ };
253
+ type TextChatInstance = {
254
+ destroy: () => void;
255
+ getCurrentNode: () => NodeObject | null;
256
+ };
257
+
258
+ declare const _default: {
259
+ structure: (options: StructureOptions) => Structure;
260
+ input: (options: InputOptions) => any;
261
+ rendering: (options: RenderingOptions) => any;
262
+ textChat: (options: TextChatOptions) => TextChatInstance;
263
+ };
264
+
265
+ export { type AddOrReferenceNodeList, type AdjustingFunction, type Attributes, type AttributesObject, type ChildmostMatchingStrategy, type ChildmostNavigationStrategy, type ConditionalFunction, type DataType, type DatumObject, type DerivedNode, type DescriptionOptions, type DimensionBehavior, type DimensionDatum, type DimensionDivisions, type DimensionId, type DimensionKey, type DimensionLevel, type DimensionList, type DimensionNavigationPair, type DimensionNavigationRules, type DimensionObject, type DimensionOperations, type DimensionOptions, type DimensionType, type Dimensions, type Direction, type DivisionObject, type DivisionOptions, type DynamicDimensionId, type DynamicDimensionRenderId, type DynamicNodeId, type DynamicNodeIdKey, type DynamicNumber, type DynamicRenderId, type DynamicRenderIdKey, type DynamicString, type EdgeDatum, type EdgeId, type EdgeList, type EdgeObject, type EdgeOptions, type Edges, type ElementData, type EntryCallbacks, type EntryObject, type ExistingElement, type ExitCallbacks, type ExitObject, type ExtentType, type FilteringFunction, type GenericDataset, type InputOptions, type KeyList, type LLMMessage, type Level0ExtentType, type Level1Behavior, type NavId, type NavObject, type NavigationList, type NavigationRules, type NodeId, type NodeObject, type NodeToAddOrReference, type Nodes, type NumericalExtentsPair, type NumericallySubdivide, type RenderId, type RenderObject, type RenderingOptions, type RenderingStrategy, type RootObject, type Semantics, type SemanticsObject, type SortingFunction, type SpatialObject, type SpatialProperties, type Structure, type StructureOptions, type TextChatInstance, type TextChatOptions, _default as default };
package/dist/index.js CHANGED
@@ -439,7 +439,7 @@ var buildNodes = (options) => {
439
439
  return nodes;
440
440
  };
441
441
  var scaffoldDimensions = (options, nodes) => {
442
- var _a, _b;
442
+ var _a, _b, _c;
443
443
  let dimensions = {};
444
444
  if ((_b = (_a = options.dimensions) == null ? void 0 : _a.parentOptions) == null ? void 0 : _b.addLevel0) {
445
445
  let level0 = options.dimensions.parentOptions.addLevel0;
@@ -453,10 +453,11 @@ var scaffoldDimensions = (options, nodes) => {
453
453
  dim.numericalExtents[1] = max > val ? max : val;
454
454
  };
455
455
  options.data.forEach((d) => {
456
- let ods = options.dimensions.values || [];
456
+ var _a2;
457
+ let ods = ((_a2 = options.dimensions) == null ? void 0 : _a2.values) || [];
457
458
  let i = 0;
458
459
  ods.forEach((dim) => {
459
- var _a2, _b2, _c, _d, _e, _f, _g, _h;
460
+ var _a3, _b2, _c2, _d, _e, _f, _g, _h;
460
461
  if (!dim.dimensionKey) {
461
462
  console.error(
462
463
  `Building nodes, parsing dimensions. Each dimension in options.dimensions must contain a dimensionKey. This dimension has no key: ${JSON.stringify(
@@ -467,7 +468,7 @@ var scaffoldDimensions = (options, nodes) => {
467
468
  }
468
469
  if (dim.dimensionKey in d) {
469
470
  let value = d[dim.dimensionKey];
470
- let keepValue = typeof ((_a2 = dim.operations) == null ? void 0 : _a2.filterFunction) === "function" ? dim.operations.filterFunction(d, dim) : true;
471
+ let keepValue = typeof ((_a3 = dim.operations) == null ? void 0 : _a3.filterFunction) === "function" ? dim.operations.filterFunction(d, dim) : true;
471
472
  if (value !== void 0 && keepValue) {
472
473
  if (!dim.type) {
473
474
  dim.type = typeof value === "bigint" || typeof value === "number" ? "numerical" : "categorical";
@@ -483,7 +484,7 @@ var scaffoldDimensions = (options, nodes) => {
483
484
  type: dim.type,
484
485
  operations: {
485
486
  compressSparseDivisions: ((_b2 = dim.operations) == null ? void 0 : _b2.compressSparseDivisions) || false,
486
- sortFunction: ((_c = dim.operations) == null ? void 0 : _c.sortFunction) || void 0
487
+ sortFunction: ((_c2 = dim.operations) == null ? void 0 : _c2.sortFunction) || void 0
487
488
  },
488
489
  behavior: dim.behavior || {
489
490
  extents: "circular"
@@ -557,7 +558,7 @@ var scaffoldDimensions = (options, nodes) => {
557
558
  });
558
559
  });
559
560
  Object.keys(dimensions).forEach((s) => {
560
- var _a2, _b2, _c, _d, _e;
561
+ var _a2, _b2, _c2, _d, _e;
561
562
  let dimension = dimensions[s];
562
563
  let divisions = dimension.divisions;
563
564
  if (dimension.type === "numerical") {
@@ -576,14 +577,18 @@ var scaffoldDimensions = (options, nodes) => {
576
577
  let i = dimension.numericalExtents[0] + interval;
577
578
  let divisionCount = 0;
578
579
  let index = 0;
580
+ let lastDivisionId = null;
581
+ let prevBound = dimension.numericalExtents[0];
579
582
  for (i = dimension.numericalExtents[0] + interval; i <= dimension.numericalExtents[1]; i += interval) {
580
- let divisionId = typeof ((_a2 = dimension.divisionOptions) == null ? void 0 : _a2.divisionNodeIds) === "function" ? dimension.divisionOptions.divisionNodeIds(s, i, i) : dimension.nodeId + "_" + i;
583
+ let divisionId = typeof ((_a2 = dimension.divisionOptions) == null ? void 0 : _a2.divisionNodeIds) === "function" ? dimension.divisionOptions.divisionNodeIds(s, i, i) : createValidId(dimension.nodeId + "_" + i);
584
+ lastDivisionId = divisionId;
581
585
  dimension.divisions[divisionId] = {
582
586
  id: divisionId,
583
587
  sortFunction: ((_b2 = dimension.divisionOptions) == null ? void 0 : _b2.sortFunction) || void 0,
584
- values: {}
588
+ values: {},
589
+ numericalExtents: [prevBound, i]
585
590
  };
586
- let divisionRenderId = typeof ((_c = dimension.divisionOptions) == null ? void 0 : _c.divisionRenderIds) === "function" ? dimension.divisionOptions.divisionRenderIds(s, i, i) : divisionId;
591
+ let divisionRenderId = typeof ((_c2 = dimension.divisionOptions) == null ? void 0 : _c2.divisionRenderIds) === "function" ? dimension.divisionOptions.divisionRenderIds(s, i, i) : divisionId;
587
592
  nodes[divisionId] = {
588
593
  id: divisionId,
589
594
  renderId: divisionRenderId,
@@ -599,16 +604,25 @@ var scaffoldDimensions = (options, nodes) => {
599
604
  let node = values[valueKeys[index]];
600
605
  let value = node[s];
601
606
  if (value <= i) {
602
- dimension.divisions[divisionId].values[node.id] = node;
607
+ const leafId = typeof options.idKey === "function" ? options.idKey(node) : node[options.idKey];
608
+ dimension.divisions[divisionId].values[leafId] = node;
609
+ index++;
603
610
  } else {
604
- i += interval;
605
611
  limit = true;
606
612
  }
607
- index++;
608
613
  }
614
+ prevBound = i;
609
615
  divisionCount++;
610
616
  }
611
- delete divisions[s];
617
+ if (lastDivisionId && index < valueKeys.length) {
618
+ while (index < valueKeys.length) {
619
+ let node = values[valueKeys[index]];
620
+ const leafId = typeof options.idKey === "function" ? options.idKey(node) : node[options.idKey];
621
+ dimension.divisions[lastDivisionId].values[leafId] = node;
622
+ index++;
623
+ }
624
+ }
625
+ delete divisions[dimension.nodeId];
612
626
  }
613
627
  } else if (typeof ((_e = dimension.operations) == null ? void 0 : _e.sortFunction) === "function") {
614
628
  dimension.divisions = Object.fromEntries(
@@ -659,7 +673,7 @@ var scaffoldDimensions = (options, nodes) => {
659
673
  }
660
674
  }
661
675
  });
662
- if (options.dimensions.adjustDimensions) {
676
+ if ((_c = options.dimensions) == null ? void 0 : _c.adjustDimensions) {
663
677
  dimensions = options.dimensions.adjustDimensions(dimensions);
664
678
  }
665
679
  return dimensions;
@@ -774,6 +788,22 @@ var buildEdges = (options, nodes, dimensions) => {
774
788
  "source"
775
789
  );
776
790
  }
791
+ const findNextNonEmptyDivIdx = (fromIdx) => {
792
+ for (let step = 1; step < divisionKeys.length; step++) {
793
+ const idx = (fromIdx + step) % divisionKeys.length;
794
+ if (Object.keys(dimension.divisions[divisionKeys[idx]].values).length > 0)
795
+ return idx;
796
+ }
797
+ return null;
798
+ };
799
+ const findPrevNonEmptyDivIdx = (fromIdx) => {
800
+ for (let step = 1; step < divisionKeys.length; step++) {
801
+ const idx = (fromIdx - step + divisionKeys.length) % divisionKeys.length;
802
+ if (Object.keys(dimension.divisions[divisionKeys[idx]].values).length > 0)
803
+ return idx;
804
+ }
805
+ return null;
806
+ };
777
807
  let j = 0;
778
808
  divisionKeys.forEach((d) => {
779
809
  let division = dimension.divisions[d];
@@ -796,13 +826,15 @@ var buildEdges = (options, nodes, dimensions) => {
796
826
  } else {
797
827
  createEdge(division.id, dimension.nodeId, dimension.navigationRules.parent_child, "source");
798
828
  }
799
- const firstChildId = typeof options.idKey === "function" ? options.idKey(division.values[valueKeys[0]]) : options.idKey;
800
- createEdge(
801
- division.id,
802
- division.values[valueKeys[0]][firstChildId],
803
- dimension.navigationRules.parent_child,
804
- "source"
805
- );
829
+ if (valueKeys.length > 0) {
830
+ const firstChildId = typeof options.idKey === "function" ? options.idKey(division.values[valueKeys[0]]) : options.idKey;
831
+ createEdge(
832
+ division.id,
833
+ division.values[valueKeys[0]][firstChildId],
834
+ dimension.navigationRules.parent_child,
835
+ "source"
836
+ );
837
+ }
806
838
  let i = 0;
807
839
  if (valueKeys.length >= 1) {
808
840
  valueKeys.forEach((vk) => {
@@ -823,22 +855,13 @@ var buildEdges = (options, nodes, dimensions) => {
823
855
  dimension.navigationRules.sibling_sibling
824
856
  );
825
857
  } else if (i === valueKeys.length - 1 && extents2 === "bridgedCousins") {
826
- if (j !== divisionKeys.length - 1) {
827
- const targetId = typeof options.idKey === "function" ? options.idKey(
828
- dimension.divisions[divisionKeys[j + 1]].values[valueKeys[0]]
829
- ) : options.idKey;
830
- createEdge(
831
- v[id],
832
- dimension.divisions[divisionKeys[j + 1]].values[valueKeys[0]][targetId],
833
- dimension.navigationRules.sibling_sibling
834
- );
835
- } else {
836
- const targetId = typeof options.idKey === "function" ? options.idKey(dimension.divisions[divisionKeys[0]].values[valueKeys[0]]) : options.idKey;
837
- createEdge(
838
- v[id],
839
- dimension.divisions[divisionKeys[0]].values[valueKeys[0]][targetId],
840
- dimension.navigationRules.sibling_sibling
841
- );
858
+ const nextIdx = findNextNonEmptyDivIdx(j);
859
+ if (nextIdx !== null) {
860
+ const nextDivValues = dimension.divisions[divisionKeys[nextIdx]].values;
861
+ const nextDivValueKeys = Object.keys(nextDivValues);
862
+ const targetDatum = nextDivValues[nextDivValueKeys[0]];
863
+ const targetId = typeof options.idKey === "function" ? options.idKey(targetDatum) : options.idKey;
864
+ createEdge(v[id], targetDatum[targetId], dimension.navigationRules.sibling_sibling);
842
865
  }
843
866
  } else if (i === valueKeys.length - 1 && extents2 === "bridgedCustom") {
844
867
  createEdge(
@@ -855,24 +878,13 @@ var buildEdges = (options, nodes, dimensions) => {
855
878
  );
856
879
  }
857
880
  if (!i && extents2 === "bridgedCousins") {
858
- if (j !== 0) {
859
- const targetId = typeof options.idKey === "function" ? options.idKey(
860
- dimension.divisions[divisionKeys[j - 1]].values[valueKeys[valueKeys.length - 1]]
861
- ) : options.idKey;
862
- createEdge(
863
- dimension.divisions[divisionKeys[j - 1]].values[valueKeys[valueKeys.length - 1]][targetId],
864
- v[id],
865
- dimension.navigationRules.sibling_sibling
866
- );
867
- } else {
868
- const targetId = typeof options.idKey === "function" ? options.idKey(
869
- dimension.divisions[divisionKeys[divisionKeys.length - 1]].values[valueKeys[valueKeys.length - 1]]
870
- ) : options.idKey;
871
- createEdge(
872
- dimension.divisions[divisionKeys[divisionKeys.length - 1]].values[valueKeys[valueKeys.length - 1]][targetId],
873
- v[id],
874
- dimension.navigationRules.sibling_sibling
875
- );
881
+ const prevIdx = findPrevNonEmptyDivIdx(j);
882
+ if (prevIdx !== null) {
883
+ const prevDivValues = dimension.divisions[divisionKeys[prevIdx]].values;
884
+ const prevDivValueKeys = Object.keys(prevDivValues);
885
+ const targetDatum = prevDivValues[prevDivValueKeys[prevDivValueKeys.length - 1]];
886
+ const targetId = typeof options.idKey === "function" ? options.idKey(targetDatum) : options.idKey;
887
+ createEdge(targetDatum[targetId], v[id], dimension.navigationRules.sibling_sibling);
876
888
  }
877
889
  } else if (!i && extents2 === "bridgedCustom") {
878
890
  createEdge(
@@ -1470,9 +1482,7 @@ var fuzzyMatch = (input, candidates, labels = {}) => {
1470
1482
  const exactName = candidates.find((c) => c.toLowerCase() === lower);
1471
1483
  if (exactName)
1472
1484
  return { match: exactName, ambiguous: [] };
1473
- const exactLabel = candidates.find(
1474
- (c) => labels[c] && labels[c].toLowerCase() === lower
1475
- );
1485
+ const exactLabel = candidates.find((c) => labels[c] && labels[c].toLowerCase() === lower);
1476
1486
  if (exactLabel)
1477
1487
  return { match: exactLabel, ambiguous: [] };
1478
1488
  const namePrefix = candidates.filter((c) => c.toLowerCase().startsWith(lower));
@@ -1566,6 +1576,8 @@ var text_chat_default = (options) => {
1566
1576
  commandLabels = {},
1567
1577
  onNavigate,
1568
1578
  onExit,
1579
+ onClick,
1580
+ onHover,
1569
1581
  llm,
1570
1582
  data
1571
1583
  } = options;
@@ -1730,12 +1742,16 @@ var text_chat_default = (options) => {
1730
1742
  }
1731
1743
  });
1732
1744
  if (llm) {
1733
- addSystemMessage('Text navigation ready. Type "enter" to begin navigating, "help" for commands, or ask a question about the data.');
1734
- addSystemMessage('Note: AI-generated answers may be inaccurate. You can ask the model to "verify" any answer \u2014 it will attempt to provide a Python script that checks the claim against the dataset. If a claim cannot be verified with code, it should be verified externally.');
1745
+ addSystemMessage(
1746
+ 'Text navigation ready. Type "enter" to begin navigating, "help" for commands, or ask a question about the data.'
1747
+ );
1748
+ addSystemMessage(
1749
+ 'Note: AI-generated answers may be inaccurate. You can ask the model to "verify" any answer \u2014 it will attempt to provide a Python script that checks the claim against the dataset. If a claim cannot be verified with code, it should be verified externally.'
1750
+ );
1735
1751
  } else {
1736
1752
  addSystemMessage('Text navigation ready. Type "enter" to begin or "help" for available commands.');
1737
1753
  }
1738
- const specialCommands = ["enter", "help", "more", "more help", "clear"];
1754
+ const specialCommands = ["enter", "help", "more", "more help", "clear", "click", "select", "hover", "inspect"];
1739
1755
  const moveToNode = (nodeId) => {
1740
1756
  const node = inputHandler.moveTo(nodeId);
1741
1757
  if (node) {
@@ -1786,15 +1802,51 @@ var text_chat_default = (options) => {
1786
1802
  }
1787
1803
  if (lower === "help") {
1788
1804
  const llmHint = llm ? " You can also type any question about the data." : "";
1805
+ const interactionHints = [];
1806
+ if (onClick)
1807
+ interactionHints.push('"click" or "select"');
1808
+ if (onHover)
1809
+ interactionHints.push('"hover" or "inspect"');
1810
+ const interactionSuffix = interactionHints.length ? ` Interaction: ${interactionHints.join(", ")}.` : "";
1789
1811
  if (!currentNodeId) {
1790
1812
  addResponse(
1791
- 'Not yet in the structure. Type "enter" to begin navigating, or "move to <search>" to jump to a node.' + llmHint
1813
+ 'Not yet in the structure. Type "enter" to begin navigating, or "move to <search>" to jump to a node.' + interactionSuffix + llmHint
1792
1814
  );
1793
1815
  } else {
1794
1816
  const node = structure.nodes[currentNodeId];
1795
1817
  const available = getAvailableRules(currentNodeId, node, structure);
1796
1818
  const formatted = available.map((r) => formatRule(r, commandLabels));
1797
- addResponse(`Available: ${formatted.join(", ")}, move to <search>.` + llmHint);
1819
+ addResponse(`Available: ${formatted.join(", ")}, move to <search>.` + interactionSuffix + llmHint);
1820
+ }
1821
+ return;
1822
+ }
1823
+ if (lower === "click" || lower === "select") {
1824
+ if (!currentNodeId) {
1825
+ addResponse('Not in the structure. Type "enter" to begin.');
1826
+ return;
1827
+ }
1828
+ const node = structure.nodes[currentNodeId];
1829
+ if (onClick && node) {
1830
+ onClick(node);
1831
+ addResponse(`Clicked: ${describeNode2(node)}`);
1832
+ } else {
1833
+ addResponse(onClick ? "Nothing to click here." : "Click interaction is not enabled for this chart.");
1834
+ }
1835
+ return;
1836
+ }
1837
+ if (lower === "hover" || lower === "inspect") {
1838
+ if (!currentNodeId) {
1839
+ addResponse('Not in the structure. Type "enter" to begin.');
1840
+ return;
1841
+ }
1842
+ const node = structure.nodes[currentNodeId];
1843
+ if (onHover && node) {
1844
+ onHover(node);
1845
+ addResponse(`Hovering over: ${describeNode2(node)}`);
1846
+ } else {
1847
+ addResponse(
1848
+ onHover ? "Nothing to hover over here." : "Hover interaction is not enabled for this chart."
1849
+ );
1798
1850
  }
1799
1851
  return;
1800
1852
  }
@@ -1831,7 +1883,9 @@ var text_chat_default = (options) => {
1831
1883
  return;
1832
1884
  }
1833
1885
  const llmHint = llm ? " Enter an API key above to ask questions about the data." : "";
1834
- addResponse('Type "enter" to begin navigating the structure, or "move to <search>" to jump to a node.' + llmHint);
1886
+ addResponse(
1887
+ 'Type "enter" to begin navigating the structure, or "move to <search>" to jump to a node.' + llmHint
1888
+ );
1835
1889
  return;
1836
1890
  }
1837
1891
  const allRules = getAllRuleNames(structure);