@stocksharp/diagram 0.5.0 → 0.6.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/README.md CHANGED
@@ -154,7 +154,9 @@ missing type. Sites can localize that message through
154
154
  `data-diagram-missing-element="Missing: {typeId}"` on the host.
155
155
 
156
156
  Every text the embedded control shows comes from the host, so a translated page
157
- carries no English leftovers:
157
+ carries no English leftovers. These attributes are per-instance overrides on top
158
+ of the page's translation bundle (see *Translating the control* below); set them
159
+ when one diagram has to read differently from the rest of the page:
158
160
 
159
161
  | Host attribute | What it renames |
160
162
  |---|---|
@@ -163,7 +165,9 @@ carries no English leftovers:
163
165
  | `data-diagram-missing-element="Missing: {typeId}"` | the placeholder for an element the palette lacks |
164
166
 
165
167
  The same labels are available to custom integrations as the `fullscreenLabels`
166
- option and `setFullscreenLabels()` on `StockSharpDiagram`.
168
+ option and `setFullscreenLabels()` on `StockSharpDiagram`. With neither the
169
+ attribute nor the option, the wording comes from the bundle, so a page that has
170
+ already been translated needs no per-diagram markup at all.
167
171
 
168
172
  ### Node actions and errors
169
173
 
@@ -190,10 +194,20 @@ the menu was opened over a socket.
190
194
 
191
195
  ### The context menu
192
196
 
193
- The control has no menu widget: right-click suppresses the browser's own menu
194
- and emits `contextMenuRequested` with page coordinates, whatever was hit, and
195
- the menu contents already resolved. Draw it however the host draws menus, then
196
- send the choice back through `executeContextCommand()`.
197
+ Right-click opens the control's own menu, with an Export submenu. It is on by
198
+ default, because right-click suppresses the browser's menu either way and a
199
+ control that takes one away owes one back. It needs no stylesheet: it is styled
200
+ inline over `--ssdiagram-*` custom properties (`--ssdiagram-menu-background`,
201
+ `--ssdiagram-menu-border`, `--ssdiagram-menu-color`, `--ssdiagram-menu-hover`,
202
+ `--ssdiagram-menu-disabled-color`, `--ssdiagram-menu-font`), falling back to the
203
+ `--ssdiagram-control-*` values the fullscreen button uses. Its class names —
204
+ `ssdiagram-context-menu`, `-item`, `-label`, `-arrow`, `-submenu`, `-separator` —
205
+ are stable if you would rather use CSS.
206
+
207
+ To draw your own instead, pass `showContextMenu: false` (or call
208
+ `setContextMenuEnabled(false)`) and handle `contextMenuRequested`, which is
209
+ emitted either way — and emitted *before* the built-in menu opens, so a handler
210
+ can still switch it off.
197
211
 
198
212
  `commands` is a tree in display order. An entry with a `group` is a submenu;
199
213
  anything else is a command. `enabled` is computed for both — a submenu is off
@@ -209,8 +223,10 @@ diagram.on('contextMenuRequested', ({ x, y, commands }) => {
209
223
  menu.on('pick', (command) => diagram.executeContextCommand(command));
210
224
  ```
211
225
 
212
- `undo`, `redo`, `cut`, `copy`, `paste` and `delete` are carried out by the
213
- control itself. The rest are requests it cannot answer alone: `open`,
226
+ `undo`, `redo`, `cut`, `copy`, `paste`, `delete` and `overview` are carried out
227
+ by the control itself. `overview` toggles the corner minimap, and reports its
228
+ current state as `checked` on the command so a menu can tick it rather than offer
229
+ a dead show/hide pair. The rest are requests it cannot answer alone: `open`,
214
230
  `properties` and `help` raise `nodeOpen` / `nodeProperties` / `nodeHelp`, and
215
231
  the `export` submenu raises `exportRequested` with the chosen format. Nothing
216
232
  is produced until the host acts on it — only the host knows where the result
@@ -226,9 +242,54 @@ diagram.on('exportRequested', ({ format }) => {
226
242
  ```
227
243
 
228
244
  Export is offered whenever the diagram has a node, including in read-only mode,
229
- because it only reads. Menu labels come from the host's i18n bundle
230
- (`ctxExportAs`, `ctxExportDocument`, `ctxExportPng`, `ctxExportSvg`, and the
231
- keys named after the other commands).
245
+ because it only reads.
246
+
247
+ #### Translating the control
248
+
249
+ There is one mechanism, and it is `window.__designerI18n`: an object typed by the
250
+ exported `DesignerI18n`, holding every string the package renders itself — the
251
+ menu, the fullscreen button's tooltip, and the embed failure notes. It is read on
252
+ every lookup, so it can be assigned at any point, before or after the bundle
253
+ loads, and reassigned later to change language. A key that is missing or empty
254
+ falls back to English.
255
+
256
+ Per-instance settings — the `fullscreenLabels` option, `setFullscreenLabels()`,
257
+ and the `data-diagram-*` attributes above — override the bundle for one diagram.
258
+ Resolution is: explicit setting, then bundle, then the built-in English.
259
+
260
+ The context menu is rebuilt on every open and needs nothing after a language
261
+ change. The fullscreen button is drawn once, so call `refreshLabels()` to
262
+ re-read the bundle for it.
263
+
264
+ ```ts
265
+ import type { DesignerI18n } from '@stocksharp/diagram';
266
+
267
+ const labels: DesignerI18n = { cut: 'Вырезать', ctxDelete: 'Удалить', /* … */ };
268
+ (window as unknown as { __designerI18n: DesignerI18n }).__designerI18n = labels;
269
+ ```
270
+
271
+ The key is not always the command name, so here is the whole menu:
272
+
273
+ | command | key | English |
274
+ |---|---|---|
275
+ | `undo` / `redo` | `undo` / `redo` | Undo / Redo |
276
+ | `cut` / `copy` / `paste` | `cut` / `copy` / `paste` | Cut / Copy / Paste |
277
+ | `open` | `ctxOpen` | Open |
278
+ | `delete` | `ctxDelete` | Delete |
279
+ | *(the submenu)* | `ctxExportAs` | Export as |
280
+ | `exportDocument` | `ctxExportDocument` | Scheme |
281
+ | `exportPng` / `exportSvg` | `ctxExportPng` / `ctxExportSvg` | PNG image / SVG image |
282
+ | `overview` | `ctxOverview` | Overview |
283
+ | `properties` | `properties` | Properties |
284
+ | `help` | `ctxHelp` | Help |
285
+ | *(fullscreen button)* | `fullscreenEnter` / `fullscreenExit` | Enter / Exit fullscreen |
286
+ | *(embed failures)* | `embedErrorLoad` / `embedErrorEmpty` / `embedErrorDraw` | the three notes shown in place of a diagram |
287
+ | *(embed placeholder)* | `embedMissingElement` | the missing-element tooltip |
288
+
289
+ `ctxDelete` is separate from the solution explorer's `delete` on purpose: that
290
+ one removes a whole strategy, this one the selected nodes and links, and a
291
+ language that declines the object cannot serve both from one key. The same goes
292
+ for `ctxExportAs` against `ctxExport`.
232
293
 
233
294
  Runtime failures flash the node border before leaving it red. Errors found
234
295
  while loading a scheme use a red background. Hovering either state shows the
@@ -1876,6 +1876,26 @@ export class Diagram {
1876
1876
  return added.map((node) => node.id);
1877
1877
  }
1878
1878
  // ---- input ------------------------------------------------------
1879
+ // Ends hover and everything hanging off it: highlight, cursor and the delayed tooltip.
1880
+ clearHover() {
1881
+ if (this.hoverPort !== null)
1882
+ this.emit('portHover', { node: this.hoverPort.node, port: this.hoverPort.port, hovering: false });
1883
+ if (this.hoverNode !== null)
1884
+ this.emit('nodeHover', { node: this.hoverNode, hovering: false });
1885
+ if (this.hoveredLink !== null)
1886
+ this.emit('linkHover', { link: this.hoveredLink, hovering: false });
1887
+ this.hoverPort = null;
1888
+ this.hoverNode = null;
1889
+ this.hoveredLink = null;
1890
+ this.tipTarget = null;
1891
+ this.tipShow = false;
1892
+ if (this.tipTimer !== null) {
1893
+ clearTimeout(this.tipTimer);
1894
+ this.tipTimer = null;
1895
+ }
1896
+ this.canvas.style.cursor = 'default';
1897
+ this.scheduleDraw();
1898
+ }
1879
1899
  cancelLongPress() {
1880
1900
  if (this.lpTimer !== null) {
1881
1901
  clearTimeout(this.lpTimer);
@@ -1914,7 +1934,10 @@ export class Diagram {
1914
1934
  this.relinking = null;
1915
1935
  this.relinkCandidate = null;
1916
1936
  this.linkSnap = null;
1917
- this.scheduleDraw();
1937
+ // A menu is about to cover this spot, and it opens under a cursor that has not moved --
1938
+ // which fires no boundary event, so nothing else would end the hover. Without this the
1939
+ // tooltip armed by the last pointermove appears 400ms later, through the open menu.
1940
+ this.clearHover();
1918
1941
  this.emit('contextMenu', { x: pageX, y: pageY, link, node, port });
1919
1942
  }
1920
1943
  listen(target, type, listener, options) {
@@ -2292,25 +2315,7 @@ export class Diagram {
2292
2315
  this.emitViewChanged(true);
2293
2316
  this.scheduleDraw();
2294
2317
  }, { passive: false });
2295
- this.listen(this.canvas, 'pointerleave', () => {
2296
- if (this.hoverPort !== null)
2297
- this.emit('portHover', { node: this.hoverPort.node, port: this.hoverPort.port, hovering: false });
2298
- if (this.hoverNode !== null)
2299
- this.emit('nodeHover', { node: this.hoverNode, hovering: false });
2300
- if (this.hoveredLink !== null)
2301
- this.emit('linkHover', { link: this.hoveredLink, hovering: false });
2302
- this.hoverPort = null;
2303
- this.hoverNode = null;
2304
- this.hoveredLink = null;
2305
- this.tipTarget = null;
2306
- this.tipShow = false;
2307
- if (this.tipTimer !== null) {
2308
- clearTimeout(this.tipTimer);
2309
- this.tipTimer = null;
2310
- }
2311
- this.canvas.style.cursor = 'default';
2312
- this.scheduleDraw();
2313
- });
2318
+ this.listen(this.canvas, 'pointerleave', () => this.clearHover());
2314
2319
  this.listen(this.canvas, 'dblclick', (e) => {
2315
2320
  const [sx, sy] = localXY(e);
2316
2321
  const [wx, wy] = this.toWorld(sx, sy);