@trdrs/quickcharts 1.2.0 → 1.3.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.
- package/CHANGELOG.md +36 -0
- package/README.md +582 -29
- package/dist/{chunk-RDAK5MUM.js → chunk-AA7XKXH4.js} +9 -5
- package/dist/{chunk-RDAK5MUM.js.map → chunk-AA7XKXH4.js.map} +1 -1
- package/dist/drawings.js +1 -1
- package/dist/feature-manifest.json +49 -1
- package/dist/index.d.ts +461 -91
- package/dist/index.js +9956 -8963
- package/dist/index.js.map +1 -1
- package/dist/quickcharts.css +122 -110
- package/dist/theme-manifest.json +90 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -918,14 +918,277 @@ function drive(widget: ChartWidget): void {
|
|
|
918
918
|
}
|
|
919
919
|
```
|
|
920
920
|
|
|
921
|
-
###
|
|
921
|
+
### Chart styles
|
|
922
922
|
|
|
923
|
-
`candles`, `hollow`, `bars`, `line`, `area`, `baseline`, `stepline`, listed
|
|
924
|
-
|
|
925
|
-
|
|
923
|
+
`candles`, `hollow`, `bars`, `line`, `area`, `baseline`, `stepline`, listed as `CHART_STYLES`. A
|
|
924
|
+
style switch is presentation: nothing refetches, and the loaded bars, indicators, drawings,
|
|
925
|
+
comparisons, scale and visible range all survive it. Four of the seven are value-shaped
|
|
926
926
|
(`valueShaped(style)`), which is the one predicate a host branches on when it renders open, high and
|
|
927
927
|
low values of its own.
|
|
928
928
|
|
|
929
|
+
By default a chart offers all seven styles, and the style picker groups them by family. `styles`
|
|
930
|
+
names the styles you offer, in the order the picker lists them, and `style` must be one of them:
|
|
931
|
+
|
|
932
|
+
```ts
|
|
933
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
934
|
+
|
|
935
|
+
declare const datafeed: ChartDatafeed
|
|
936
|
+
|
|
937
|
+
// Candles, line and area, in this order, opening on the line.
|
|
938
|
+
createChart({ container, datafeed, styles: ['candles', 'line', 'area'], style: 'line' })
|
|
939
|
+
|
|
940
|
+
// Every style and no picker: a control of your own runs the chart.style.<id> commands.
|
|
941
|
+
createChart({ container, datafeed, ui: { topBar: { styles: false } } })
|
|
942
|
+
|
|
943
|
+
// Candles and line, with no picker.
|
|
944
|
+
createChart({ container, datafeed, styles: ['candles', 'line'], ui: { topBar: { styles: false } } })
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
A style you leave out has no `chart.style.<id>` command, so no menu, shortcut or host control
|
|
948
|
+
reaches it, and `setStyle` ignores it. With one style offered the picker is not shown, since there
|
|
949
|
+
is nothing to choose. `ui.topBar.styles: false` hides the picker on its own terms, so you can
|
|
950
|
+
restrict the set, hide the picker, do both, or do neither. A saved layout, saved chart or stored
|
|
951
|
+
preference that names a style you left out opens on the first style in `styles`, and the rest of it
|
|
952
|
+
restores. An empty list, an id outside `CHART_STYLES`, a repeated id and a `style` outside the list
|
|
953
|
+
are setup errors that `createChart` throws.
|
|
954
|
+
|
|
955
|
+
### Offered timeframes
|
|
956
|
+
|
|
957
|
+
By default a chart offers the 26 presets (`TIMEFRAME_PRESETS`) and lets the viewer compose custom
|
|
958
|
+
timeframes. `timeframes` names the timeframe tokens you offer, presets or any other token the
|
|
959
|
+
grammar reads, and `timeframe` must be one of them. The order you give does not matter: the picker
|
|
960
|
+
lists each unit's group smallest first. `customTimeframes: false` keeps the presets and removes
|
|
961
|
+
custom timeframes:
|
|
962
|
+
|
|
963
|
+
```ts
|
|
964
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
965
|
+
|
|
966
|
+
declare const datafeed: ChartDatafeed
|
|
967
|
+
|
|
968
|
+
// Four timeframes, one of them beyond the presets, opening on five minutes.
|
|
969
|
+
createChart({ container, datafeed, timeframes: ['2m', '5m', '1h', '1d'], timeframe: '5m' })
|
|
970
|
+
|
|
971
|
+
// Every preset and no custom timeframes.
|
|
972
|
+
createChart({ container, datafeed, customTimeframes: false })
|
|
973
|
+
|
|
974
|
+
// Two timeframes and no picker: a control of your own runs the chart.timeframe.<token> commands.
|
|
975
|
+
createChart({ container, datafeed, timeframes: ['1m', '5m'], ui: { topBar: { timeframes: false } } })
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
A preset you leave out has no `chart.timeframe.<token>` command, and `chart.timeframe.set` and
|
|
979
|
+
`setTimeframe` ignore any token you leave out. The picker shows only what you list: a group with
|
|
980
|
+
no listed token is not drawn, a listed token beyond the presets sits in its unit's group without a
|
|
981
|
+
delete, and there is no custom-timeframe composer. The viewer's saved chips show where you list
|
|
982
|
+
them. When you list none of them, the chips are your five smallest timeframes, and the viewer's
|
|
983
|
+
stored chips stay stored for a chart that offers them. With one timeframe offered the picker is not
|
|
984
|
+
shown. `ui.topBar.timeframes: false` hides the picker on its own terms. A range preset whose
|
|
985
|
+
interval you leave out reads its span at your nearest coarser timeframe, or at your largest when
|
|
986
|
+
none is coarser. Within what you offer, the feed's `resolutions` and the symbol's
|
|
987
|
+
`supportedResolutions` still apply: a chip the feed or the symbol does not serve is disabled, and
|
|
988
|
+
the picker's list leaves its row out.
|
|
989
|
+
|
|
990
|
+
A saved layout, saved chart or stored preference that names a timeframe you left out opens on your
|
|
991
|
+
smallest timeframe, and the rest of it restores. With `customTimeframes: false` a stored custom
|
|
992
|
+
token opens on `1m`. An empty list, a token the grammar cannot read, a repeated token, a
|
|
993
|
+
`timeframe` or `layout.charts[].timeframe` outside what you offer, and `customTimeframes: true`
|
|
994
|
+
beside a list are setup errors that `createChart` throws. `customTimeframes` is the switch, and
|
|
995
|
+
`preferences.customTimeframes` is the list of custom tokens a first-run viewer starts with.
|
|
996
|
+
|
|
997
|
+
### Offered layouts
|
|
998
|
+
|
|
999
|
+
By default a chart offers all 55 arrangements (`ARRANGEMENTS`) and lets the viewer change every
|
|
1000
|
+
sync switch. `layouts` names the arrangement codes you offer, and the layout opens on
|
|
1001
|
+
`layout.arrangement` or else on the first code you list. `layoutSync` names the sync switches the
|
|
1002
|
+
viewer may change:
|
|
1003
|
+
|
|
1004
|
+
```ts
|
|
1005
|
+
import { createChart, type ChartDatafeed, type ChartSaveLoadAdapter } from '@trdrs/quickcharts'
|
|
1006
|
+
|
|
1007
|
+
declare const datafeed: ChartDatafeed
|
|
1008
|
+
declare const saveLoad: ChartSaveLoadAdapter
|
|
1009
|
+
|
|
1010
|
+
// A single chart: no layout setup menu, and no re-tile from any door.
|
|
1011
|
+
createChart({ container, datafeed, layouts: ['s'] })
|
|
1012
|
+
|
|
1013
|
+
// One chart, two side by side or a 2x2 grid, with the crosshair always synced and symbol sync the
|
|
1014
|
+
// only switch the viewer changes.
|
|
1015
|
+
createChart({ container, datafeed, layouts: ['s', '2h', '4'], layoutSync: ['symbol'], layout: { sync: { crosshair: true } } })
|
|
1016
|
+
|
|
1017
|
+
// Every arrangement, with the layout setup menu kept and the saved-layouts menu hidden.
|
|
1018
|
+
createChart({ container, datafeed, saveLoad, ui: { topBar: { savedLayouts: false } } })
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
The layout setup menu shows only the arrangements you list, in its own rows by chart count, and
|
|
1022
|
+
leaves out a row with none of them. An arrangement you leave out has no tile, and
|
|
1023
|
+
`widget.layout.setArrangement` and its command ignore it. With one arrangement offered there is
|
|
1024
|
+
nothing to choose, so the menu is not shown, unless that arrangement holds several charts and a
|
|
1025
|
+
sync switch is offered, in which case the menu holds the switches alone. A switch you leave out holds
|
|
1026
|
+
the value `layout.sync` gives it, or off: the menu does not show it, `widget.layout.setSync` and its
|
|
1027
|
+
command leave it as it is, and a saved layout cannot change it. An empty `layoutSync` fixes every
|
|
1028
|
+
switch.
|
|
1029
|
+
|
|
1030
|
+
A saved layout whose arrangement you left out opens on the offered arrangement with the most charts
|
|
1031
|
+
not above the saved count, the first one listed winning a tie. When every offered arrangement holds
|
|
1032
|
+
more charts, it opens on the one with the fewest, and the extra panes clone the first chart as a
|
|
1033
|
+
re-tile does. The saved charts past the ones shown are not dropped. The layout carries them with the
|
|
1034
|
+
saved arrangement and its divider positions, and a save writes them back unchanged beside the shown
|
|
1035
|
+
charts as they are now: a host offering only `['s']` opens a saved 4-chart layout as one chart, and
|
|
1036
|
+
after it saves, a host offering every arrangement still opens all four. A re-tile is a new
|
|
1037
|
+
arrangement and ends the carry, as it tears down the charts it has no pane for. A saved active chart
|
|
1038
|
+
among the hidden ones leaves the first chart active, and the sync switches restore as saved, apart
|
|
1039
|
+
from those `layoutSync` leaves out. An empty `layouts`, an unknown or repeated code, a
|
|
1040
|
+
`layout.arrangement` outside the list, and an unknown or repeated sync switch are setup errors that
|
|
1041
|
+
`createChart` throws.
|
|
1042
|
+
|
|
1043
|
+
`ui.topBar.layouts: false` hides the layout setup menu and the saved-layouts menu together;
|
|
1044
|
+
`ui.topBar.layoutSetup: false` and `ui.topBar.savedLayouts: false` hide one of them, and neither
|
|
1045
|
+
shows a menu `layouts: false` hid. The saved-layouts menu saves, opens and lists layouts, so it is
|
|
1046
|
+
shown only with a layouts store (`saveLoad.layouts`). Without one, its Download chart data row joins
|
|
1047
|
+
the image menu beside Download image and Copy image, the way an export menu offers a chart as a
|
|
1048
|
+
picture or as data.
|
|
1049
|
+
|
|
1050
|
+
### Offered drawing tools
|
|
1051
|
+
|
|
1052
|
+
By default a chart offers every drawing tool. `drawingTools` names the tools you offer, by the type
|
|
1053
|
+
`access.drawingTool` and the `tool.<type>` icon ids use: a type `drawingTools.all()` lists (from
|
|
1054
|
+
`@trdrs/quickcharts/drawings`), `zoom` or `eraser`. The order you give does not matter, because the
|
|
1055
|
+
rail keeps its own groups and sections:
|
|
1056
|
+
|
|
1057
|
+
```ts
|
|
1058
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
1059
|
+
|
|
1060
|
+
declare const datafeed: ChartDatafeed
|
|
1061
|
+
|
|
1062
|
+
// Lines, levels and a ruler: no shapes, patterns, notes or glyphs.
|
|
1063
|
+
createChart({ container, datafeed, drawingTools: ['trend_line', 'horizontal_line', 'fib_retracement', 'measure'] })
|
|
1064
|
+
|
|
1065
|
+
// A chart whose viewers keep and remove the drawings on it and make nothing new.
|
|
1066
|
+
createChart({ container, datafeed, drawingTools: ['eraser'] })
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
A tool you leave out is not drawn anywhere a tool is chosen: not in its group's flyout, not as a
|
|
1070
|
+
group's face, which wears the first tool the group offers, not on the favorites bar, and not on the
|
|
1071
|
+
rail for `measure` and `zoom`. A section or group it empties goes with it. The glyph picker's kinds
|
|
1072
|
+
are the `emoji`, `sticker` and `icon` tools, so a kind you leave out has no tab, and leaving out all
|
|
1073
|
+
three removes the glyph group. Every door that would arm it refuses: `chart.drawings.arm` answers
|
|
1074
|
+
`denied`, and `armTool`, `placeImage` and an image pasted over the chart place nothing. A copy is a
|
|
1075
|
+
new drawing, so a drawing of a tool you leave out is not copied into another: `chart.drawings.clone`
|
|
1076
|
+
and `chart.drawings.paste` are unavailable for it, `clone` and `paste` make nothing, and a
|
|
1077
|
+
Control- or Command-drag moves the drawing instead of duplicating it. Copying it to the clipboard is
|
|
1078
|
+
not refused, so it pastes on a chart that offers its tool.
|
|
1079
|
+
|
|
1080
|
+
Drawings of a tool you leave out that are already on the chart, from a saved chart or layout, a
|
|
1081
|
+
drawings document or another host, render and stay fully editable. They select, restyle through the
|
|
1082
|
+
selection's settings bar and the settings dialog, lock, hide and delete exactly as any drawing does:
|
|
1083
|
+
from the settings bar, the eraser, the remove menu and the Delete key. The eraser is always offered,
|
|
1084
|
+
listed or not, because it removes drawings rather than making them, which is why `['eraser']` is the
|
|
1085
|
+
way to offer no tool that creates one.
|
|
1086
|
+
|
|
1087
|
+
The list composes with your access policy: a tool is offered when you list it and the policy permits
|
|
1088
|
+
it, and a listed tool the policy refuses is drawn as `access.refused` says (see Refused controls).
|
|
1089
|
+
Nothing stored is rewritten: a starred tool you leave out keeps its star and returns to the favorites
|
|
1090
|
+
bar on a chart that offers it, and a group's remembered face is kept the same way.
|
|
1091
|
+
`features.drawings: false` removes drawing altogether, whatever the list says. An empty list, a type
|
|
1092
|
+
that names no tool and a repeated type are setup errors that `createChart` throws.
|
|
1093
|
+
|
|
1094
|
+
### Offered indicators
|
|
1095
|
+
|
|
1096
|
+
By default a chart offers every built-in indicator. `builtInIndicators` names the built-ins you
|
|
1097
|
+
offer, by the definition id `access.indicator` receives: an id `BUILT_IN_INDICATORS` lists, such as
|
|
1098
|
+
`sma`. The order you give does not matter, because the indicator browser keeps its own:
|
|
1099
|
+
|
|
1100
|
+
```ts
|
|
1101
|
+
import { BUILT_IN_INDICATORS, createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
1102
|
+
|
|
1103
|
+
declare const datafeed: ChartDatafeed
|
|
1104
|
+
|
|
1105
|
+
// Moving averages and RSI only.
|
|
1106
|
+
createChart({ container, datafeed, builtInIndicators: ['sma', 'ema', 'rsi'] })
|
|
1107
|
+
|
|
1108
|
+
// A chart that opens with a study it offers.
|
|
1109
|
+
const sma = BUILT_IN_INDICATORS.find((definition) => definition.id === 'sma')!
|
|
1110
|
+
createChart({ container, datafeed, builtInIndicators: ['sma'], indicators: [{ id: 'sma-1', definition: sma }] })
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
A built-in you leave out is not listed in the indicator browser: not among the built-ins, not
|
|
1114
|
+
among the favorites, and not in search results. Every door that would add one refuses:
|
|
1115
|
+
`chart.indicators.add` (from the browser, the keyboard or your own control) answers `denied`,
|
|
1116
|
+
`indicators.add` adds nothing, `indicators.set` leaves out an instance of it the chart does not
|
|
1117
|
+
already hold, and a new pane that a re-tile adds copies the first chart's studies without it. The
|
|
1118
|
+
chart has no verb that duplicates an indicator, so a second instance is an add like any other.
|
|
1119
|
+
|
|
1120
|
+
Instances of a built-in you leave out that are already on the chart, from a saved chart or layout,
|
|
1121
|
+
an undo step or another host sharing the store, render and stay fully editable and removable. The
|
|
1122
|
+
settings dialog, `chart.indicators.update`, the legend's eye and remove control, remove all and
|
|
1123
|
+
`indicators.remove` work on them exactly as on any instance, and an edit that would move one onto
|
|
1124
|
+
another built-in you leave out keeps it as it stands. Nothing saved is rewritten because of the
|
|
1125
|
+
list: a starred built-in you leave out keeps its star and returns to the favorites on a chart that
|
|
1126
|
+
offers it.
|
|
1127
|
+
|
|
1128
|
+
The list filters the built-ins alone. A definition of your own, whose `manifest.id` names no
|
|
1129
|
+
built-in, is never filtered, and neither is what your `indicatorPicker` source lists: its
|
|
1130
|
+
collections and rows are your content. The source is handed only the offered ids as `builtInIds`.
|
|
1131
|
+
The list composes with your access policy: a built-in is offered when you list it and the policy
|
|
1132
|
+
permits it, and a listed one the policy refuses is drawn as `access.refused` says (see Refused
|
|
1133
|
+
controls). A non-list, an empty list, an id that names no built-in, a repeated id and an
|
|
1134
|
+
`indicators` instance whose built-in you do not list are setup errors that `createChart` throws.
|
|
1135
|
+
|
|
1136
|
+
### Offered ranges
|
|
1137
|
+
|
|
1138
|
+
By default the bottom bar offers every range preset in `RANGE_PRESETS`, from `1D` to `All`.
|
|
1139
|
+
`ranges` names the presets you offer by their keys, in the order the bottom bar draws them:
|
|
1140
|
+
|
|
1141
|
+
```ts
|
|
1142
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
1143
|
+
|
|
1144
|
+
declare const datafeed: ChartDatafeed
|
|
1145
|
+
|
|
1146
|
+
// A day, a week's worth of sessions and a year, in this order.
|
|
1147
|
+
createChart({ container, datafeed, ranges: ['1D', '5D', '1Y'] })
|
|
1148
|
+
|
|
1149
|
+
// No range buttons: the bottom bar keeps its clock, timezone picker and session view.
|
|
1150
|
+
createChart({ container, datafeed, ranges: [] })
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
A preset you leave out has no `chart.range.<key>` command, so no menu, shortcut or host control
|
|
1154
|
+
reaches it, `chart.range.set` ignores its key, and the bottom bar draws no button for it. An empty
|
|
1155
|
+
list offers none, which is how you keep the rest of the bottom bar without range buttons;
|
|
1156
|
+
`ui.bottomBar: false` removes the whole bar. `chart.range.set` still takes an explicit
|
|
1157
|
+
`{ from, to }` window either way. A preset whose interval your `timeframes` leave out reads its
|
|
1158
|
+
span at your nearest coarser timeframe, as it does without a list. A non-list, a key that names no
|
|
1159
|
+
preset and a repeated key are setup errors that `createChart` throws.
|
|
1160
|
+
|
|
1161
|
+
### Offered timezones
|
|
1162
|
+
|
|
1163
|
+
By default the timezone picker offers every zone in `TIMEZONES` and the exchange choice.
|
|
1164
|
+
`timezones` names the choices you offer: zone ids, and `exchange` (`EXCHANGE_TIMEZONE`) for the
|
|
1165
|
+
charted symbol's own zone. The picker keeps its own order (UTC, the exchange choice, then every
|
|
1166
|
+
zone by its current offset); the order you give decides only which choice comes first:
|
|
1167
|
+
|
|
1168
|
+
```ts
|
|
1169
|
+
import { createChart, EXCHANGE_TIMEZONE, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
1170
|
+
|
|
1171
|
+
declare const datafeed: ChartDatafeed
|
|
1172
|
+
|
|
1173
|
+
// The exchange's own time, New York or London, opening on the exchange's time for a first-run viewer.
|
|
1174
|
+
createChart({ container, datafeed, timezones: [EXCHANGE_TIMEZONE, 'America/New_York', 'Europe/London'] })
|
|
1175
|
+
|
|
1176
|
+
// Always Tokyo time: no picker, and the clock reads Tokyo.
|
|
1177
|
+
createChart({ container, datafeed, timezones: ['Asia/Tokyo'] })
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
A choice you leave out has no `chart.timezone.<id>` command (`chart.timezone.exchange` for the
|
|
1181
|
+
exchange choice), `chart.timezone.set` and `setTimezone` ignore it, and the picker lists no row for
|
|
1182
|
+
it. With one choice offered the picker is not shown, since there is nothing to choose, and the
|
|
1183
|
+
bottom bar's clock still reads the time in that zone.
|
|
1184
|
+
|
|
1185
|
+
A chart opens on the viewer's stored choice, else on `preferences.timezone`, when you list it, and
|
|
1186
|
+
otherwise on the first choice you list. A stored choice you leave out is not rewritten: it stays
|
|
1187
|
+
stored until the viewer picks another zone, so a chart that offers it again opens on it. The
|
|
1188
|
+
exported registry is not filtered: `TIMEZONES`, `isTimezoneChoice` and `timezoneListing` answer
|
|
1189
|
+
the full set for code of your own. A non-list, an empty list, an id that names no zone and a
|
|
1190
|
+
repeated id are setup errors that `createChart` throws.
|
|
1191
|
+
|
|
929
1192
|
### Events
|
|
930
1193
|
|
|
931
1194
|
Typed maps, one per scope. Every subscription returns its unsubscribe and is inert after
|
|
@@ -981,7 +1244,9 @@ function toolbar(widget: ChartWidget): void {
|
|
|
981
1244
|
|
|
982
1245
|
A command whose one id spans many subjects declares `refuses(arg)` beside `available()`: an
|
|
983
1246
|
argument your access policy turns away answers `denied` before availability is asked, which is how
|
|
984
|
-
`chart.drawings.arm` refuses a tool.
|
|
1247
|
+
`chart.drawings.arm` refuses a tool. `onChange` fires when the registered set or a shortcut changes,
|
|
1248
|
+
and when `widget.refreshAccess()` says your policy may answer differently. A refusal is a value,
|
|
1249
|
+
never a throw: `ok`, `unavailable`,
|
|
985
1250
|
`denied`, `unknown`, or `failed` with the
|
|
986
1251
|
error. Your own commands register through the same door: a chart extension's `contributeCommands`
|
|
987
1252
|
puts them in this list with `scope: 'chart'` and its own label text.
|
|
@@ -1028,6 +1293,99 @@ The library reads `features` and `ui` once, when the widget is created. A key or
|
|
|
1028
1293
|
plane does not take throws a `TypeError` naming its path before anything mounts. The package's
|
|
1029
1294
|
`dist/feature-manifest.json` lists every flag in its `features` and `ui` blocks.
|
|
1030
1295
|
|
|
1296
|
+
### Refused controls
|
|
1297
|
+
|
|
1298
|
+
By default the chart's own controls draw what your access policy refuses disabled, which suits an
|
|
1299
|
+
offer the viewer can unlock. `access.refused: 'hide'` leaves it out instead, for a chart that simply
|
|
1300
|
+
does not offer it:
|
|
1301
|
+
|
|
1302
|
+
```ts
|
|
1303
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
1304
|
+
|
|
1305
|
+
declare const datafeed: ChartDatafeed
|
|
1306
|
+
declare const session: { may(feature: string): boolean }
|
|
1307
|
+
|
|
1308
|
+
createChart({
|
|
1309
|
+
container,
|
|
1310
|
+
datafeed,
|
|
1311
|
+
access: {
|
|
1312
|
+
refused: 'hide',
|
|
1313
|
+
command: (id) => id !== 'chart.replay.start' || session.may('replay'),
|
|
1314
|
+
drawingTool: (tool) => tool !== 'brush' && tool !== 'highlighter',
|
|
1315
|
+
indicator: (id) => id !== 'vwap',
|
|
1316
|
+
},
|
|
1317
|
+
})
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
With `'hide'`, a refused drawing tool is not in its group's flyout, on the favorites bar or in the
|
|
1321
|
+
glyph picker; a section, a group or a favorites bar it leaves empty goes with it, and a group's face
|
|
1322
|
+
that would wear a refused tool wears the first one the group still offers. A refused indicator is
|
|
1323
|
+
not in the indicator browser. A control or menu row whose command the policy refuses is not drawn
|
|
1324
|
+
(top bar, bottom bar, menus, the drawing toolbar and the selection's bar, the navigation cluster,
|
|
1325
|
+
the replay transport, legend row controls and the level menu), and a rule left with nothing beside it
|
|
1326
|
+
goes too. A picker always keeps its current choice: the chosen style, timeframe, timezone or scale
|
|
1327
|
+
is listed even when its command is refused.
|
|
1328
|
+
|
|
1329
|
+
Only a refusal hides. A permitted command that cannot run now (nothing to undo, no bars loaded,
|
|
1330
|
+
nothing selected) is drawn disabled as before. The policy is asked whenever the chrome syncs (a
|
|
1331
|
+
change on the active chart, a change to the command registry) and whenever a menu or flyout opens,
|
|
1332
|
+
and `widget.refreshAccess()` asks it again at any moment (see A policy that changes), so a policy
|
|
1333
|
+
that follows your session moves the controls with it. Nothing stored is rewritten: a
|
|
1334
|
+
viewer's favorite keeps its star and returns to the favorites bar once you permit the tool again,
|
|
1335
|
+
and drawings and indicators already on the chart render exactly as they do under a refusal by
|
|
1336
|
+
default. The keyboard, `widget.commands` and the chart handles refuse exactly as they do with
|
|
1337
|
+
`'disable'`. A value other than `'disable'` or `'hide'` is a setup error that `createChart` throws.
|
|
1338
|
+
|
|
1339
|
+
Under either value the policy refuses creating content, never what is already on the chart or in
|
|
1340
|
+
saved content: it refuses adding, never restoring. A saved chart, a layout load, an undo or a redo,
|
|
1341
|
+
a drawings document and shared drawing storage put back every indicator and drawing they carry,
|
|
1342
|
+
those the policy refuses included, so a load followed by a save never rewrites a viewer's content
|
|
1343
|
+
without them. An indicator whose definition `access.indicator` refuses, once on the chart (added
|
|
1344
|
+
before the policy changed, or put back by a restore), renders and stays fully editable and
|
|
1345
|
+
removable:
|
|
1346
|
+
`indicators.set`, `chart.indicators.update` and the settings dialog edit it, an edit of any other
|
|
1347
|
+
study keeps it, and an edit that would move it, or another study, onto a refused definition leaves
|
|
1348
|
+
that study as it stands. A new instance of a refused definition is still left out by every door. A
|
|
1349
|
+
drawing whose tool `access.drawingTool` refuses selects, restyles, locks, hides and deletes as any
|
|
1350
|
+
drawing does; a copy is a new drawing, so it is not cloned, pasted or duplicated by a Control- or
|
|
1351
|
+
Command-drag, while copying it to the clipboard is not refused.
|
|
1352
|
+
|
|
1353
|
+
### A policy that changes
|
|
1354
|
+
|
|
1355
|
+
The predicates are asked live: every door that runs a command, arms a tool or adds an indicator asks
|
|
1356
|
+
at that moment, so a refusal holds from the instant your policy answers it. The chart's own controls
|
|
1357
|
+
read the policy when they sync and when a menu opens. When its answers change and nothing on the
|
|
1358
|
+
chart does (a viewer's plan changed mid-session), call `widget.refreshAccess()`:
|
|
1359
|
+
|
|
1360
|
+
```ts
|
|
1361
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
1362
|
+
|
|
1363
|
+
declare const datafeed: ChartDatafeed
|
|
1364
|
+
declare const session: { may(feature: string): boolean; onChange(listener: () => void): void }
|
|
1365
|
+
|
|
1366
|
+
const widget = createChart({
|
|
1367
|
+
container,
|
|
1368
|
+
datafeed,
|
|
1369
|
+
access: {
|
|
1370
|
+
refused: 'hide',
|
|
1371
|
+
command: (id) => id !== 'chart.replay.start' || session.may('replay'),
|
|
1372
|
+
indicator: (id) => id !== 'vwap' || session.may('vwap'),
|
|
1373
|
+
},
|
|
1374
|
+
})
|
|
1375
|
+
session.onChange(() => widget.refreshAccess())
|
|
1376
|
+
```
|
|
1377
|
+
|
|
1378
|
+
Every surface that reads the policy reads it again at once: the top and bottom bars and their
|
|
1379
|
+
controls, the drawing toolbar's tools and groups, the favorites bar, the selection's bar, the
|
|
1380
|
+
navigation cluster, the replay transport and the legend's row controls, shown or left out as
|
|
1381
|
+
`access.refused` says and enabled or disabled. A menu, flyout or dialog that is open reads it too:
|
|
1382
|
+
the bars' menus, the level menu, the indicator browser and the saved layouts dialog rebuild their
|
|
1383
|
+
rows in place, and a drawing toolbar flyout (the glyph picker among them) or a selection bar panel
|
|
1384
|
+
opens again from its control, or closes when that control is now left out or disabled. Listeners of `widget.commands.onChange`
|
|
1385
|
+
hear it as well, so a control of your own can read `commands.available` again. Nothing stored
|
|
1386
|
+
changes, nothing on the chart changes, and no `saveNeeded` is raised: a refresh is a reading, never
|
|
1387
|
+
a write.
|
|
1388
|
+
|
|
1031
1389
|
### Your own interface
|
|
1032
1390
|
|
|
1033
1391
|
The widget supports three ways to present a chart, over the same behavior and the same commands.
|
|
@@ -1134,7 +1492,9 @@ const indicatorPicker: IndicatorPickerSource = {
|
|
|
1134
1492
|
}
|
|
1135
1493
|
```
|
|
1136
1494
|
|
|
1137
|
-
The source receives the collection, query and shipped definition ids
|
|
1495
|
+
The source receives the collection, query and the shipped definition ids the browser lists (only
|
|
1496
|
+
those `builtInIndicators` offers, when you name a list). Its own collections and rows are never
|
|
1497
|
+
filtered by that list. It owns filtering and any
|
|
1138
1498
|
service limits; the browser does not promise an unlimited catalog or implement paging for you.
|
|
1139
1499
|
Collection ids `builtin` and `favorites`, and action ids `add` and `favorite`, are reserved.
|
|
1140
1500
|
Item ids cannot collide with shipped definition ids or these reserved ids. Duplicate ids,
|
|
@@ -1159,8 +1519,9 @@ chrome is painted from it and renders nothing without it.
|
|
|
1159
1519
|
|
|
1160
1520
|
- **The top bar.** The symbol pill opens the symbol search for the active chart; the compare door
|
|
1161
1521
|
opens it in compare mode. The timeframe picker shows the saved timeframes as chips and a list of
|
|
1162
|
-
the 26 presets in five groups, each row savable as a
|
|
1163
|
-
|
|
1522
|
+
the timeframes the chart offers (by default the 26 presets in five groups), each row savable as a
|
|
1523
|
+
chip, with a composer for a custom interval under the unit's ceiling while custom timeframes are
|
|
1524
|
+
offered. The style picker lists the styles the chart offers. Indicators opens the picker
|
|
1164
1525
|
over the 23 built-in definitions, and the legend's gear opens the settings dialog for an instance
|
|
1165
1526
|
(inputs, style, visibility). Bar replay enters and leaves replay for the active chart; entering
|
|
1166
1527
|
asks where to begin rather than choosing a starting bar. Its starting-point menu answers with a
|
|
@@ -1168,19 +1529,20 @@ chrome is painted from it and renders nothing without it.
|
|
|
1168
1529
|
date runs `chart.replay.startFirst`: the chart pages the feed's history back to its oldest bar,
|
|
1169
1530
|
within the 20,000 bars one session holds, and starts there. Undo and
|
|
1170
1531
|
redo step back and forward through the active chart's own content, and each names the change it
|
|
1171
|
-
would move. Layout setup offers the
|
|
1172
|
-
arrangements and the five sync switches; the saved-layouts menu
|
|
1173
|
-
deletes layouts through `saveLoad.layouts` and marks
|
|
1532
|
+
would move. Layout setup offers the arrangements and sync switches the chart offers (by default
|
|
1533
|
+
the 55 arrangements and the five sync switches); the saved-layouts menu, shown with a layouts
|
|
1534
|
+
store, saves, copies, renames, opens and deletes layouts through `saveLoad.layouts` and marks
|
|
1535
|
+
unsaved changes, with an autosave switch and Download chart data.
|
|
1174
1536
|
Chart settings edits appearance, grid and session shading, the price-scale mode and the theme
|
|
1175
1537
|
mode, and its Reset defaults row runs `chart.appearance.reset`, which drops the viewer's own
|
|
1176
1538
|
appearance edits and returns the price scale to normal so the chart reads as the theme and your
|
|
1177
1539
|
constructor options paint it. `ui: { topBar: { settings: { theme: false } } }` removes its Theme
|
|
1178
1540
|
section and leaves the rest of the menu, for a host that offers the theme choice in its own
|
|
1179
|
-
settings; the widget's theme API and theme commands are untouched. Fullscreen and the image menu (Download image,
|
|
1180
|
-
the bar.
|
|
1181
|
-
- **The bottom bar.** The range presets, the clock in the display zone with the
|
|
1182
|
-
(UTC and the exchange choice first), and the session view for a
|
|
1183
|
-
regular hours.
|
|
1541
|
+
settings; the widget's theme API and theme commands are untouched. Fullscreen and the image menu (Download image, Copy image where the browser can, and
|
|
1542
|
+
Download chart data when there is no layouts store) close the bar.
|
|
1543
|
+
- **The bottom bar.** The range presets the chart offers, the clock in the display zone with the
|
|
1544
|
+
list of the timezones it offers (UTC and the exchange choice first), and the session view for a
|
|
1545
|
+
symbol that trades outside regular hours.
|
|
1184
1546
|
- **Around the charts.** A navigation cluster (zoom, scroll, reset) sits at the bottom of each
|
|
1185
1547
|
pane, and past one tile it carries a sixth control that fills the layout with the active tile or
|
|
1186
1548
|
gives the layout back, over `widget.layout.toggleMaximize`, the same verb the Alt press on a tile
|
|
@@ -1273,7 +1635,8 @@ null when `ui: { topBar: false }` removes the bar, which is what to check before
|
|
|
1273
1635
|
you would have nowhere to put.
|
|
1274
1636
|
|
|
1275
1637
|
Every control acts through the command registry and reflects `commands.available`, so a command
|
|
1276
|
-
your access policy refuses renders disabled and does nothing
|
|
1638
|
+
your access policy refuses renders disabled and does nothing, or is not drawn with
|
|
1639
|
+
`access.refused: 'hide'`. The saved-layouts menu runs the
|
|
1277
1640
|
layout verbs `widget.layout.save`, `rename`, `load`, `open`, `delete`, `create` and `autosave`; what a
|
|
1278
1641
|
verb did reports through the `layout` event and what it refused through `saveConflict`. Copy image
|
|
1279
1642
|
reports through the `image` event (a refused copy falls back to a download). Each control has a flag
|
|
@@ -1298,7 +1661,9 @@ trimmed.on('saveNeeded', () => note('the layout has unsaved changes'))
|
|
|
1298
1661
|
```
|
|
1299
1662
|
|
|
1300
1663
|
The search dialog's class chips come from your datafeed's `config().classes`, named through
|
|
1301
|
-
`search.classNames`; a class you do not name wears its token. `search.
|
|
1664
|
+
`search.classNames`; a class you do not name wears its token. `search.spreads`,
|
|
1665
|
+
`search.allClasses` and `search.classSelection` choose how the classes and the spread operators are
|
|
1666
|
+
offered, as "Classes and spread operators" under Search describes. `search.scope` names what your
|
|
1302
1667
|
search is limited to at the far edge of that strip, with the mark its `mark` paints; your datafeed
|
|
1303
1668
|
decides what is found. The range presets read your
|
|
1304
1669
|
datafeed's optional `earliestBar(symbol)`: a preset deeper than the history you serve is disabled.
|
|
@@ -1381,8 +1746,8 @@ before costs that one glyph its artwork. The control draws the chart's own glyph
|
|
|
1381
1746
|
`icons` covers the chart's own controls. The emoji, sticker and icon glyphs a drawing places come
|
|
1382
1747
|
through the asset port, and a chart extension draws its rows and layers from the
|
|
1383
1748
|
`ChartExtensionIcon` descriptors it contributes. The product's mark in the plot's corner is not an
|
|
1384
|
-
icon. `mountContextMenu`, `openSymbolSearch
|
|
1385
|
-
|
|
1749
|
+
icon. `mountContextMenu`, `openSymbolSearch`, `mountSymbolSearch`, `openTimeframePicker` and
|
|
1750
|
+
`mountTimeframePicker` take the same `icons` for a menu, search or picker you mount without a widget.
|
|
1386
1751
|
|
|
1387
1752
|
### Neutral marks
|
|
1388
1753
|
|
|
@@ -1535,15 +1900,61 @@ inventory, and the built-in palettes and the generated stylesheet are built from
|
|
|
1535
1900
|
property names are private, so a color, a size or a radius is always set through a role. The
|
|
1536
1901
|
class names inside the stylesheet are private too, except the supported hooks below.
|
|
1537
1902
|
|
|
1903
|
+
### Motion
|
|
1904
|
+
|
|
1905
|
+
Every duration, timing function and motion scale the chart uses is a role in the `motion` family,
|
|
1906
|
+
so you retune the chart's motion the way you retune its colors. Both built-in palettes carry the
|
|
1907
|
+
same values:
|
|
1908
|
+
|
|
1909
|
+
| Role | Default | What it times |
|
|
1910
|
+
|---|---|---|
|
|
1911
|
+
| `motion.durationFast` | `90ms` | A state change under the pointer, such as a hover fill. |
|
|
1912
|
+
| `motion.durationBase` | `150ms` | A modal dialog and its backdrop opening and closing, and the replay row entering and leaving. |
|
|
1913
|
+
| `motion.durationModerate` | `250ms` | A control moving to its new state, such as a switch knob sliding. |
|
|
1914
|
+
| `motion.durationSlow` | `350ms` | A small mark settling, such as a disclosure caret turning or a checkbox filling. |
|
|
1915
|
+
| `motion.durationSlower` | `500ms` | A larger mark turning, such as the drawing rail's chevron. |
|
|
1916
|
+
| `motion.easingStandard` | `ease` | A modal dialog box, the replay row, a caret dip, a checkbox fill. |
|
|
1917
|
+
| `motion.easingOut` | `ease-out` | A modal backdrop fading, a switch knob sliding. |
|
|
1918
|
+
| `motion.easingLinear` | `linear` | A color or opacity change under the pointer. |
|
|
1919
|
+
| `motion.easingSpring` | `cubic-bezier(0.175, 0.885, 0.32, 1.275)` | A caret or chevron turning with a slight overshoot. |
|
|
1920
|
+
| `motion.scaleEnter` | `0.97` | The scale a modal dialog box grows from as it opens. |
|
|
1921
|
+
|
|
1922
|
+
A modal dialog, such as the symbol search or chart settings, opens with its backdrop fading in over
|
|
1923
|
+
`motion.durationBase` on `motion.easingOut`, and its box fading in and growing from
|
|
1924
|
+
`motion.scaleEnter` to full size over `motion.durationBase` on `motion.easingStandard`. It closes
|
|
1925
|
+
with the same motion reversed. A closing dialog stops taking input and returns focus at once, and the
|
|
1926
|
+
chart removes it when the same `motion.durationBase` has elapsed, so a duration you set times both
|
|
1927
|
+
the transition and the removal.
|
|
1928
|
+
|
|
1929
|
+
A duration takes a CSS duration such as `200ms` or `0.2s`; an easing takes any CSS timing function,
|
|
1930
|
+
such as `ease-in-out`, `steps(4)` or `cubic-bezier(0.2, 0, 0, 1)`; a scale takes a unitless factor
|
|
1931
|
+
such as `0.95`.
|
|
1932
|
+
|
|
1933
|
+
```ts
|
|
1934
|
+
import { createThemeController, type CustomThemes } from '@trdrs/quickcharts'
|
|
1935
|
+
|
|
1936
|
+
const calm: CustomThemes = {
|
|
1937
|
+
light: { 'motion.durationBase': '200ms', 'motion.scaleEnter': '0.95' },
|
|
1938
|
+
dark: { 'motion.durationBase': '200ms', 'motion.scaleEnter': '0.95' },
|
|
1939
|
+
}
|
|
1940
|
+
const theme = createThemeController({ mode: 'dark', custom: calm })
|
|
1941
|
+
note(theme.get()['motion.durationBase'])
|
|
1942
|
+
```
|
|
1943
|
+
|
|
1944
|
+
When the reader's system asks for reduced motion (`prefers-reduced-motion: reduce`), the
|
|
1945
|
+
stylesheet resolves every duration role to `0ms` and marks those declarations `!important`, so a
|
|
1946
|
+
palette you supply cannot bring motion back: dialogs, menus and the replay row open and close at
|
|
1947
|
+
once.
|
|
1948
|
+
|
|
1538
1949
|
### Cascade layers
|
|
1539
1950
|
|
|
1540
1951
|
The stylesheet declares two cascade layers and puts everything it contains in them: the built-in
|
|
1541
1952
|
palettes in `trdrs.tokens`, every recipe in `trdrs.chart`. Nothing in it is unlayered. It marks a
|
|
1542
|
-
declaration `!important` only where a later layer must not undo it: an
|
|
1543
|
-
attribute, the
|
|
1544
|
-
search field, which shows its focus by its caret. Your first stylesheet must
|
|
1545
|
-
order before any product stylesheet loads, because a layer's position is fixed
|
|
1546
|
-
statement that names it:
|
|
1953
|
+
declaration `!important` only where a later layer or an inline palette must not undo it: an
|
|
1954
|
+
element's `hidden` attribute, the zero motion durations under a reduced-motion preference, and the
|
|
1955
|
+
missing focus ring of a search field, which shows its focus by its caret. Your first stylesheet must
|
|
1956
|
+
declare the complete order before any product stylesheet loads, because a layer's position is fixed
|
|
1957
|
+
by the first statement that names it:
|
|
1547
1958
|
|
|
1548
1959
|
```css
|
|
1549
1960
|
@layer reset, trdrs.tokens, trdrs.chart, trdrs.platform, host;
|
|
@@ -1838,6 +2249,87 @@ isSymbolPair('BTC/USD') // true
|
|
|
1838
2249
|
spreadExpression(' es - nq ') // 'ES-NQ'
|
|
1839
2250
|
```
|
|
1840
2251
|
|
|
2252
|
+
### Classes and spread operators
|
|
2253
|
+
|
|
2254
|
+
The dialog offers your datafeed's `config().classes` as a strip of chips, an All chip first, one
|
|
2255
|
+
class selected at a time and sent to your feed as `cls`. Its field offers six spread operators
|
|
2256
|
+
behind a toggle, and a query that reads as an expression leads the list with the expression row.
|
|
2257
|
+
Three `search` options change that, and a chart that sets none of them searches exactly as above.
|
|
2258
|
+
|
|
2259
|
+
- `spreads`: `false` offers no operator toggle, no operator and no expression row, and your feed
|
|
2260
|
+
receives the query exactly as typed. `{ operators }` keeps spreads on and offers only the listed
|
|
2261
|
+
operators (`SpreadOperatorId`: `division`, `subtraction`, `addition`, `multiplication`,
|
|
2262
|
+
`exponentiation`, `reciprocal`), in the listed order. Default `true`. The compare dialog never
|
|
2263
|
+
offers the operators.
|
|
2264
|
+
- `allClasses`: `false` offers no All chip. With one class at a time, the first class you declare
|
|
2265
|
+
starts selected; with several, no selection means every class. `{ label }` writes the All chip
|
|
2266
|
+
with your label. Default: the chip, with the catalog's label.
|
|
2267
|
+
- `classSelection`: `'multiple'` makes every chip a toggle, and the All chip clears the selection.
|
|
2268
|
+
Your feed receives `classes` with every selected class in declared order, and `cls` too while
|
|
2269
|
+
exactly one is selected, so a feed that reads only `cls` still narrows a single chip. Default
|
|
2270
|
+
`'single'`.
|
|
2271
|
+
|
|
2272
|
+
A `classes` entry is a class token, or a `SearchClassNode`, `{ id, children }`, whose children are
|
|
2273
|
+
narrower classes. Selecting such a class opens a second row of chips beneath the strip: its own
|
|
2274
|
+
all chip, written like the All chip, and one chip per child, named through `search.classNames`.
|
|
2275
|
+
Your feed always receives the most specific class selected: the child when one is picked, the
|
|
2276
|
+
class when none is. The dialog renders two levels; a child's own `children` are not offered, and
|
|
2277
|
+
the child narrows by its `id`. The second row is a labelled group of pressed buttons, reached by
|
|
2278
|
+
Tab after the strip, as the strip is.
|
|
2279
|
+
|
|
2280
|
+
```ts
|
|
2281
|
+
import { createChart, type ChartDatafeed, type DatafeedConfig } from '@trdrs/quickcharts'
|
|
2282
|
+
|
|
2283
|
+
declare const cryptoBase: ChartDatafeed
|
|
2284
|
+
|
|
2285
|
+
// Spot markets by quote currency, beside perpetuals.
|
|
2286
|
+
const cryptoFeed: ChartDatafeed = {
|
|
2287
|
+
...cryptoBase,
|
|
2288
|
+
async config(): Promise<DatafeedConfig> {
|
|
2289
|
+
return { classes: [{ id: 'spot', children: ['usdc', 'usdt'] }, 'perp'] }
|
|
2290
|
+
},
|
|
2291
|
+
// `cls` is 'spot' while the whole class is selected, and 'usdc' once USDC is picked under it.
|
|
2292
|
+
search: (query, options) => cryptoBase.search(query, options),
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
const crypto = createChart({
|
|
2296
|
+
container,
|
|
2297
|
+
datafeed: cryptoFeed,
|
|
2298
|
+
search: {
|
|
2299
|
+
classNames: { spot: 'Spot', usdc: 'USDC', usdt: 'USDT', perp: 'Perpetuals' },
|
|
2300
|
+
allClasses: { label: 'Every market' },
|
|
2301
|
+
spreads: { operators: ['division', 'subtraction'] },
|
|
2302
|
+
},
|
|
2303
|
+
})
|
|
2304
|
+
void crypto
|
|
2305
|
+
```
|
|
2306
|
+
|
|
2307
|
+
With several classes at once, read `classes` and fall back to `cls`:
|
|
2308
|
+
|
|
2309
|
+
```ts
|
|
2310
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
2311
|
+
|
|
2312
|
+
declare const catalogFeed: ChartDatafeed
|
|
2313
|
+
|
|
2314
|
+
const multiFeed: ChartDatafeed = {
|
|
2315
|
+
...catalogFeed,
|
|
2316
|
+
async search(query, options = {}) {
|
|
2317
|
+
// ['future', 'option'] with two chips selected; absent with none, which is every class.
|
|
2318
|
+
const classes = options.classes ?? (options.cls ? [options.cls] : [])
|
|
2319
|
+
return myBackend.search(query, classes, options.limit, options.offset)
|
|
2320
|
+
},
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
const multi = createChart({
|
|
2324
|
+
container,
|
|
2325
|
+
datafeed: multiFeed,
|
|
2326
|
+
search: { classSelection: 'multiple', allClasses: false, spreads: false },
|
|
2327
|
+
})
|
|
2328
|
+
void multi
|
|
2329
|
+
```
|
|
2330
|
+
|
|
2331
|
+
`openSymbolSearch` and `mountSymbolSearch` take the same three options beside their `classes`.
|
|
2332
|
+
|
|
1841
2333
|
### The picker, away from a chart
|
|
1842
2334
|
|
|
1843
2335
|
A page that picks a market where no chart is mounted opens the same dialog the chart's symbol pill
|
|
@@ -1877,6 +2369,60 @@ card.dispose()
|
|
|
1877
2369
|
catalog.dispose()
|
|
1878
2370
|
```
|
|
1879
2371
|
|
|
2372
|
+
### The timeframe picker, away from a chart
|
|
2373
|
+
|
|
2374
|
+
A page that asks for an interval where no chart is mounted (a backtest's timeframe, an alert's
|
|
2375
|
+
interval) opens the list the chart's timeframe caret drops: the same unit groups, rows, labels and
|
|
2376
|
+
custom composer, built by the same code, so an interval reads there exactly as it reads on a chart.
|
|
2377
|
+
`openTimeframePicker` drops it from a control you own, which gets `aria-expanded` while it is open
|
|
2378
|
+
and focus back when it closes; `mountTimeframePicker` builds it bare into a box you own and
|
|
2379
|
+
position, as a card on a phone. Picking a row, or composing a custom timeframe, hands the token to
|
|
2380
|
+
`onPick`, because there is no chart here to set. The drop-down then closes; the card calls
|
|
2381
|
+
`onClose`, your cue to close its box.
|
|
2382
|
+
|
|
2383
|
+
It takes what you already give a chart. `timeframe` is the value it opens on, checked and focused,
|
|
2384
|
+
and a custom value sits in its unit's group. `timeframes` and `customTimeframes` offer exactly what
|
|
2385
|
+
they offer a chart (see Offered timeframes), with the same setup errors, thrown before anything
|
|
2386
|
+
mounts, plus one for a `timeframe` the grammar cannot read. `resolutions` and
|
|
2387
|
+
`supportedResolutions` say what your feed and the symbol serve: as in the chart's list, a row they
|
|
2388
|
+
do not serve is left out, and the composer does not offer one. `theme`, `locale` (or a prepared
|
|
2389
|
+
`i18n`) and `icons` paint it as they paint a chart, on a theme root of its own, so the stylesheet
|
|
2390
|
+
reaches it outside any chart in either mode.
|
|
2391
|
+
|
|
2392
|
+
It carries no saved chips and no stars, and keeps nothing: the chips are a chart's quick-select row,
|
|
2393
|
+
stored with the chart's preferences, and a field that asks for one interval has no row to put them
|
|
2394
|
+
in. A composed timeframe is handed back and not added to any list. The drop-down takes the keyboard
|
|
2395
|
+
as the chart's does (arrow keys, Home and End rove the rows; Escape and a press outside close
|
|
2396
|
+
it) and moves as every menu does.
|
|
2397
|
+
|
|
2398
|
+
```ts
|
|
2399
|
+
import { mountTimeframePicker, openTimeframePicker } from '@trdrs/quickcharts'
|
|
2400
|
+
|
|
2401
|
+
declare const intervalField: HTMLButtonElement
|
|
2402
|
+
declare const intervalBox: HTMLElement
|
|
2403
|
+
declare const backtest: { timeframe: string; setTimeframe(timeframe: string): void }
|
|
2404
|
+
|
|
2405
|
+
intervalField.addEventListener('click', () => {
|
|
2406
|
+
const picker = openTimeframePicker({
|
|
2407
|
+
anchor: intervalField,
|
|
2408
|
+
timeframe: backtest.timeframe,
|
|
2409
|
+
resolutions: ['1m', '5m', '15m', '1h', '4h', '1d'], // what the feed serves
|
|
2410
|
+
theme: { mode: 'light' },
|
|
2411
|
+
onPick: (timeframe) => backtest.setTimeframe(timeframe),
|
|
2412
|
+
})
|
|
2413
|
+
void picker // picker.close() closes it from outside; Escape and a press outside close it too
|
|
2414
|
+
})
|
|
2415
|
+
|
|
2416
|
+
const card = mountTimeframePicker({
|
|
2417
|
+
container: intervalBox,
|
|
2418
|
+
timeframes: ['5m', '15m', '1h', '4h', '1d'],
|
|
2419
|
+
timeframe: '1h',
|
|
2420
|
+
onPick: (timeframe) => backtest.setTimeframe(timeframe),
|
|
2421
|
+
onClose: () => intervalBox.remove(),
|
|
2422
|
+
})
|
|
2423
|
+
card.focus()
|
|
2424
|
+
```
|
|
2425
|
+
|
|
1880
2426
|
### The legend
|
|
1881
2427
|
|
|
1882
2428
|
Every chart carries its own legend over its plot: an identity row naming the market, the interval
|
|
@@ -2028,10 +2574,12 @@ with its weak and strong strengths; stay in drawing mode; lock all; the eye that
|
|
|
2028
2574
|
indicators, or both; drawing sync, shown only in a layout of more than one chart; the remove menu,
|
|
2029
2575
|
which names what each row takes and carries the locked-item policy; and the favorites star. Each
|
|
2030
2576
|
group button wears the tool it last armed, and every action is a `chart.drawings.*` command
|
|
2031
|
-
through the registry: a control renders disabled
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2577
|
+
through the registry: a control renders disabled while the registry would not run its command (a
|
|
2578
|
+
tool or verb your access policy refuses, a selection verb with nothing selected, the Image tool
|
|
2579
|
+
without an asset port), and with `access.refused: 'hide'` a tool or verb the policy refuses is left
|
|
2580
|
+
out instead (see Refused controls), and a command the policy refuses answers `denied` from the
|
|
2581
|
+
toolbar as from anywhere else, including `chart.drawings.arm` for a refused tool. A tool your
|
|
2582
|
+
`drawingTools` list leaves out is not on the toolbar at all (see Offered drawing tools). An image is
|
|
2035
2583
|
placed through `chart.drawings.placeImage`, from the picker or a system-clipboard paste over the
|
|
2036
2584
|
chart. Every flyout, palette and dialog a surface opens sits inside the chart root and closes with
|
|
2037
2585
|
it, and the eye and lock all announce their state through a live region. Lock all also makes Paste
|
|
@@ -2085,6 +2633,11 @@ layer.armTool('rectangle')
|
|
|
2085
2633
|
layer.destroy()
|
|
2086
2634
|
```
|
|
2087
2635
|
|
|
2636
|
+
A layer you mount yourself arms whatever you arm. Its `copies` option answers whether a new drawing
|
|
2637
|
+
may be made as a copy of a drawing of a type: return false for a tool you do not offer, and clone,
|
|
2638
|
+
paste and a Control- or Command-drag duplicate make nothing for its drawings, `canPaste` answers
|
|
2639
|
+
false, and the drag moves the drawing itself.
|
|
2640
|
+
|
|
2088
2641
|
### `@trdrs/quickcharts/drawings`
|
|
2089
2642
|
|
|
2090
2643
|
Everything about drawings a host builds its own UI from is one subpath. A host that never draws
|