@aquera/nile-visualization 2.9.4 → 2.9.6
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/src/nile-chart/nile-chart.d.ts +2 -0
- package/dist/src/nile-chart/nile-chart.js +25 -2
- package/dist/src/nile-filter-chart/nile-filter-chart.css.js +13 -17
- package/dist/src/nile-filter-chart/nile-filter-chart.d.ts +8 -0
- package/dist/src/nile-filter-chart/nile-filter-chart.js +37 -2
- package/dist/src/nile-filter-chart/utils/prompt.js +120 -44
- package/dist/src/nile-filter-chart/utils/types.d.ts +4 -0
- package/package.json +1 -1
|
@@ -111,6 +111,8 @@ export declare class NileChart extends NileElement {
|
|
|
111
111
|
addAiResponse(text: string): void;
|
|
112
112
|
private handleAiSend;
|
|
113
113
|
private switchType;
|
|
114
|
+
/** Single-control filter chart where the in-body label should be promoted to the chart header. */
|
|
115
|
+
private get isPromotableFilter();
|
|
114
116
|
private get headerTitle();
|
|
115
117
|
private get headerSubtitle();
|
|
116
118
|
private onHeaderSlotChange;
|
|
@@ -286,11 +286,32 @@ let NileChart = class NileChart extends NileElement {
|
|
|
286
286
|
config: converted,
|
|
287
287
|
});
|
|
288
288
|
}
|
|
289
|
+
/** Single-control filter chart where the in-body label should be promoted to the chart header. */
|
|
290
|
+
get isPromotableFilter() {
|
|
291
|
+
const cfg = (this.activeConfig ?? this.resolvedConfig);
|
|
292
|
+
return cfg?.type === 'filter'
|
|
293
|
+
&& Array.isArray(cfg?.controls)
|
|
294
|
+
&& cfg.controls.length === 1;
|
|
295
|
+
}
|
|
289
296
|
get headerTitle() {
|
|
290
|
-
|
|
297
|
+
const explicit = this.activeConfig?.chartTitle ?? this.resolvedConfig?.chartTitle;
|
|
298
|
+
if (explicit)
|
|
299
|
+
return explicit;
|
|
300
|
+
if (this.isPromotableFilter) {
|
|
301
|
+
const cfg = (this.activeConfig ?? this.resolvedConfig);
|
|
302
|
+
return cfg.controls[0]?.label ?? '';
|
|
303
|
+
}
|
|
304
|
+
return '';
|
|
291
305
|
}
|
|
292
306
|
get headerSubtitle() {
|
|
293
|
-
|
|
307
|
+
const explicit = this.activeConfig?.chartSubtitle ?? this.resolvedConfig?.chartSubtitle;
|
|
308
|
+
if (explicit)
|
|
309
|
+
return explicit;
|
|
310
|
+
if (this.isPromotableFilter) {
|
|
311
|
+
const cfg = (this.activeConfig ?? this.resolvedConfig);
|
|
312
|
+
return cfg.controls[0]?.description ?? '';
|
|
313
|
+
}
|
|
314
|
+
return '';
|
|
294
315
|
}
|
|
295
316
|
onHeaderSlotChange(e) {
|
|
296
317
|
const slot = e.target;
|
|
@@ -1675,8 +1696,10 @@ let NileChart = class NileChart extends NileElement {
|
|
|
1675
1696
|
></nile-data-grid>`;
|
|
1676
1697
|
}
|
|
1677
1698
|
case 'filter': {
|
|
1699
|
+
const promote = this.isPromotableFilter;
|
|
1678
1700
|
return html `<nile-filter-chart
|
|
1679
1701
|
.config=${{ chart: config }}
|
|
1702
|
+
?hide-control-headers=${promote}
|
|
1680
1703
|
@nile-change="${(e) => this.emit('nile-change', e.detail)}"
|
|
1681
1704
|
></nile-filter-chart>`;
|
|
1682
1705
|
}
|
|
@@ -90,7 +90,10 @@ export const styles = css `
|
|
|
90
90
|
color: var(--nile-colors-neutral-700, var(--ng-colors-text-tertiary-600));
|
|
91
91
|
text-transform: uppercase;
|
|
92
92
|
letter-spacing: 0.05em;
|
|
93
|
+
line-height: 1.4;
|
|
94
|
+
padding-top: 2px;
|
|
93
95
|
margin-bottom: var(--nile-spacing-4px, var(--ng-spacing-1));
|
|
96
|
+
overflow: visible;
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
.fc-control__desc {
|
|
@@ -487,9 +490,10 @@ export const styles = css `
|
|
|
487
490
|
outline: none;
|
|
488
491
|
background: transparent;
|
|
489
492
|
border-radius: inherit;
|
|
490
|
-
padding: var(--nile-spacing-
|
|
493
|
+
padding: var(--nile-spacing-md, var(--ng-spacing-2)) var(--nile-spacing-xl, var(--ng-spacing-4));
|
|
491
494
|
font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
|
|
492
495
|
font-size: var(--nile-type-scale-4, var(--ng-font-size-text-md));
|
|
496
|
+
font-weight: 400;
|
|
493
497
|
color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
|
|
494
498
|
line-height: 1.4;
|
|
495
499
|
box-sizing: border-box;
|
|
@@ -602,18 +606,14 @@ export const styles = css `
|
|
|
602
606
|
color: transparent;
|
|
603
607
|
}
|
|
604
608
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
top: calc(100% + 6px);
|
|
614
|
-
left: 0;
|
|
615
|
-
right: 0;
|
|
616
|
-
z-index: 20;
|
|
609
|
+
.fc-prompt__dropdown {
|
|
610
|
+
flex: 1;
|
|
611
|
+
min-width: 0;
|
|
612
|
+
display: block;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.fc-prompt__dropdown::part(panel) {
|
|
616
|
+
display: flex;
|
|
617
617
|
flex-direction: column;
|
|
618
618
|
max-height: 280px;
|
|
619
619
|
overflow-y: auto;
|
|
@@ -625,10 +625,6 @@ export const styles = css `
|
|
|
625
625
|
padding: var(--nile-spacing-4px, var(--ng-spacing-1));
|
|
626
626
|
}
|
|
627
627
|
|
|
628
|
-
.fc-prompt__inner:focus-within .fc-prompt__suggestions {
|
|
629
|
-
display: flex;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
628
|
.fc-prompt__suggestion {
|
|
633
629
|
display: flex;
|
|
634
630
|
align-items: center;
|
|
@@ -5,6 +5,9 @@ export type { NileTagVariant, FilterOption, QueryLanguageConfig, QueryNode, Quer
|
|
|
5
5
|
export declare class NileFilterChart extends NileElement implements FilterChartHost {
|
|
6
6
|
static get styles(): CSSResultArray;
|
|
7
7
|
config: FilterChartSeparatedPayload | null;
|
|
8
|
+
/** When set, the host (e.g. nile-chart) is already rendering the control's label/description
|
|
9
|
+
* in its own header — skip the in-body copies to avoid duplication. */
|
|
10
|
+
hideControlHeaders: boolean;
|
|
8
11
|
selectedValues: Map<string, unknown>;
|
|
9
12
|
private collapsedGroups;
|
|
10
13
|
/** Currently displayed (animated) placeholder text per prompt-variant control id. */
|
|
@@ -15,6 +18,9 @@ export declare class NileFilterChart extends NileElement implements FilterChartH
|
|
|
15
18
|
promptModes: Map<string, PromptMode>;
|
|
16
19
|
/** Highlighted suggestion index per prompt control id (-1 = none highlighted). */
|
|
17
20
|
promptActiveIndex: Map<string, number>;
|
|
21
|
+
/** Id of the prompt control whose input currently has focus (drives the
|
|
22
|
+
* nile-dropdown panel open state). null when no prompt is focused. */
|
|
23
|
+
promptFocusedId: string | null;
|
|
18
24
|
/** Active typewriter timers per prompt control id (so we can stop them). */
|
|
19
25
|
private _promptTimers;
|
|
20
26
|
/** Compiled filtrex predicates per prompt control id (strict-mode successes). */
|
|
@@ -30,6 +36,7 @@ export declare class NileFilterChart extends NileElement implements FilterChartH
|
|
|
30
36
|
connectedCallback(): void;
|
|
31
37
|
disconnectedCallback(): void;
|
|
32
38
|
updated(changed: Map<string, unknown>): void;
|
|
39
|
+
private _syncPortalDropdowns;
|
|
33
40
|
setValue(id: string, value: unknown): void;
|
|
34
41
|
/**
|
|
35
42
|
* Update a prompt control's value. The value lands in `selectedValues`
|
|
@@ -52,6 +59,7 @@ export declare class NileFilterChart extends NileElement implements FilterChartH
|
|
|
52
59
|
*/
|
|
53
60
|
submitPrompt(ctrl: NormalizedFilterControl): void;
|
|
54
61
|
setPromptActiveIndex(id: string, idx: number): void;
|
|
62
|
+
setPromptFocused(id: string, focused: boolean): void;
|
|
55
63
|
private _validateOrClear;
|
|
56
64
|
private _clearPromptValidation;
|
|
57
65
|
private _syncPromptAnimations;
|
|
@@ -21,6 +21,9 @@ let NileFilterChart = NileFilterChart_1 = class NileFilterChart extends NileElem
|
|
|
21
21
|
constructor() {
|
|
22
22
|
super(...arguments);
|
|
23
23
|
this.config = null;
|
|
24
|
+
/** When set, the host (e.g. nile-chart) is already rendering the control's label/description
|
|
25
|
+
* in its own header — skip the in-body copies to avoid duplication. */
|
|
26
|
+
this.hideControlHeaders = false;
|
|
24
27
|
this.selectedValues = new Map();
|
|
25
28
|
this.collapsedGroups = new Set();
|
|
26
29
|
/** Currently displayed (animated) placeholder text per prompt-variant control id. */
|
|
@@ -31,6 +34,9 @@ let NileFilterChart = NileFilterChart_1 = class NileFilterChart extends NileElem
|
|
|
31
34
|
this.promptModes = new Map();
|
|
32
35
|
/** Highlighted suggestion index per prompt control id (-1 = none highlighted). */
|
|
33
36
|
this.promptActiveIndex = new Map();
|
|
37
|
+
/** Id of the prompt control whose input currently has focus (drives the
|
|
38
|
+
* nile-dropdown panel open state). null when no prompt is focused. */
|
|
39
|
+
this.promptFocusedId = null;
|
|
34
40
|
/** Active typewriter timers per prompt control id (so we can stop them). */
|
|
35
41
|
this._promptTimers = new Map();
|
|
36
42
|
/** Compiled filtrex predicates per prompt control id (strict-mode successes). */
|
|
@@ -66,6 +72,20 @@ let NileFilterChart = NileFilterChart_1 = class NileFilterChart extends NileElem
|
|
|
66
72
|
this._initValues();
|
|
67
73
|
this._syncPromptAnimations();
|
|
68
74
|
}
|
|
75
|
+
// Keep portaled suggestion panels in sync with the latest panel content.
|
|
76
|
+
// nile-dropdown clones the panel once at open-time and never re-clones,
|
|
77
|
+
// so without this the suggestions appear frozen at the value they had
|
|
78
|
+
// when the dropdown first opened.
|
|
79
|
+
this._syncPortalDropdowns();
|
|
80
|
+
}
|
|
81
|
+
_syncPortalDropdowns() {
|
|
82
|
+
const dropdowns = this.shadowRoot?.querySelectorAll('nile-dropdown.fc-prompt__dropdown');
|
|
83
|
+
dropdowns?.forEach((dd) => {
|
|
84
|
+
const d = dd;
|
|
85
|
+
if (d.open && d.portal && d.portalManager?.clonedPanel) {
|
|
86
|
+
d.portalManager.updatePortalPanel?.();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
69
89
|
}
|
|
70
90
|
// ── FilterChartHost surface ─────────────────────────────────────────────────
|
|
71
91
|
setValue(id, value) {
|
|
@@ -124,6 +144,14 @@ let NileFilterChart = NileFilterChart_1 = class NileFilterChart extends NileElem
|
|
|
124
144
|
setPromptActiveIndex(id, idx) {
|
|
125
145
|
this.promptActiveIndex = new Map(this.promptActiveIndex).set(id, idx);
|
|
126
146
|
}
|
|
147
|
+
setPromptFocused(id, focused) {
|
|
148
|
+
if (focused) {
|
|
149
|
+
this.promptFocusedId = id;
|
|
150
|
+
}
|
|
151
|
+
else if (this.promptFocusedId === id) {
|
|
152
|
+
this.promptFocusedId = null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
127
155
|
_validateOrClear(ctrl, value, opts = {}) {
|
|
128
156
|
const isNql = this.promptModes.get(ctrl.id) === 'nql';
|
|
129
157
|
if (value.trim() === '') {
|
|
@@ -468,10 +496,11 @@ let NileFilterChart = NileFilterChart_1 = class NileFilterChart extends NileElem
|
|
|
468
496
|
break;
|
|
469
497
|
default: body = html ``;
|
|
470
498
|
}
|
|
499
|
+
const showHeader = !this.hideControlHeaders;
|
|
471
500
|
return html `
|
|
472
501
|
<div class="fc-control" part="filter-control">
|
|
473
|
-
${ctrl.label ? html `<div class="fc-control__label">${ctrl.label}</div>` : nothing}
|
|
474
|
-
${ctrl.description ? html `<div class="fc-control__desc">${ctrl.description}</div>` : nothing}
|
|
502
|
+
${showHeader && ctrl.label ? html `<div class="fc-control__label">${ctrl.label}</div>` : nothing}
|
|
503
|
+
${showHeader && ctrl.description ? html `<div class="fc-control__desc">${ctrl.description}</div>` : nothing}
|
|
475
504
|
<div class="fc-control__body">${body}</div>
|
|
476
505
|
</div>`;
|
|
477
506
|
}
|
|
@@ -541,6 +570,9 @@ NileFilterChart._PROMPT_DEBOUNCE_MS = 500;
|
|
|
541
570
|
__decorate([
|
|
542
571
|
property({ attribute: false })
|
|
543
572
|
], NileFilterChart.prototype, "config", void 0);
|
|
573
|
+
__decorate([
|
|
574
|
+
property({ type: Boolean, attribute: 'hide-control-headers', reflect: true })
|
|
575
|
+
], NileFilterChart.prototype, "hideControlHeaders", void 0);
|
|
544
576
|
__decorate([
|
|
545
577
|
state()
|
|
546
578
|
], NileFilterChart.prototype, "selectedValues", void 0);
|
|
@@ -559,6 +591,9 @@ __decorate([
|
|
|
559
591
|
__decorate([
|
|
560
592
|
state()
|
|
561
593
|
], NileFilterChart.prototype, "promptActiveIndex", void 0);
|
|
594
|
+
__decorate([
|
|
595
|
+
state()
|
|
596
|
+
], NileFilterChart.prototype, "promptFocusedId", void 0);
|
|
562
597
|
NileFilterChart = NileFilterChart_1 = __decorate([
|
|
563
598
|
customElement('nile-filter-chart')
|
|
564
599
|
], NileFilterChart);
|
|
@@ -264,7 +264,42 @@ function renderModeToggle(host, ctrl, mode) {
|
|
|
264
264
|
</nile-button-toggle-group>
|
|
265
265
|
`;
|
|
266
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Layout-only global stylesheet for the portaled suggestion menu. The portal
|
|
269
|
+
* clones the panel into document.body, so component-scoped CSS can't reach
|
|
270
|
+
* it — these two rules use nile-menu-item's exposed `label` part to grow the
|
|
271
|
+
* label so the suffix (type pill) sticks to the row's right edge, and style
|
|
272
|
+
* the pill itself. Idempotent: bails if already injected.
|
|
273
|
+
*/
|
|
274
|
+
const SUGGESTION_LAYOUT_STYLE_ID = 'fc-prompt-suggestion-layout';
|
|
275
|
+
function ensureSuggestionLayoutStyles() {
|
|
276
|
+
if (typeof document === 'undefined')
|
|
277
|
+
return;
|
|
278
|
+
if (document.getElementById(SUGGESTION_LAYOUT_STYLE_ID))
|
|
279
|
+
return;
|
|
280
|
+
const el = document.createElement('style');
|
|
281
|
+
el.id = SUGGESTION_LAYOUT_STYLE_ID;
|
|
282
|
+
el.textContent = `
|
|
283
|
+
.fc-prompt__menu nile-menu-item::part(label) {
|
|
284
|
+
flex: 1;
|
|
285
|
+
min-width: 0;
|
|
286
|
+
}
|
|
287
|
+
.fc-prompt__suggestion-tag {
|
|
288
|
+
flex-shrink: 0;
|
|
289
|
+
padding: 1px 8px;
|
|
290
|
+
border-radius: 999px;
|
|
291
|
+
background: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));
|
|
292
|
+
color: var(--nile-colors-neutral-700, var(--ng-colors-text-quaternary-500));
|
|
293
|
+
font-size: 10px;
|
|
294
|
+
font-weight: 600;
|
|
295
|
+
text-transform: uppercase;
|
|
296
|
+
letter-spacing: 0.04em;
|
|
297
|
+
}
|
|
298
|
+
`;
|
|
299
|
+
document.head.appendChild(el);
|
|
300
|
+
}
|
|
267
301
|
export function renderPrompt(host, ctrl) {
|
|
302
|
+
ensureSuggestionLayoutStyles();
|
|
268
303
|
const value = String(host.selectedValues.get(ctrl.id) ?? '');
|
|
269
304
|
const animated = host.promptPlaceholder.get(ctrl.id) ?? '';
|
|
270
305
|
const error = host.promptErrors.get(ctrl.id);
|
|
@@ -608,60 +643,101 @@ export function renderPrompt(host, ctrl) {
|
|
|
608
643
|
ctrl.noAiBorder ? 'fc-prompt--no-ai-border' : '',
|
|
609
644
|
error ? 'fc-prompt--error' : '',
|
|
610
645
|
].filter(Boolean).join(' ');
|
|
646
|
+
const onFocus = () => host.setPromptFocused(ctrl.id, true);
|
|
647
|
+
// Don't close on blur if focus moved into the suggestion menu (or its
|
|
648
|
+
// portaled clone) — mousedown on a nile-menu-item momentarily shifts focus
|
|
649
|
+
// because of the menu's roving tab-index. We let nile-dropdown's own
|
|
650
|
+
// outside-click / Tab / Escape handlers close the panel.
|
|
651
|
+
const onBlur = (e) => {
|
|
652
|
+
const rel = e.relatedTarget;
|
|
653
|
+
if (rel) {
|
|
654
|
+
if (rel.closest('nile-menu-item'))
|
|
655
|
+
return;
|
|
656
|
+
if (rel.closest('nile-menu'))
|
|
657
|
+
return;
|
|
658
|
+
if (rel.closest('nile-dropdown'))
|
|
659
|
+
return;
|
|
660
|
+
if (rel.closest('.nile-dropdown-portal-append'))
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
host.setPromptFocused(ctrl.id, false);
|
|
664
|
+
};
|
|
665
|
+
const isFocused = host.promptFocusedId === ctrl.id;
|
|
666
|
+
const dropdownOpen = isFiltrexMode && isFocused && suggestionData.length > 0;
|
|
611
667
|
return html `
|
|
612
668
|
<div class="fc-prompt-row">
|
|
613
669
|
<div class="${classes} fc-prompt--row-input" part="filter-prompt" style="${inlineStyle}">
|
|
614
670
|
<div class="fc-prompt__inner">
|
|
615
|
-
<
|
|
616
|
-
|
|
617
|
-
|
|
671
|
+
<nile-dropdown
|
|
672
|
+
class="fc-prompt__dropdown"
|
|
673
|
+
placement="bottom-start"
|
|
674
|
+
sync="width"
|
|
675
|
+
distance="6"
|
|
676
|
+
hoist
|
|
677
|
+
portal
|
|
678
|
+
stay-open-on-select
|
|
679
|
+
?open=${dropdownOpen}
|
|
680
|
+
.noOpenOnClick=${true}
|
|
681
|
+
>
|
|
682
|
+
<div
|
|
683
|
+
slot="trigger"
|
|
684
|
+
class="fc-prompt__field${isFiltrexMode ? ' fc-prompt__field--highlight' : ''}"
|
|
685
|
+
part="filter-prompt-field"
|
|
686
|
+
>
|
|
687
|
+
${isFiltrexMode ? html `
|
|
688
|
+
<div class="fc-prompt__highlight" aria-hidden="true">${tokens.map((tok) => {
|
|
618
689
|
const color = TOKEN_COLOR[tok.type] ?? 'inherit';
|
|
619
690
|
return html `<span style="color:${color};${tok.type === 'keyword' ? 'font-weight:600;' : ''}">${tok.text}</span>`;
|
|
620
691
|
})}</div>
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const inputEl = e.currentTarget
|
|
653
|
-
.closest('.fc-prompt__inner')
|
|
654
|
-
?.querySelector('input.fc-prompt__input');
|
|
692
|
+
` : nothing}
|
|
693
|
+
<input
|
|
694
|
+
type="text"
|
|
695
|
+
class="fc-prompt__input"
|
|
696
|
+
id="fc-prompt-${ctrl.id}"
|
|
697
|
+
name="${ctrl.id}"
|
|
698
|
+
part="filter-prompt-input"
|
|
699
|
+
autocomplete="off"
|
|
700
|
+
spellcheck="false"
|
|
701
|
+
aria-invalid="${error ? 'true' : 'false'}"
|
|
702
|
+
placeholder="${animated || (ctrl.placeholders?.[0] ?? '')}"
|
|
703
|
+
.value="${value}"
|
|
704
|
+
@input="${onInput}"
|
|
705
|
+
@keydown="${onKeyDown}"
|
|
706
|
+
@scroll="${onScroll}"
|
|
707
|
+
@focus="${onFocus}"
|
|
708
|
+
@blur="${onBlur}"
|
|
709
|
+
/>
|
|
710
|
+
</div>
|
|
711
|
+
${isFiltrexMode && suggestionData.length > 0 ? html `
|
|
712
|
+
<nile-menu
|
|
713
|
+
class="fc-prompt__menu"
|
|
714
|
+
@nile-select="${(e) => {
|
|
715
|
+
const idx = Number(e.detail?.value);
|
|
716
|
+
const item = suggestionData[idx];
|
|
717
|
+
if (!item)
|
|
718
|
+
return;
|
|
719
|
+
const inputId = `fc-prompt-${ctrl.id}`;
|
|
720
|
+
const root = e.currentTarget.getRootNode();
|
|
721
|
+
const inputEl = (root.querySelector(`#${CSS.escape(inputId)}`)
|
|
722
|
+
?? document.getElementById(inputId));
|
|
655
723
|
if (inputEl)
|
|
656
724
|
pickItem(item, inputEl);
|
|
657
725
|
}}"
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
726
|
+
>
|
|
727
|
+
${suggestionData.map((item, idx) => html `
|
|
728
|
+
<nile-menu-item
|
|
729
|
+
class="fc-prompt__suggestion"
|
|
730
|
+
part="filter-prompt-suggestion${idx === activeIdx ? ' filter-prompt-suggestion-active' : ''}"
|
|
731
|
+
value="${idx}"
|
|
732
|
+
?active="${idx === activeIdx}"
|
|
733
|
+
>
|
|
734
|
+
${item.label}
|
|
735
|
+
${item.type ? html `<span slot="suffix" class="fc-prompt__suggestion-tag">${item.type}</span>` : nothing}
|
|
736
|
+
</nile-menu-item>
|
|
737
|
+
`)}
|
|
738
|
+
</nile-menu>
|
|
739
|
+
` : nothing}
|
|
740
|
+
</nile-dropdown>
|
|
665
741
|
${showToggle ? renderModeToggle(host, ctrl, mode) : nothing}
|
|
666
742
|
</div>
|
|
667
743
|
${error ? html `
|
|
@@ -321,6 +321,8 @@ export interface FilterChartHost {
|
|
|
321
321
|
readonly promptModes: Map<string, PromptMode>;
|
|
322
322
|
/** Highlighted suggestion index per prompt control id (-1 = none highlighted). */
|
|
323
323
|
readonly promptActiveIndex: Map<string, number>;
|
|
324
|
+
/** Id of the focused prompt control (drives the suggestion dropdown's open state). */
|
|
325
|
+
readonly promptFocusedId: string | null;
|
|
324
326
|
setValue(id: string, value: unknown): void;
|
|
325
327
|
emit(name: string, detail?: unknown): void;
|
|
326
328
|
/** Update a prompt's value and (when in NQL mode) re-validate it. */
|
|
@@ -331,4 +333,6 @@ export interface FilterChartHost {
|
|
|
331
333
|
submitPrompt(ctrl: NormalizedFilterControl): void;
|
|
332
334
|
/** Set the highlighted suggestion index. Use -1 to clear the highlight. */
|
|
333
335
|
setPromptActiveIndex(id: string, idx: number): void;
|
|
336
|
+
/** Mark a prompt control's input as focused / unfocused. */
|
|
337
|
+
setPromptFocused(id: string, focused: boolean): void;
|
|
334
338
|
}
|