Symbol search
@chart-on/ui includes a symbol search dialog: a search box over a list of results, with optional exchange and type filters, paging and full keyboard control. It reads from any object with a search method, so a data source can be passed in as is.
With the shell
import { createChartShell, symbolHeader, symbolSearch } from '@chart-on/ui';
const shell = createChartShell(el, {
toolbar: [
symbolHeader(), // shows the current symbol and opens the dialog
// ...
],
symbolSearch: symbolSearch({
source: dataSource, // or anything with search(query, request)
onSelect: (result) => shell.chart.setSymbol(result.symbol, interval),
}),
});The dialog is an optional part: a shell without the symbolSearch option leaves its code out, and symbolHeader() shows the symbol but is disabled.
Typing a letter or digit on the focused chart, or anywhere else in the shell outside a text field, opens the dialog with that character in the search box and the caret after it; the rest of the typing goes on in the search box. Keys bound to an action on chart.actions keep running their action, and nothing opens with Ctrl, Alt or Cmd held, during IME composition, or while a modal dialog is open. Pass openOnTyping: false to turn that off, for example when the host app uses those keys.
Favourite symbols
With the shell's favorites part as well, each result has a star that adds its symbol to the favourites (kind symbol). While the search box is empty and no filter is picked, the starred symbols are listed first under Favorites, as a watchlist. Each is looked up through the source with search(symbol, …), taking the result whose symbol matches; one the source no longer finds (a delisted symbol, say) is listed as unavailable: greyed out, not selectable, still starred, and its star removes it. Starring past the kind's limit is refused, and the status says so.
Shortcut
/ opens the dialog from anywhere in the shell: the chart, the toolbar, the bottom bar. Keys typed into a text field or an editable element stay there, and nothing happens while a modal dialog is open. Pass shortcutScope: 'document' to hear it anywhere on the page, for a page with a single chart. Ctrl+K (Cmd+K on a Mac) opens the command palette, which lists the symbol search; a shell without the commandPalette option gives Mod+K to the symbol search too.
The shortcut is the symbol-search action on chart.actions, labelled with the actionSymbolSearch shell message, so it is rebound or turned off like the chart's own (actions and shortcuts):
shell.chart.actions.setKeys('symbol-search', ['Alt+S']); // replace its default keys
shell.chart.actions.setKeys('symbol-search', []); // turn it offThe symbolHeader() preset keeps its tooltip ("Symbol search (/)") and aria-keyshortcuts in step with the action's keys, including later rebinding. For a header you build yourself, showShortcut does the same:
import { showShortcut } from '@chart-on/ui';
const stop = showShortcut(symbolButton, shell.chart, 'symbol-search');onSelect runs after the dialog has closed and focus has returned to where it was. Load the picked symbol there, e.g. through the data source with chart.setSymbol. The symbolHeader() preset follows the switch on its own; see toolbar presets.
On its own
import { createSymbolSearch } from '@chart-on/ui';
const search = createSymbolSearch(shell.el, { source, onSelect, openOnTyping: chart.root });
search.open('AB'); // open with a query
search.close();
search.isOpen;
search.destroy();Mount it in a .pc-shell element so it picks up the theme. It covers that element while open.
| Option | Default | |
|---|---|---|
source | Where results come from (see below) | |
onSelect(result) | Called with the picked result | |
debounce | 200 | Milliseconds to wait after the last keystroke before searching |
pageSize | 50 | Results per page |
openOnTyping | shell: the whole shell | Element on which typing a letter or digit opens the dialog; false for none |
favorites | in the shell: of('symbol') of its favourites | A FavoritesList of starred symbols: a star on each result, and the starred symbols listed first while the search box is empty |
shortcutScope | 'shell' | Shell only: where the shortcut is heard, 'shell' or 'document' |
chart | shell: its chart | Chart whose localization sets the dialog's language, read each time it opens; English without one |
Search source
const source: SymbolSearchSource = {
async search(query, { exchange = '', type = '', offset, limit }) {
const params = new URLSearchParams({ q: query, exchange, type, offset: String(offset), limit: String(limit) });
const res = await fetch(`/api/symbols?${params}`);
const { items, total } = await res.json();
return { items, hasMore: offset + items.length < total };
},
searchFilters: {
types: [{ value: 'stock', label: 'Stocks' }, { value: 'futures', label: 'Futures' }],
exchanges: [{ value: 'Exchange A', label: 'Exchange A' }, { value: 'Exchange B', label: 'Exchange B' }],
},
};search(query, request) returns either:
- an array: the whole result. The dialog shows it a page at a time as the list scrolls.
- a page,
{ items, hasMore }: the dialog asks for the next page withrequest.offset(results received so far) when the list nears its end.
The dialog searches as soon as it opens, also for an empty query; return what suits your source (everything, popular symbols, or nothing). Responses that arrive after a newer query are ignored, so a slow request never overwrites newer results. A rejected promise shows an error with a retry button.
| Request field | |
|---|---|
exchange? | Selected exchange filter value |
type? | Selected type filter value |
offset | Results already received for this query |
limit | Page size |
The exchange select sits beside the search box and shows only when exchanges lists two or more; the type tabs sit on a row below. The filters are also applied to the returned results, so a source that ignores them still shows the right rows. Filter values are compared with each result's exchange and type.
| Result field | |
|---|---|
symbol | Key handed to onSelect |
name | Shown when there is no description and it differs from symbol |
description? | Second column |
type? | Third column, e.g. 'stock', 'futures' |
exchange? | Last column (hidden when the dialog is narrow) |
logo? | Image URL shown before the symbol |
root? | Futures root; results with the same root are listed together under it |
Keyboard
| Key | Action |
|---|---|
| Letter or digit on the focused chart | Open with that character |
| / in the shell (and Ctrl/Cmd+K without the command palette) | Open (the symbol-search action) |
| ↓ / ↑ | Next / previous result (wraps once every result is loaded) |
| Page Down / Page Up | Move by a page |
| Enter | Pick the active result |
| Escape | Close (an open exchange list closes first) |
| Tab | Move between the search box, the active result's star (with favourites), filters and close button; focus stays in the dialog |
The search box is a combobox over a listbox, so screen readers announce the active result, and a status region announces the result count.