@aquera/nile-visualization 2.9.8 → 2.9.9
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-config.d.ts +1 -0
- package/dist/src/nile-chart/nile-chart.css.js +9 -0
- package/dist/src/nile-chart/nile-chart.d.ts +2 -0
- package/dist/src/nile-chart/nile-chart.js +22 -0
- package/dist/src/nile-filter-chart/nile-filter-chart.css.js +184 -107
- package/dist/src/nile-filter-chart/nile-filter-chart.d.ts +20 -3
- package/dist/src/nile-filter-chart/nile-filter-chart.js +97 -25
- package/dist/src/nile-filter-chart/utils/prompt.js +210 -60
- package/dist/src/nile-filter-chart/utils/types.d.ts +59 -25
- package/package.json +1 -1
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { html, nothing } from 'lit';
|
|
2
|
+
import { live } from 'lit/directives/live.js';
|
|
3
|
+
import { ref } from 'lit/directives/ref.js';
|
|
4
|
+
// Per-element ResizeObserver bookkeeping. WeakMap so observers are GC'd
|
|
5
|
+
// alongside their elements when the prompt is unmounted.
|
|
6
|
+
const _fieldObservers = new WeakMap();
|
|
2
7
|
const PUNCT_TOKENS = new Set(['(', ')', '[', ']', ',']);
|
|
3
8
|
/**
|
|
4
9
|
* Splits the input into the tokens analyzePromptCtx reasons about:
|
|
@@ -227,21 +232,28 @@ export async function validatePromptExpression(value, ctrl) {
|
|
|
227
232
|
return { ast, evaluate };
|
|
228
233
|
}
|
|
229
234
|
/**
|
|
230
|
-
* Renders one segment of the
|
|
231
|
-
*
|
|
232
|
-
*
|
|
235
|
+
* Renders one segment of the Prompt / Expression toggle. Defaults: Prompt
|
|
236
|
+
* shows "Prompt"; Expression shows "Expression". Either segment can be
|
|
237
|
+
* overridden with a label, an icon, or both.
|
|
233
238
|
*/
|
|
234
239
|
function renderModeToggleSegment(segValue, cfg) {
|
|
235
|
-
const defaults = segValue === '
|
|
236
|
-
? { label: '
|
|
237
|
-
: { label: '
|
|
240
|
+
const defaults = segValue === 'prompt'
|
|
241
|
+
? { label: 'Prompt', icon: undefined, aria: 'Prompt mode', glyph: 'ng-sparkles' }
|
|
242
|
+
: { label: 'Expression', icon: undefined, aria: 'Expression mode', glyph: 'ng-code' };
|
|
238
243
|
const label = cfg?.label ?? defaults.label;
|
|
239
244
|
const icon = cfg?.icon ?? defaults.icon;
|
|
240
245
|
const aria = cfg?.ariaLabel ?? label ?? defaults.aria;
|
|
241
246
|
return html `
|
|
242
247
|
<nile-button-toggle value="${segValue}" aria-label="${aria}">
|
|
243
248
|
${icon ? html `<nile-icon class="fc-prompt__mode-icon" name="${icon}" size="14"></nile-icon>` : nothing}
|
|
244
|
-
${label ? html `<span>${label}</span>` : nothing}
|
|
249
|
+
${label ? html `<span class="fc-prompt__mode-label">${label}</span>` : nothing}
|
|
250
|
+
<nile-glyph
|
|
251
|
+
class="fc-prompt__mode-glyph"
|
|
252
|
+
name="${defaults.glyph}"
|
|
253
|
+
size="14"
|
|
254
|
+
method="stroke"
|
|
255
|
+
aria-hidden="true"
|
|
256
|
+
></nile-glyph>
|
|
245
257
|
</nile-button-toggle>
|
|
246
258
|
`;
|
|
247
259
|
}
|
|
@@ -255,23 +267,16 @@ function renderModeToggle(host, ctrl, mode) {
|
|
|
255
267
|
@nile-change="${(e) => {
|
|
256
268
|
e.stopPropagation();
|
|
257
269
|
const next = e.detail?.value;
|
|
258
|
-
if (next === '
|
|
270
|
+
if (next === 'prompt' || next === 'expression')
|
|
259
271
|
host.setPromptMode(ctrl, next);
|
|
260
272
|
}}"
|
|
261
273
|
>
|
|
262
|
-
${renderModeToggleSegment('
|
|
263
|
-
${renderModeToggleSegment('
|
|
274
|
+
${renderModeToggleSegment('expression', toggleCfg?.expression)}
|
|
275
|
+
${renderModeToggleSegment('prompt', toggleCfg?.prompt)}
|
|
264
276
|
</nile-button-toggle-group>
|
|
265
277
|
`;
|
|
266
278
|
}
|
|
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';
|
|
279
|
+
const SUGGESTION_LAYOUT_STYLE_ID = 'fc-prompt-suggestion-layout-v4';
|
|
275
280
|
function ensureSuggestionLayoutStyles() {
|
|
276
281
|
if (typeof document === 'undefined')
|
|
277
282
|
return;
|
|
@@ -283,6 +288,41 @@ function ensureSuggestionLayoutStyles() {
|
|
|
283
288
|
.fc-prompt__menu nile-menu-item::part(label) {
|
|
284
289
|
flex: 1;
|
|
285
290
|
min-width: 0;
|
|
291
|
+
overflow: hidden;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.fc-prompt__suggestion-label {
|
|
295
|
+
display: block;
|
|
296
|
+
max-width: 100%;
|
|
297
|
+
overflow: hidden;
|
|
298
|
+
text-overflow: ellipsis;
|
|
299
|
+
white-space: nowrap;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.fc-prompt__suggestion-tooltip {
|
|
303
|
+
display: block;
|
|
304
|
+
flex: 1;
|
|
305
|
+
min-width: 0;
|
|
306
|
+
overflow: hidden;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
[data-tippy-root] {
|
|
312
|
+
max-width: var(--fc-prompt-tooltip-max-width, 320px);
|
|
313
|
+
}
|
|
314
|
+
[data-tippy-root] > .tippy-box,
|
|
315
|
+
.tippy-box[data-theme~='lite'] {
|
|
316
|
+
max-width: var(--fc-prompt-tooltip-max-width, 320px);
|
|
317
|
+
white-space: normal;
|
|
318
|
+
overflow-wrap: anywhere;
|
|
319
|
+
word-break: break-word;
|
|
320
|
+
}
|
|
321
|
+
[data-tippy-root] > .tippy-box > .tippy-content {
|
|
322
|
+
white-space: normal;
|
|
323
|
+
overflow-wrap: anywhere;
|
|
324
|
+
word-break: break-word;
|
|
325
|
+
max-width: 100%;
|
|
286
326
|
}
|
|
287
327
|
.fc-prompt__suggestion-tag {
|
|
288
328
|
flex-shrink: 0;
|
|
@@ -304,16 +344,26 @@ export function renderPrompt(host, ctrl) {
|
|
|
304
344
|
const animated = host.promptPlaceholder.get(ctrl.id) ?? '';
|
|
305
345
|
const error = host.promptErrors.get(ctrl.id);
|
|
306
346
|
const mode = host.promptModes.get(ctrl.id);
|
|
347
|
+
// Pick the placeholder for the active mode. Hybrid/Expression variants
|
|
348
|
+
// can expose mode-specific lists; everything else falls back to the shared
|
|
349
|
+
// `placeholders[0]`. The animated typewriter (when active) overrides all.
|
|
350
|
+
const supportsModePlaceholders = ctrl.variant === 'hybrid' || ctrl.variant === 'expression';
|
|
351
|
+
const modePlaceholder = supportsModePlaceholders && mode === 'expression' && ctrl.expressionPlaceholders?.length
|
|
352
|
+
? ctrl.expressionPlaceholders[0]
|
|
353
|
+
: supportsModePlaceholders && mode === 'prompt' && ctrl.promptPlaceholders?.length
|
|
354
|
+
? ctrl.promptPlaceholders[0]
|
|
355
|
+
: (ctrl.placeholders?.[0] ?? '');
|
|
356
|
+
const placeholder = animated || modePlaceholder;
|
|
307
357
|
// Toggle shows only when the consumer explicitly opted into a hybrid surface —
|
|
308
358
|
// the standalone `expression` variant is filtrex-only by design and never
|
|
309
359
|
// renders a Basic / JQL switch even though queryLanguage is enabled.
|
|
310
360
|
const showToggle = !!ctrl.queryLanguage?.enabled && !!mode && ctrl.variant !== 'expression';
|
|
311
361
|
// Suggestions + inline syntax highlighting are filtrex-mode-only:
|
|
312
362
|
// - `expression` variant → always on
|
|
313
|
-
// - `hybrid` variant → only when the toggle is on Expression
|
|
363
|
+
// - `hybrid` variant → only when the toggle is on Expression
|
|
314
364
|
// - `prompt` variant → never (plain free text)
|
|
315
365
|
const isFiltrexMode = ctrl.variant === 'expression'
|
|
316
|
-
|| (ctrl.variant === 'hybrid' && mode === '
|
|
366
|
+
|| (ctrl.variant === 'hybrid' && mode === 'expression');
|
|
317
367
|
const styleParts = [];
|
|
318
368
|
if (ctrl.gradientColors && ctrl.gradientColors.length > 0) {
|
|
319
369
|
const dir = ctrl.gradientDirection ?? '90deg';
|
|
@@ -376,7 +426,7 @@ export function renderPrompt(host, ctrl) {
|
|
|
376
426
|
default:
|
|
377
427
|
return userSuggestions.filter(s => s.type === 'attribute'
|
|
378
428
|
|| s.type === 'function'
|
|
379
|
-
|| s.type === '
|
|
429
|
+
|| s.type === 'suggestion'
|
|
380
430
|
|| !s.type);
|
|
381
431
|
}
|
|
382
432
|
})();
|
|
@@ -511,10 +561,19 @@ export function renderPrompt(host, ctrl) {
|
|
|
511
561
|
host.setPromptActiveIndex(ctrl.id, -1);
|
|
512
562
|
host.handlePromptInput(ctrl, next);
|
|
513
563
|
requestAnimationFrame(() => {
|
|
514
|
-
if (inputEl.isConnected
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
564
|
+
if (!inputEl.isConnected || !atEnd())
|
|
565
|
+
return;
|
|
566
|
+
// Over-scroll past max so the browser pins to the absolute righ t edge.
|
|
567
|
+
inputEl.scrollLeft = inputEl.scrollWidth + 100;
|
|
568
|
+
// Explicitly re-sync the overlay AFTER Lit has re-rendered its tokens
|
|
569
|
+
// for the new value. Relying on @scroll for this fails: that event
|
|
570
|
+
// fires when the browser scrolls the input on keystroke (BEFORE the
|
|
571
|
+
// re-render), at which point the overlay's old, shorter content
|
|
572
|
+
// clamps the synced scrollLeft to its stale max — leaving the typed
|
|
573
|
+
// glyph offscreen and the caret visually one char ahead of the text.
|
|
574
|
+
const highlightEl = inputEl.parentElement?.querySelector('.fc-prompt__highlight');
|
|
575
|
+
if (highlightEl)
|
|
576
|
+
highlightEl.scrollLeft = inputEl.scrollLeft;
|
|
518
577
|
});
|
|
519
578
|
};
|
|
520
579
|
const pickItem = (item, inputEl) => {
|
|
@@ -621,17 +680,19 @@ export function renderPrompt(host, ctrl) {
|
|
|
621
680
|
}
|
|
622
681
|
return;
|
|
623
682
|
}
|
|
683
|
+
if (e.key === 'Tab' && !e.shiftKey && len > 0) {
|
|
684
|
+
const idx = current >= 0 ? current : 0;
|
|
685
|
+
e.preventDefault();
|
|
686
|
+
e.stopPropagation();
|
|
687
|
+
pickItem(suggestionData[idx], inputEl);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
624
690
|
if (e.key === 'Escape' && current >= 0) {
|
|
625
691
|
e.preventDefault();
|
|
626
692
|
e.stopPropagation();
|
|
627
693
|
host.setPromptActiveIndex(ctrl.id, -1);
|
|
628
694
|
}
|
|
629
695
|
};
|
|
630
|
-
// The native <input> scrolls horizontally to keep the caret visible, but
|
|
631
|
-
// the syntax-highlight overlay (`.fc-prompt__highlight`) is `overflow:
|
|
632
|
-
// hidden` and would otherwise clip at the right edge — leaving the typed
|
|
633
|
-
// text invisible past the field width. Mirror the input's scrollLeft onto
|
|
634
|
-
// the overlay so both layers stay aligned.
|
|
635
696
|
const onScroll = (e) => {
|
|
636
697
|
const inputEl = e.currentTarget;
|
|
637
698
|
const highlightEl = inputEl.parentElement?.querySelector('.fc-prompt__highlight');
|
|
@@ -664,6 +725,27 @@ export function renderPrompt(host, ctrl) {
|
|
|
664
725
|
};
|
|
665
726
|
const isFocused = host.promptFocusedId === ctrl.id;
|
|
666
727
|
const dropdownOpen = isFiltrexMode && isFocused && suggestionData.length > 0;
|
|
728
|
+
// Clears the prompt value, fires the existing `nile-change` flow (via
|
|
729
|
+
// submitPrompt → _emitChange) and additionally emits a dedicated
|
|
730
|
+
// `nile-clear` event so consumers can distinguish a user-initiated clear
|
|
731
|
+
// from a normal value change. `source` lets them tell the two surfaces
|
|
732
|
+
// apart ('icon' = in-input cross, 'button' = sibling Clear filter button).
|
|
733
|
+
const clearValue = (e, source) => {
|
|
734
|
+
host.handlePromptInput(ctrl, '');
|
|
735
|
+
host.submitPrompt(ctrl);
|
|
736
|
+
host.setPromptActiveIndex(ctrl.id, -1);
|
|
737
|
+
host.emit('nile-clear', { id: ctrl.id, source });
|
|
738
|
+
const target = e.currentTarget;
|
|
739
|
+
const root = (target?.getRootNode() ?? document);
|
|
740
|
+
const inputId = `fc-prompt-${ctrl.id}`;
|
|
741
|
+
requestAnimationFrame(() => {
|
|
742
|
+
const inp = root.querySelector(`#${CSS.escape(inputId)}`);
|
|
743
|
+
if (!inp)
|
|
744
|
+
return;
|
|
745
|
+
inp.value = '';
|
|
746
|
+
inp.focus({ preventScroll: true });
|
|
747
|
+
});
|
|
748
|
+
};
|
|
667
749
|
return html `
|
|
668
750
|
<div class="fc-prompt-row">
|
|
669
751
|
<div class="${classes} fc-prompt--row-input" part="filter-prompt" style="${inlineStyle}">
|
|
@@ -683,30 +765,64 @@ export function renderPrompt(host, ctrl) {
|
|
|
683
765
|
slot="trigger"
|
|
684
766
|
class="fc-prompt__field${isFiltrexMode ? ' fc-prompt__field--highlight' : ''}"
|
|
685
767
|
part="filter-prompt-field"
|
|
768
|
+
${ref((el) => {
|
|
769
|
+
if (!el)
|
|
770
|
+
return;
|
|
771
|
+
if (_fieldObservers.has(el))
|
|
772
|
+
return;
|
|
773
|
+
const obs = new ResizeObserver(([entry]) => {
|
|
774
|
+
if (entry)
|
|
775
|
+
host.setPromptFieldWidth(ctrl.id, entry.contentRect.width);
|
|
776
|
+
});
|
|
777
|
+
obs.observe(el);
|
|
778
|
+
_fieldObservers.set(el, obs);
|
|
779
|
+
host.setPromptFieldWidth(ctrl.id, el.getBoundingClientRect().width);
|
|
780
|
+
})}
|
|
686
781
|
>
|
|
687
|
-
|
|
688
|
-
|
|
782
|
+
<div class="fc-prompt__field-input">
|
|
783
|
+
${isFiltrexMode ? html `
|
|
784
|
+
<div class="fc-prompt__highlight" aria-hidden="true">${tokens.map((tok) => {
|
|
689
785
|
const color = TOKEN_COLOR[tok.type] ?? 'inherit';
|
|
690
|
-
return html `<span style="color:${color}
|
|
786
|
+
return html `<span style="color:${color};">${tok.text}</span>`;
|
|
691
787
|
})}</div>
|
|
788
|
+
` : nothing}
|
|
789
|
+
<input
|
|
790
|
+
type="text"
|
|
791
|
+
class="fc-prompt__input"
|
|
792
|
+
id="fc-prompt-${ctrl.id}"
|
|
793
|
+
name="${ctrl.id}"
|
|
794
|
+
part="filter-prompt-input"
|
|
795
|
+
autocomplete="off"
|
|
796
|
+
spellcheck="false"
|
|
797
|
+
aria-invalid="${error ? 'true' : 'false'}"
|
|
798
|
+
placeholder="${placeholder}"
|
|
799
|
+
.value="${live(value)}"
|
|
800
|
+
@input="${onInput}"
|
|
801
|
+
@keydown="${onKeyDown}"
|
|
802
|
+
@scroll="${onScroll}"
|
|
803
|
+
@focus="${onFocus}"
|
|
804
|
+
@blur="${onBlur}"
|
|
805
|
+
/>
|
|
806
|
+
</div>
|
|
807
|
+
${value && ctrl.showClearIcon !== false ? html `
|
|
808
|
+
<nile-button
|
|
809
|
+
variant="ghost"
|
|
810
|
+
class="fc-prompt__clear-icon"
|
|
811
|
+
aria-label="Clear input"
|
|
812
|
+
title="Clear"
|
|
813
|
+
@mousedown="${(e) => e.preventDefault()}"
|
|
814
|
+
@click="${(e) => clearValue(e, 'icon')}"
|
|
815
|
+
>
|
|
816
|
+
<nile-glyph
|
|
817
|
+
slot="prefix"
|
|
818
|
+
name="close"
|
|
819
|
+
size="14"
|
|
820
|
+
method="fill"
|
|
821
|
+
color="var(--nile-colors-neutral-700, var(--ng-colors-text-quaternary-500, #6b7280))"
|
|
822
|
+
></nile-glyph>
|
|
823
|
+
</nile-button>
|
|
692
824
|
` : nothing}
|
|
693
|
-
|
|
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
|
-
/>
|
|
825
|
+
${showToggle ? renderModeToggle(host, ctrl, mode) : nothing}
|
|
710
826
|
</div>
|
|
711
827
|
${isFiltrexMode && suggestionData.length > 0 ? html `
|
|
712
828
|
<nile-menu
|
|
@@ -717,28 +833,45 @@ export function renderPrompt(host, ctrl) {
|
|
|
717
833
|
if (!item)
|
|
718
834
|
return;
|
|
719
835
|
const inputId = `fc-prompt-${ctrl.id}`;
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
836
|
+
// The dropdown is portaled to <body>, so the bubbled
|
|
837
|
+
// event's root is `document` — not the chart's shadow
|
|
838
|
+
// root that owns the input. Look the input up via the
|
|
839
|
+
// host's shadow root directly.
|
|
840
|
+
const inputEl = host.shadowRoot
|
|
841
|
+
?.querySelector(`#${CSS.escape(inputId)}`);
|
|
723
842
|
if (inputEl)
|
|
724
843
|
pickItem(item, inputEl);
|
|
725
844
|
}}"
|
|
726
845
|
>
|
|
727
|
-
${suggestionData.map((item, idx) =>
|
|
846
|
+
${suggestionData.map((item, idx) => {
|
|
847
|
+
const fieldWidth = host.promptFieldWidth.get(ctrl.id) ?? 320;
|
|
848
|
+
const charBudget = Math.max(8, Math.floor((fieldWidth - 80) / 7));
|
|
849
|
+
const isTruncated = item.label.length > charBudget;
|
|
850
|
+
const wantsTooltip = ctrl.suggestionTooltip && isTruncated;
|
|
851
|
+
const showTypeBadge = fieldWidth >= 480;
|
|
852
|
+
return html `
|
|
728
853
|
<nile-menu-item
|
|
729
854
|
class="fc-prompt__suggestion"
|
|
730
|
-
part="filter-prompt-suggestion${idx === activeIdx ? ' filter-prompt-suggestion-active' : ''}"
|
|
731
855
|
value="${idx}"
|
|
732
856
|
?active="${idx === activeIdx}"
|
|
733
857
|
>
|
|
734
|
-
${
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
858
|
+
${wantsTooltip
|
|
859
|
+
? html `<nile-lite-tooltip
|
|
860
|
+
hoist
|
|
861
|
+
placement="top"
|
|
862
|
+
content="${item.label}"
|
|
863
|
+
class="fc-prompt__suggestion-tooltip"
|
|
864
|
+
.maxWidth=${fieldWidth}
|
|
865
|
+
>
|
|
866
|
+
<span class="fc-prompt__suggestion-label">${item.label}</span>
|
|
867
|
+
</nile-lite-tooltip>`
|
|
868
|
+
: html `<span class="fc-prompt__suggestion-label">${item.label}</span>`}
|
|
869
|
+
${item.type && showTypeBadge ? html `<span slot="suffix" class="fc-prompt__suggestion-tag">${item.type}</span>` : nothing}
|
|
870
|
+
</nile-menu-item>`;
|
|
871
|
+
})}
|
|
738
872
|
</nile-menu>
|
|
739
873
|
` : nothing}
|
|
740
874
|
</nile-dropdown>
|
|
741
|
-
${showToggle ? renderModeToggle(host, ctrl, mode) : nothing}
|
|
742
875
|
</div>
|
|
743
876
|
${error ? html `
|
|
744
877
|
<div class="fc-prompt__error" role="alert">
|
|
@@ -747,6 +880,23 @@ export function renderPrompt(host, ctrl) {
|
|
|
747
880
|
</div>
|
|
748
881
|
` : nothing}
|
|
749
882
|
</div>
|
|
883
|
+
${ctrl.showClearButton === true ? html `
|
|
884
|
+
<nile-button
|
|
885
|
+
variant="secondary"
|
|
886
|
+
class="fc-prompt__clear-filter"
|
|
887
|
+
?disabled="${!value}"
|
|
888
|
+
@click="${(e) => clearValue(e, 'button')}"
|
|
889
|
+
>
|
|
890
|
+
<nile-glyph
|
|
891
|
+
slot="prefix"
|
|
892
|
+
name="close"
|
|
893
|
+
size="14"
|
|
894
|
+
method="fill"
|
|
895
|
+
color="currentColor"
|
|
896
|
+
></nile-glyph>
|
|
897
|
+
Clear
|
|
898
|
+
</nile-button>
|
|
899
|
+
` : nothing}
|
|
750
900
|
</div>`;
|
|
751
901
|
}
|
|
752
902
|
//# sourceMappingURL=prompt.js.map
|
|
@@ -30,13 +30,13 @@ export interface SuggestionItem {
|
|
|
30
30
|
* - `attribute` → field position (status, priority, …)
|
|
31
31
|
* - `operator` → after a known field (==, !=, <, …)
|
|
32
32
|
* - `value` → after `<attr> <op>` (scope per-attribute by setting `id` to the attribute's id)
|
|
33
|
-
* - `connector`
|
|
34
|
-
* - `keyword`
|
|
35
|
-
* - `function`
|
|
36
|
-
* - `
|
|
37
|
-
* - `clause`
|
|
33
|
+
* - `connector` → after a complete predicate (and, or, not, in)
|
|
34
|
+
* - `keyword` → trailing SQL-ish keywords (ORDER BY, ASC, DESC, LIMIT)
|
|
35
|
+
* - `function` → callable templates (abs(value), …)
|
|
36
|
+
* - `suggestion` → ready-made full expressions
|
|
37
|
+
* - `clause` → DEPRECATED. Treated as `connector` at routing time.
|
|
38
38
|
*/
|
|
39
|
-
type?: 'attribute' | 'operator' | 'value' | 'connector' | 'keyword' | 'function' | '
|
|
39
|
+
type?: 'attribute' | 'operator' | 'value' | 'connector' | 'keyword' | 'function' | 'suggestion' | 'clause';
|
|
40
40
|
/** Legacy: text inserted on pick. New shape uses `label` for both. */
|
|
41
41
|
value?: string;
|
|
42
42
|
/** Legacy: muted secondary text. */
|
|
@@ -46,25 +46,25 @@ export interface SuggestionItem {
|
|
|
46
46
|
}
|
|
47
47
|
/** Visual config for one segment of the prompt's mode toggle. */
|
|
48
48
|
export interface PromptModeToggleOption {
|
|
49
|
-
/** Visible text inside the segment. Defaults: '
|
|
49
|
+
/** Visible text inside the segment. Defaults: `'Prompt'` for the prompt segment, `'Expression'` for the expression segment. */
|
|
50
50
|
label?: string;
|
|
51
|
-
/** nile-glyph icon name (e.g. `'ng-ai_magic'`). Defaults: none
|
|
51
|
+
/** nile-glyph icon name (e.g. `'ng-ai_magic'`). Defaults: none. */
|
|
52
52
|
icon?: string;
|
|
53
53
|
/** ARIA label for screen readers — required when the segment is icon-only. */
|
|
54
54
|
ariaLabel?: string;
|
|
55
55
|
}
|
|
56
|
-
/** Override the
|
|
56
|
+
/** Override the Prompt / Expression toggle's two segments. */
|
|
57
57
|
export interface PromptModeToggleConfig {
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
prompt?: PromptModeToggleOption;
|
|
59
|
+
expression?: PromptModeToggleOption;
|
|
60
60
|
}
|
|
61
61
|
export interface QueryLanguageConfig {
|
|
62
62
|
/** Enables the query layer at all. Default: false. */
|
|
63
63
|
enabled: boolean;
|
|
64
64
|
/**
|
|
65
|
-
* Customise the
|
|
66
|
-
* -
|
|
67
|
-
* -
|
|
65
|
+
* Customise the Prompt / Expression mode toggle's segments. Defaults:
|
|
66
|
+
* - prompt → label `'Prompt'`
|
|
67
|
+
* - expression → label `'Expression'`
|
|
68
68
|
* Either segment can be overridden with a label, an icon, or both.
|
|
69
69
|
*/
|
|
70
70
|
toggle?: PromptModeToggleConfig;
|
|
@@ -211,14 +211,14 @@ export type QueryJson = {
|
|
|
211
211
|
* Detail emitted with `nile-change`.
|
|
212
212
|
*
|
|
213
213
|
* `filters[id]` shape depends on the variant + (for prompts) the runtime mode:
|
|
214
|
-
* - any non-prompt variant
|
|
215
|
-
* - prompt in `'
|
|
216
|
-
* - prompt in `'
|
|
217
|
-
* parse succeeded
|
|
218
|
-
* - prompt in `'
|
|
219
|
-
* pre-validation / parse fail
|
|
214
|
+
* - any non-prompt variant → the variant's natural value
|
|
215
|
+
* - prompt in `'prompt'` mode → plain `string` (raw text)
|
|
216
|
+
* - prompt in `'expression'` mode,
|
|
217
|
+
* parse succeeded → `QueryJson` ({ source, ast }) — JSON-serializable
|
|
218
|
+
* - prompt in `'expression'` mode,
|
|
219
|
+
* pre-validation / parse fail → plain `string` (the typed text)
|
|
220
220
|
*
|
|
221
|
-
* `errors` and `evaluators` are populated for prompt controls in
|
|
221
|
+
* `errors` and `evaluators` are populated for prompt controls in Expression mode.
|
|
222
222
|
*/
|
|
223
223
|
export type FilterChangeDetail = {
|
|
224
224
|
filters: Record<string, unknown>;
|
|
@@ -246,10 +246,26 @@ export interface FilterControl {
|
|
|
246
246
|
/**
|
|
247
247
|
* Placeholder text. Always an array — `placeholders[0]` is the initial /
|
|
248
248
|
* static placeholder used by every variant (search, dropdown, prompt).
|
|
249
|
-
* For the `prompt`
|
|
250
|
-
* with a typewriter effect.
|
|
249
|
+
* For the `prompt`, `hybrid`, and `expression` variants, additional
|
|
250
|
+
* entries are cycled through with a typewriter effect.
|
|
251
|
+
*
|
|
252
|
+
* For `hybrid`, you can supply mode-specific overrides via
|
|
253
|
+
* `promptPlaceholders` and `expressionPlaceholders`; when present they
|
|
254
|
+
* take precedence over `placeholders` for that mode.
|
|
251
255
|
*/
|
|
252
256
|
placeholders?: string[];
|
|
257
|
+
/**
|
|
258
|
+
* Hybrid variant only. Placeholder phrases used while the toggle is on
|
|
259
|
+
* the Prompt side. Cycled with the typewriter effect when multiple
|
|
260
|
+
* entries are provided. Falls back to `placeholders` when unset.
|
|
261
|
+
*/
|
|
262
|
+
promptPlaceholders?: string[];
|
|
263
|
+
/**
|
|
264
|
+
* Hybrid variant only. Placeholder phrases used while the toggle is on
|
|
265
|
+
* the Expression side. Cycled with the typewriter effect when multiple
|
|
266
|
+
* entries are provided. Falls back to `placeholders` when unset.
|
|
267
|
+
*/
|
|
268
|
+
expressionPlaceholders?: string[];
|
|
253
269
|
/** Typewriter speed in ms per character (default 60). */
|
|
254
270
|
typeSpeedMs?: number;
|
|
255
271
|
/** Pause before deleting a typed phrase (default 1400). */
|
|
@@ -262,6 +278,12 @@ export interface FilterControl {
|
|
|
262
278
|
gradientSpeedMs?: number;
|
|
263
279
|
/** Prompt variant only. When true, disables the animated AI gradient border. */
|
|
264
280
|
noAiBorder?: boolean;
|
|
281
|
+
/**
|
|
282
|
+
* Prompt variant only. Shows the inline clear (×) icon inside the input
|
|
283
|
+
* when the value is non-empty. Defaults to `false` (hidden). Set `true` to
|
|
284
|
+
* opt in.
|
|
285
|
+
*/
|
|
286
|
+
showClearIcon?: boolean;
|
|
265
287
|
/**
|
|
266
288
|
* Prompt variant only. Inline autocomplete suggestions shown below the
|
|
267
289
|
* input when the prompt is focused (Basic mode). Items are filtered
|
|
@@ -275,6 +297,8 @@ export interface FilterControl {
|
|
|
275
297
|
* - `'append'` → appends the suggestion to the existing text (space-separated)
|
|
276
298
|
*/
|
|
277
299
|
suggestionInsert?: 'replace' | 'append';
|
|
300
|
+
suggestionTooltip?: boolean;
|
|
301
|
+
showClearButton?: boolean;
|
|
278
302
|
valueB?: string;
|
|
279
303
|
operator?: string;
|
|
280
304
|
thresholdValue?: number | string;
|
|
@@ -290,6 +314,12 @@ export type FilterEntry = FilterControl | FilterGroup;
|
|
|
290
314
|
export type FilterChartConfig = {
|
|
291
315
|
type: 'filter';
|
|
292
316
|
controls: FilterEntry[];
|
|
317
|
+
/**
|
|
318
|
+
* Visual appearance of the filter chart.
|
|
319
|
+
* - `'default'` (omitted) → standard 16px / 20px padding around each control.
|
|
320
|
+
* - `'minimal'` → collapses control padding to 6px for compact layouts.
|
|
321
|
+
*/
|
|
322
|
+
appearance?: 'default' | 'minimal';
|
|
293
323
|
[key: string]: unknown;
|
|
294
324
|
};
|
|
295
325
|
export type FilterChartSeparatedPayload = {
|
|
@@ -301,8 +331,8 @@ export type FilterChartSeparatedPayload = {
|
|
|
301
331
|
* The chart class implements this so render functions stay decoupled
|
|
302
332
|
* from the LitElement subclass.
|
|
303
333
|
*/
|
|
304
|
-
/** Runtime mode for a prompt control. `'
|
|
305
|
-
export type PromptMode = '
|
|
334
|
+
/** Runtime mode for a prompt control. `'prompt'` = plain text, no validation. `'expression'` = strict filtrex parsing. */
|
|
335
|
+
export type PromptMode = 'prompt' | 'expression';
|
|
306
336
|
/**
|
|
307
337
|
* A `FilterControl` after the chart has filled in a guaranteed `id` (either
|
|
308
338
|
* the author-supplied one, or one derived from `label`). Render helpers and
|
|
@@ -335,4 +365,8 @@ export interface FilterChartHost {
|
|
|
335
365
|
setPromptActiveIndex(id: string, idx: number): void;
|
|
336
366
|
/** Mark a prompt control's input as focused / unfocused. */
|
|
337
367
|
setPromptFocused(id: string, focused: boolean): void;
|
|
368
|
+
/** Pixel width of the bordered prompt field, keyed by control id. */
|
|
369
|
+
readonly promptFieldWidth: Map<string, number>;
|
|
370
|
+
/** Update the tracked width for a prompt control (called from a ResizeObserver). */
|
|
371
|
+
setPromptFieldWidth(id: string, width: number): void;
|
|
338
372
|
}
|