@peter.prib/renderengine 0.13.0 → 0.15.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.
Files changed (124) hide show
  1. package/README.md +480 -302
  2. package/bin/renderengine.mjs +157 -0
  3. package/dist/renderengine-workspace.umd.js +49217 -37484
  4. package/dist/renderengine.umd.js +24899 -13093
  5. package/documentation/REFERENCE.md +68 -1
  6. package/package.json +13 -3
  7. package/src/CanvasRenderer.js +225 -200
  8. package/src/SVGRenderer.js +476 -454
  9. package/src/archimate.js +22 -2
  10. package/src/archimateArchiFormat.js +1735 -1701
  11. package/src/archimateArchive.js +36 -7
  12. package/src/archimateCSV.js +93 -37
  13. package/src/archimateDrawio.js +2 -0
  14. package/src/archimateExchangeFormat.js +3 -1
  15. package/src/chartCatalog.js +269 -3
  16. package/src/charting/Annotations.js +169 -0
  17. package/src/charting/ArchimateDiagramEditor.js +54 -10
  18. package/src/charting/Axis.js +217 -5
  19. package/src/charting/Chart.js +99 -9
  20. package/src/charting/Chart3D.js +7 -2
  21. package/src/charting/ChartBox.js +8 -1
  22. package/src/charting/ColorPalette.js +42 -4
  23. package/src/charting/DiagramEditor.js +174 -39
  24. package/src/charting/DiagramEditorCommands.js +28 -0
  25. package/src/charting/Edge.js +7 -0
  26. package/src/charting/Hierarchy.js +142 -0
  27. package/src/charting/InteractionManager.js +4 -0
  28. package/src/charting/ModelFileFormat.js +5 -1
  29. package/src/charting/SettingsManager.js +35 -0
  30. package/src/charting/ShapePaths.js +82 -10
  31. package/src/charting/archimate/ArchimateCsvFileFormat.js +51 -12
  32. package/src/charting/archimate/ArchimateElementTemplate.js +235 -231
  33. package/src/charting/ask/AskBar.js +388 -0
  34. package/src/charting/ask/AskView.js +400 -0
  35. package/src/charting/charts/AlluvialDiagram.js +311 -0
  36. package/src/charting/charts/AreaChart.js +69 -15
  37. package/src/charting/charts/BarChart.js +38 -3
  38. package/src/charting/charts/BarChart3D.js +6 -4
  39. package/src/charting/charts/BarChart3DOrbit.js +3 -1
  40. package/src/charting/charts/BarChartBase.js +26 -6
  41. package/src/charting/charts/BeeswarmChart.js +164 -0
  42. package/src/charting/charts/BoxPlotChart.js +25 -3
  43. package/src/charting/charts/BubbleChart.js +4 -1
  44. package/src/charting/charts/CalendarHeatmapChart.js +2 -1
  45. package/src/charting/charts/CandlestickChart.js +172 -4
  46. package/src/charting/charts/ChordDiagram.js +107 -4
  47. package/src/charting/charts/CirclePackChart.js +81 -0
  48. package/src/charting/charts/ComboChart.js +233 -0
  49. package/src/charting/charts/ContourChart.js +310 -0
  50. package/src/charting/charts/DensityChart.js +1 -1
  51. package/src/charting/charts/DonutChart3D.js +13 -0
  52. package/src/charting/charts/DumbbellChart.js +217 -0
  53. package/src/charting/charts/EcdfChart.js +150 -0
  54. package/src/charting/charts/FunnelChart.js +5 -2
  55. package/src/charting/charts/GeoMapChart.js +132 -11
  56. package/src/charting/charts/GroupedBarChart.js +44 -3
  57. package/src/charting/charts/GroupedDistributionChart.js +26 -1
  58. package/src/charting/charts/HeatmapChart.js +6 -3
  59. package/src/charting/charts/HexbinChart.js +137 -0
  60. package/src/charting/charts/HierarchyChart.js +77 -3
  61. package/src/charting/charts/HistogramChart.js +2 -2
  62. package/src/charting/charts/HorizonChart.js +105 -0
  63. package/src/charting/charts/IcicleChart.js +47 -0
  64. package/src/charting/charts/KagiChart.js +167 -0
  65. package/src/charting/charts/LineChart.js +74 -18
  66. package/src/charting/charts/LollipopChart.js +104 -0
  67. package/src/charting/charts/MarimekkoChart.js +75 -0
  68. package/src/charting/charts/OhlcChart.js +79 -0
  69. package/src/charting/charts/OutlineChart.js +10 -8
  70. package/src/charting/charts/ParallelCoordinatesChart.js +11 -3
  71. package/src/charting/charts/ParetoChart.js +92 -0
  72. package/src/charting/charts/PieChart.js +195 -17
  73. package/src/charting/charts/PieChart3D.js +137 -0
  74. package/src/charting/charts/PipeChart.js +5 -2
  75. package/src/charting/charts/PointAndFigureChart.js +198 -0
  76. package/src/charting/charts/PolarAreaChart.js +189 -0
  77. package/src/charting/charts/PopulationPyramidChart.js +4 -4
  78. package/src/charting/charts/RadarChart.js +12 -5
  79. package/src/charting/charts/RadialBarChart.js +6 -3
  80. package/src/charting/charts/RenkoChart.js +178 -0
  81. package/src/charting/charts/RidgelineChart.js +113 -0
  82. package/src/charting/charts/SankeyDiagram.js +129 -6
  83. package/src/charting/charts/ScatterChart.js +19 -3
  84. package/src/charting/charts/ScatterMatrixChart.js +119 -0
  85. package/src/charting/charts/SlopegraphChart.js +205 -0
  86. package/src/charting/charts/SparklineTableChart.js +166 -0
  87. package/src/charting/charts/StackedBarChart.js +125 -11
  88. package/src/charting/charts/StackedBarChart3DOrbit.js +14 -6
  89. package/src/charting/charts/StreamGraphChart.js +18 -3
  90. package/src/charting/charts/SunburstChart.js +28 -8
  91. package/src/charting/charts/SurfaceChart3D.js +157 -0
  92. package/src/charting/charts/TreemapChart.js +31 -11
  93. package/src/charting/charts/ViolinChart.js +20 -2
  94. package/src/charting/charts/WaterfallChart.js +2 -2
  95. package/src/charting/charts/WordCloudChart.js +2 -2
  96. package/src/charting/datamodel/ArchimateDataLink.js +70 -0
  97. package/src/charting/datamodel/DataLineage.js +150 -0
  98. package/src/charting/datamodel/DataModel.js +83 -1
  99. package/src/charting/datamodel/ModelMapping.js +87 -0
  100. package/src/charting/datamodel/SqlDdl.js +110 -85
  101. package/src/charting/datamodel/index.js +3 -0
  102. package/src/charting/decision/DecisionWorkbench.js +295 -0
  103. package/src/charting/diagrams/ArchimateDiagram.js +25 -9
  104. package/src/charting/diagrams/ArchimateModel.js +1382 -1238
  105. package/src/charting/diagrams/GraphDiagram.js +1424 -1350
  106. package/src/charting/diagrams/SketchView.js +121 -0
  107. package/src/charting/diagrams/sketchBoxKinds.js +30 -0
  108. package/src/charting/modelTree/AnalysisPanel.js +101 -23
  109. package/src/charting/modelTree/NavigatorModel.js +410 -0
  110. package/src/charting/modelTree/NavigatorPanel.js +453 -0
  111. package/src/charting/pivot/PivotChart.js +310 -13
  112. package/src/charting/pivot/PivotGridChart.js +322 -34
  113. package/src/charting/pivot/PivotView.js +626 -21
  114. package/src/charting/report/TemplatedReport.js +547 -0
  115. package/src/charting/templates/BoxTemplate.js +28 -27
  116. package/src/charting/templates/PackageTemplate.js +26 -25
  117. package/src/charting/templates/SwimlaneTemplate.js +19 -18
  118. package/src/charting/templates/Template.js +219 -212
  119. package/src/cli.js +146 -0
  120. package/src/index.js +3370 -2899
  121. package/src/workspace/Workspace.js +126 -7
  122. package/src/workspace/archimateServices.js +4 -0
  123. package/src/workspace/parts/ArchimateCanvasPart.js +1 -1
  124. package/src/workspace/styles.js +36 -1
@@ -0,0 +1,388 @@
1
+ import { AskView } from './AskView.js';
2
+
3
+ /**
4
+ * AskBar — a self-contained browser UI component for asking questions with preemptive
5
+ * next-word prediction in a scrollable list (generalizing the Ask bar from
6
+ * `semanticengine/examples/browser/index.html` into a reusable RenderEngine capability).
7
+ *
8
+ * Follows the same OO encapsulation pattern as `ArchimateDiagramEditor`:
9
+ * - Owns DOM creation, CSS injection, event wiring, and keyboard/scroll management.
10
+ * - Separates pure state and prediction logic into `AskView` (`this.view`) so all logic is
11
+ * DOM-free testable under Node.js.
12
+ * - Includes a configurable **Predict next word** selector allowing the user (or host) to
13
+ * choose how many predicted items are visible at once in the scrollable list (e.g. `Off`,
14
+ * `3`, `5`, `8`, `10`), while scrolling (`overflow-y: auto`) or pressing `↑`/`↓` browses
15
+ * through the full list of predictions.
16
+ */
17
+ export class AskBar {
18
+ /** Approximate height in pixels of one suggestion row in the scrollable list. */
19
+ static ITEM_HEIGHT = 44;
20
+
21
+ /** Numbers each bar's list, so several bars on one page each name their own (ARIA ids). */
22
+ static _count = 0;
23
+
24
+ /**
25
+ * @param {HTMLElement|string} container host element or its DOM id
26
+ * @param {object} [options] all `AskView` options plus UI options:
27
+ * @param {Document} [options.document] DOM document (defaults to `globalThis.document`)
28
+ * @param {string} [options.placeholder='Ask a question…'] input placeholder
29
+ * @param {string} [options.buttonLabel='Run'] submit button label
30
+ * @param {string} [options.ariaLabel='Question'] input aria-label
31
+ * @param {boolean} [options.showPredictControl=true] whether to show the configurable "Predict next word" selector
32
+ * @param {string} [options.predictLabel='Predict next word:'] label for the predict selector
33
+ * @param {boolean} [options.autoRun=false] whether to run `onAsk` immediately upon mounting
34
+ * @param {object} [options.resultEngine] optional `RenderEngine` instance whose `resultView()` displays `{ rows, columns, complete }` returned by `onAsk`
35
+ */
36
+ constructor(container, options = {}) {
37
+ this.options = options;
38
+ this.doc = options.document ?? container?.ownerDocument ?? globalThis.document ?? null;
39
+ this.container = typeof container === 'string'
40
+ ? this.doc?.getElementById?.(container) ?? null
41
+ : container ?? null;
42
+ this.resultEngine = options.resultEngine ?? null;
43
+
44
+ const userOnAsk = options.onAsk;
45
+ this.listId = `re-ask-list-${++AskBar._count}`;
46
+ this._blurTimer = null;
47
+ // An answer (`{ rows, columns, complete }`), or a promise of one, is shown by resultEngine.
48
+ const show = (res) => {
49
+ if (res && typeof res === 'object' && Array.isArray(res.rows) && this.resultEngine?.resultView) {
50
+ this.resultEngine.resultView(res.rows, {
51
+ columns: res.columns,
52
+ complete: res.complete,
53
+ pageSize: options.pageSize ?? 10,
54
+ });
55
+ }
56
+ return res;
57
+ };
58
+ this.view = options.view instanceof AskView ? options.view : new AskView({
59
+ ...options,
60
+ onAsk: (question, view) => {
61
+ const res = userOnAsk?.(question, view, this);
62
+ return res && typeof res.then === 'function' ? Promise.resolve(res).then(show) : show(res);
63
+ },
64
+ onChange: (view) => {
65
+ this._syncFromView();
66
+ options.onChange?.(view, this);
67
+ },
68
+ });
69
+
70
+ this.root = null;
71
+ this.inputEl = null;
72
+ this.suggestEl = null;
73
+ this.predictSelectEl = null;
74
+ this.samplesEl = null;
75
+
76
+ if (this.container && this.doc) {
77
+ this.render();
78
+ if (options.autoRun && this.view.value) {
79
+ this.view.submit(this.view.value);
80
+ }
81
+ }
82
+ }
83
+
84
+ /** Injects the component's scoped stylesheet once per document, build-free and theme-aware. */
85
+ static _injectStyles(doc) {
86
+ if (!doc?.head || doc.getElementById?.('re-ask-bar-styles')) return;
87
+ const style = doc.createElement('style');
88
+ style.id = 're-ask-bar-styles';
89
+ style.textContent = `
90
+ .re-ask-bar { display: flex; flex-direction: column; gap: 6px; font: 14px/1.45 system-ui, sans-serif; color: var(--ws-fg, var(--fg, #1d1d1f)); }
91
+ .re-ask-form { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin: 0; }
92
+ .re-ask-input-wrapper { position: relative; flex: 1 1 260px; min-width: 180px; }
93
+ .re-ask-input { width: 100%; box-sizing: border-box; padding: 7px 10px; font: inherit; color: inherit; background: var(--ws-panel-bg, var(--bg, #fff)); border: 1px solid var(--ws-border, var(--line, #ccc)); border-radius: 6px; }
94
+ .re-ask-input:focus { outline: 2px solid var(--ws-accent, var(--accent, #2b5fd9)); outline-offset: -1px; }
95
+ .re-ask-submit { padding: 7px 14px; font: inherit; font-weight: 500; color: #fff; background: var(--ws-accent, var(--accent, #2b5fd9)); border: 0; border-radius: 6px; cursor: pointer; }
96
+ .re-ask-submit:hover { filter: brightness(1.06); }
97
+ .re-ask-predict-ctrl { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ws-muted, var(--muted, #666)); }
98
+ .re-ask-predict-select { padding: 5px 7px; font: inherit; color: var(--ws-fg, var(--fg, #1d1d1f)); background: var(--ws-panel-bg, var(--bg, #fff)); border: 1px solid var(--ws-border, var(--line, #ccc)); border-radius: 5px; }
99
+ .re-ask-suggest-box {
100
+ position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 250;
101
+ background: var(--ws-panel-bg, var(--bg, #fff));
102
+ border: 1px solid var(--ws-border, var(--line, #ccc));
103
+ border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.18);
104
+ overflow-y: auto; display: flex; flex-direction: column;
105
+ }
106
+ .re-ask-suggest-box[hidden] { display: none !important; }
107
+ .re-ask-suggest-item {
108
+ display: flex; align-items: center; justify-content: space-between;
109
+ padding: 7px 10px; cursor: pointer; font-size: 13px; gap: 8px; user-select: none;
110
+ box-sizing: border-box; min-height: 36px;
111
+ }
112
+ .re-ask-suggest-item:hover, .re-ask-suggest-item.active {
113
+ background: var(--ws-accent, var(--accent, #2b5fd9)); color: #fff;
114
+ }
115
+ .re-ask-suggest-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
116
+ .re-ask-suggest-text { font-family: ui-monospace, monospace; font-size: 13px; }
117
+ .re-ask-suggest-sense { font-size: 11px; color: var(--ws-muted, var(--muted, #6b6b70)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
118
+ .re-ask-suggest-item:hover .re-ask-suggest-sense,
119
+ .re-ask-suggest-item.active .re-ask-suggest-sense { color: rgba(255, 255, 255, 0.85); }
120
+ .re-ask-suggest-badges { display: flex; gap: 4px; align-items: center; flex-shrink: 0; }
121
+ .re-ask-suggest-badge {
122
+ font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em;
123
+ padding: 2px 6px; border-radius: 4px;
124
+ background: var(--ws-border, var(--line, #e0e0e4));
125
+ color: var(--ws-muted, var(--muted, #555)); flex-shrink: 0;
126
+ }
127
+ .re-ask-suggest-item:hover .re-ask-suggest-badge,
128
+ .re-ask-suggest-item.active .re-ask-suggest-badge {
129
+ background: rgba(255, 255, 255, 0.25); color: #fff;
130
+ }
131
+ .re-ask-samples { font-size: 12px; color: var(--ws-muted, var(--muted, #666)); display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
132
+ .re-ask-sample-pill {
133
+ font-family: ui-monospace, monospace; font-size: 11.5px;
134
+ padding: 2px 6px; border-radius: 4px; cursor: pointer;
135
+ background: rgba(127,127,127,0.12); border: 1px solid var(--ws-border, var(--line, #ddd));
136
+ color: var(--ws-fg, var(--fg, #222));
137
+ }
138
+ .re-ask-sample-pill:hover { border-color: var(--ws-accent, var(--accent, #2b5fd9)); }
139
+ `;
140
+ doc.head.appendChild(style);
141
+ }
142
+
143
+ /** Builds the AskBar DOM inside `this.container`. */
144
+ render() {
145
+ if (!this.container || !this.doc) return this;
146
+ AskBar._injectStyles(this.doc);
147
+
148
+ const root = this.doc.createElement('div');
149
+ root.className = 're-ask-bar';
150
+
151
+ const form = this.doc.createElement('form');
152
+ form.className = 're-ask-form';
153
+
154
+ const wrapper = this.doc.createElement('div');
155
+ wrapper.className = 're-ask-input-wrapper';
156
+
157
+ const input = this.doc.createElement('input');
158
+ input.className = 're-ask-input';
159
+ input.type = 'text';
160
+ input.value = this.view.value;
161
+ input.placeholder = this.options.placeholder ?? 'Ask a question…';
162
+ input.setAttribute('aria-label', this.options.ariaLabel ?? 'Question');
163
+ input.setAttribute('autocomplete', 'off');
164
+ input.setAttribute('spellcheck', 'false');
165
+ // A combobox over its list of completions (WAI-ARIA 1.2), so a screen reader follows ↑/↓.
166
+ input.setAttribute('role', 'combobox');
167
+ input.setAttribute('aria-autocomplete', 'list');
168
+ input.setAttribute('aria-controls', this.listId);
169
+ input.setAttribute('aria-expanded', 'false');
170
+
171
+ const suggestBox = this.doc.createElement('div');
172
+ suggestBox.className = 're-ask-suggest-box';
173
+ suggestBox.id = this.listId;
174
+ suggestBox.hidden = true;
175
+ suggestBox.setAttribute('role', 'listbox');
176
+ suggestBox.setAttribute('aria-label', 'Suggested completions');
177
+
178
+ wrapper.appendChild(input);
179
+ wrapper.appendChild(suggestBox);
180
+ form.appendChild(wrapper);
181
+
182
+ const submitBtn = this.doc.createElement('button');
183
+ submitBtn.type = 'submit';
184
+ submitBtn.className = 're-ask-submit';
185
+ submitBtn.textContent = this.options.buttonLabel ?? 'Run';
186
+ form.appendChild(submitBtn);
187
+
188
+ if (this.options.showPredictControl !== false) {
189
+ const ctrlLabel = this.doc.createElement('label');
190
+ ctrlLabel.className = 're-ask-predict-ctrl';
191
+ ctrlLabel.textContent = this.options.predictLabel ?? 'Predict next word:';
192
+
193
+ const select = this.doc.createElement('select');
194
+ select.className = 're-ask-predict-select';
195
+ select.setAttribute('aria-label', 'Predict next word in scrollable list');
196
+ for (const choice of this.view.predictChoices) {
197
+ const n = Number(choice);
198
+ const text = n <= 0 ? 'Off' : `${n} visible (scrollable)`;
199
+ const opt = this.doc.createElement('option');
200
+ opt.value = String(n);
201
+ opt.textContent = text;
202
+ if (n === this.view.predictCount) opt.selected = true;
203
+ select.appendChild(opt);
204
+ }
205
+ select.addEventListener('change', () => {
206
+ this.view.setPredictCount(Number(select.value));
207
+ });
208
+ ctrlLabel.appendChild(select);
209
+ form.appendChild(ctrlLabel);
210
+ this.predictSelectEl = select;
211
+ }
212
+
213
+ root.appendChild(form);
214
+
215
+ if (this.view.samples.length > 0) {
216
+ const samplesRow = this.doc.createElement('div');
217
+ samplesRow.className = 're-ask-samples';
218
+ const prefix = this.doc.createElement('span');
219
+ prefix.textContent = 'Try:';
220
+ samplesRow.appendChild(prefix);
221
+ for (const q of this.view.samples) {
222
+ const pill = this.doc.createElement('code');
223
+ pill.className = 're-ask-sample-pill';
224
+ pill.textContent = q;
225
+ pill.addEventListener('click', () => {
226
+ this.view.submit(q);
227
+ });
228
+ samplesRow.appendChild(pill);
229
+ }
230
+ root.appendChild(samplesRow);
231
+ this.samplesEl = samplesRow;
232
+ }
233
+
234
+ this.root = root;
235
+ this.inputEl = input;
236
+ this.suggestEl = suggestBox;
237
+ this._wireEvents(form, input);
238
+ this.container.replaceChildren ? this.container.replaceChildren(root) : (this.container.innerHTML = '', this.container.appendChild(root));
239
+ this._syncFromView();
240
+ return this;
241
+ }
242
+
243
+ _wireEvents(form, input) {
244
+ input.addEventListener('input', () => {
245
+ this.view.setInput(input.value, { suggest: true });
246
+ });
247
+ input.addEventListener('focus', () => {
248
+ if (this.view.predict) this.view.refresh();
249
+ });
250
+ input.addEventListener('blur', () => {
251
+ this._blurTimer = setTimeout(() => { this._blurTimer = null; this.view.close(); }, 150);
252
+ });
253
+ input.addEventListener('keydown', (e) => {
254
+ if (!this.view.open || this.view.items.length === 0) {
255
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
256
+ this.view.refresh();
257
+ }
258
+ return;
259
+ }
260
+ if (e.key === 'ArrowDown') {
261
+ e.preventDefault();
262
+ this.view.move(1);
263
+ } else if (e.key === 'ArrowUp') {
264
+ e.preventDefault();
265
+ this.view.move(-1);
266
+ } else if (e.key === 'Tab') {
267
+ e.preventDefault();
268
+ this.view.accept(this.view.selectedIndex >= 0 ? this.view.selectedIndex : 0);
269
+ this.inputEl?.focus?.();
270
+ } else if (e.key === 'Enter' && this.view.selectedIndex >= 0) {
271
+ e.preventDefault();
272
+ this.view.accept(this.view.selectedIndex);
273
+ this.inputEl?.focus?.();
274
+ } else if (e.key === 'Escape') {
275
+ this.view.close();
276
+ }
277
+ });
278
+ form.addEventListener('submit', (e) => {
279
+ e.preventDefault();
280
+ this.view.submit(input.value);
281
+ });
282
+ }
283
+
284
+ /** Swaps where suggestions come from (`AskView.setSuggest`): another model or ontology. */
285
+ setSuggest(suggest) {
286
+ this.view.setSuggest(suggest);
287
+ return this;
288
+ }
289
+
290
+ /** Sets the question shown, without suggesting. */
291
+ setValue(text) {
292
+ this.view.setInput(text, { suggest: false });
293
+ return this;
294
+ }
295
+
296
+ /** Puts the cursor in the question. */
297
+ focus() {
298
+ this.inputEl?.focus?.();
299
+ return this;
300
+ }
301
+
302
+ /** Takes the bar out of its container; a pending close is cancelled. */
303
+ destroy() {
304
+ if (this._blurTimer !== null) clearTimeout(this._blurTimer);
305
+ this._blurTimer = null;
306
+ this.root?.remove?.();
307
+ this.root = this.inputEl = this.suggestEl = this.predictSelectEl = this.samplesEl = null;
308
+ }
309
+
310
+ _syncFromView() {
311
+ if (!this.inputEl || !this.suggestEl) return;
312
+ if (this.inputEl.value !== this.view.value) {
313
+ this.inputEl.value = this.view.value;
314
+ }
315
+ if (this.predictSelectEl) {
316
+ const val = String(this.view.predictCount);
317
+ if (this.predictSelectEl.value !== val) this.predictSelectEl.value = val;
318
+ }
319
+
320
+ const active = this.view.selectedIndex;
321
+ if (active >= 0 && this.view.open) this.inputEl.setAttribute('aria-activedescendant', `${this.listId}-${active}`);
322
+ else this.inputEl.removeAttribute?.('aria-activedescendant');
323
+
324
+ if (!this.view.open || !this.view.predict || this.view.items.length === 0) {
325
+ this.inputEl.setAttribute('aria-expanded', 'false');
326
+ this.suggestEl.hidden = true;
327
+ if (this.suggestEl.replaceChildren) this.suggestEl.replaceChildren();
328
+ return;
329
+ }
330
+
331
+ this.suggestEl.style.maxHeight = `${Math.max(1, this.view.visibleCount) * AskBar.ITEM_HEIGHT}px`;
332
+ if (this.suggestEl.replaceChildren) this.suggestEl.replaceChildren();
333
+
334
+ this.view.items.forEach((item, idx) => {
335
+ const row = this.doc.createElement('div');
336
+ row.className = `re-ask-suggest-item${idx === this.view.selectedIndex ? ' active' : ''}`;
337
+ row.dataset.index = String(idx);
338
+ row.id = `${this.listId}-${idx}`;
339
+ row.setAttribute('role', 'option');
340
+ row.setAttribute('aria-selected', String(idx === this.view.selectedIndex));
341
+
342
+ const mainCol = this.doc.createElement('div');
343
+ mainCol.className = 're-ask-suggest-main';
344
+
345
+ const textSpan = this.doc.createElement('span');
346
+ textSpan.className = 're-ask-suggest-text';
347
+ textSpan.textContent = item.display;
348
+ mainCol.appendChild(textSpan);
349
+
350
+ if (item.sense) {
351
+ const senseSpan = this.doc.createElement('span');
352
+ senseSpan.className = 're-ask-suggest-sense';
353
+ senseSpan.textContent = `${item.sense}${item.reference ? ` → ${item.reference}` : ''}`;
354
+ mainCol.appendChild(senseSpan);
355
+ }
356
+
357
+ const badges = this.doc.createElement('div');
358
+ badges.className = 're-ask-suggest-badges';
359
+
360
+ if (item.relation && item.relation !== 'canonical' && item.relation !== 'function') {
361
+ const relBadge = this.doc.createElement('span');
362
+ relBadge.className = 're-ask-suggest-badge';
363
+ relBadge.textContent = item.relation;
364
+ badges.appendChild(relBadge);
365
+ }
366
+ if (item.kind) {
367
+ const kindBadge = this.doc.createElement('span');
368
+ kindBadge.className = 're-ask-suggest-badge';
369
+ kindBadge.textContent = item.kind;
370
+ badges.appendChild(kindBadge);
371
+ }
372
+
373
+ row.appendChild(mainCol);
374
+ row.appendChild(badges);
375
+ row.addEventListener('mousedown', (e) => {
376
+ e.preventDefault();
377
+ this.view.accept(idx);
378
+ this.inputEl?.focus?.();
379
+ });
380
+ this.suggestEl.appendChild(row);
381
+ if (idx === this.view.selectedIndex && typeof row.scrollIntoView === 'function') {
382
+ row.scrollIntoView({ block: 'nearest' });
383
+ }
384
+ });
385
+ this.suggestEl.hidden = false;
386
+ this.inputEl.setAttribute('aria-expanded', 'true');
387
+ }
388
+ }