@luxalgo/vela 0.8.1 → 0.8.2

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.
Files changed (48) hide show
  1. package/README.md +1 -1
  2. package/dist/{DataProvider-BrlxntPN.d.cts → DataProvider-COGDs9Xg.d.cts} +1 -1
  3. package/dist/{DataProvider-DwFIoV4r.d.ts → DataProvider-DpOWZD8F.d.ts} +1 -1
  4. package/dist/{chunk-LMBYEGUR.js → chunk-27UTYE7T.js} +25 -2
  5. package/dist/{chunk-EZ5FWVLA.js → chunk-DPAA3SRT.js} +94 -13
  6. package/dist/{chunk-BZQM2XO7.js → chunk-HKNO24GD.js} +13 -1
  7. package/dist/{chunk-NELQJCGK.js → chunk-NLDTVFOI.js} +41 -3
  8. package/dist/{chunk-DBDXKZUF.js → chunk-R2JMNIHY.js} +53 -13
  9. package/dist/{chunk-ATGTCWHJ.js → chunk-T3DZIR5Y.js} +171 -32
  10. package/dist/{contributions-tjNExo1o.d.ts → contributions-Cgm9RJYP.d.cts} +38 -11
  11. package/dist/{contributions-CtlptxR8.d.cts → contributions-bWgwyiVi.d.ts} +38 -11
  12. package/dist/index.cjs +239 -37
  13. package/dist/index.d.cts +8 -8
  14. package/dist/index.d.ts +8 -8
  15. package/dist/index.js +4 -4
  16. package/dist/{options-BnEPUuVV.d.ts → options-NAUHiQEC.d.cts} +3 -3
  17. package/dist/{options-BnEPUuVV.d.cts → options-NAUHiQEC.d.ts} +3 -3
  18. package/dist/{plugin-BWxJRKzS.d.cts → plugin-CjjzZX2F.d.cts} +4 -4
  19. package/dist/{plugin-e2WUEtiI.d.ts → plugin-CqxWoDKS.d.ts} +4 -4
  20. package/dist/plugin.cjs +20 -6
  21. package/dist/plugin.d.cts +5 -5
  22. package/dist/plugin.d.ts +5 -5
  23. package/dist/plugin.js +2 -2
  24. package/dist/providers/binance.d.cts +2 -2
  25. package/dist/providers/binance.d.ts +2 -2
  26. package/dist/providers/coinbase.d.cts +2 -2
  27. package/dist/providers/coinbase.d.ts +2 -2
  28. package/dist/providers/hyperliquid.d.cts +2 -2
  29. package/dist/providers/hyperliquid.d.ts +2 -2
  30. package/dist/{side-panel-HF0IAzwf.d.cts → side-panel-wMNfgyZb.d.cts} +13 -0
  31. package/dist/{side-panel-HF0IAzwf.d.ts → side-panel-wMNfgyZb.d.ts} +13 -0
  32. package/dist/{statusline-model-zKXtGIjj.d.ts → statusline-model-Dih6FGWP.d.ts} +3 -3
  33. package/dist/{statusline-model-DFY7tuHj.d.cts → statusline-model-Dpfpq54A.d.cts} +3 -3
  34. package/dist/ui.cjs +77 -2
  35. package/dist/ui.d.cts +28 -2
  36. package/dist/ui.d.ts +28 -2
  37. package/dist/ui.js +3 -3
  38. package/dist/vela.global.js +239 -37
  39. package/dist/vela.global.min.js +52 -52
  40. package/dist/widget.cjs +383 -50
  41. package/dist/widget.d.cts +20 -10
  42. package/dist/widget.d.ts +20 -10
  43. package/dist/widget.js +7 -7
  44. package/dist/workspace.cjs +383 -50
  45. package/dist/workspace.d.cts +5 -5
  46. package/dist/workspace.d.ts +5 -5
  47. package/dist/workspace.js +6 -6
  48. package/package.json +2 -2
@@ -214,7 +214,7 @@ var Vela = (function (exports) {
214
214
  this.controller.applyProps(this.id, values);
215
215
  }
216
216
  updateCode(source) {
217
- this.controller.updateCode(this.id, source);
217
+ return this.controller.updateCode(this.id, source);
218
218
  }
219
219
  setVisible(visible) {
220
220
  this.controller.setVisible(this.id, visible);
@@ -617,6 +617,7 @@ var Vela = (function (exports) {
617
617
  registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
618
618
  registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
619
619
  registerIcon("maximize", S('<path d="M2.5 6V3a.5.5 0 0 1 .5-.5h3M10 2.5h3a.5.5 0 0 1 .5.5v3M13.5 10v3a.5.5 0 0 1-.5.5h-3M6 13.5H3a.5.5 0 0 1-.5-.5v-3"/>'));
620
+ registerIcon("restore", S('<path d="M6 2.5v3a.5.5 0 0 1-.5.5h-3M13.5 6h-3a.5.5 0 0 1-.5-.5v-3M10 13.5v-3a.5.5 0 0 1 .5-.5h3M2.5 10h3a.5.5 0 0 1 .5.5v3"/>'));
620
621
  registerIcon("restore", S('<path d="M6.2 2.5v3.7H2.5M9.8 13.5V9.8h3.7M13.5 6.2H9.8V2.5M2.5 9.8h3.7v3.7"/>'));
621
622
  registerIcon("star", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>'));
622
623
  registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>', 'fill="currentColor"'));
@@ -3275,6 +3276,10 @@ var Vela = (function (exports) {
3275
3276
 
3276
3277
  // src/core/drawings/types/FibLevels.ts
3277
3278
  var FibLevels = class extends FibRatios {
3279
+ /** Price of a level for the two anchor prices; subclasses override to change which anchor ratio 0 sits on. */
3280
+ levelPrice(ratio, p1, p2) {
3281
+ return p1 + ratio * (p2 - p1);
3282
+ }
3278
3283
  /** Per-level pixel line + price for the ENABLED levels, spanning the anchors' time range. */
3279
3284
  levelLines(proj) {
3280
3285
  const a = this.anchors[0];
@@ -3284,11 +3289,10 @@ var Vela = (function (exports) {
3284
3289
  const xb = proj.xOf(b.time);
3285
3290
  const x1 = Math.min(xa, xb);
3286
3291
  const x2 = Math.max(xa, xb);
3287
- const delta = b.price - a.price;
3288
3292
  const out = [];
3289
3293
  for (const lv of this.levels) {
3290
3294
  if (!lv.enabled) continue;
3291
- const price = a.price + lv.ratio * delta;
3295
+ const price = this.levelPrice(lv.ratio, a.price, b.price);
3292
3296
  const y = proj.yOf(price, this.paneId);
3293
3297
  if (y == null) continue;
3294
3298
  out.push({ ratio: lv.ratio, color: lv.color, label: lv.label, price, x1, x2, y });
@@ -3328,8 +3332,7 @@ var Vela = (function (exports) {
3328
3332
  const a = this.anchors[0];
3329
3333
  const b = this.anchors[1];
3330
3334
  if (!a || !b) return null;
3331
- const delta = b.price - a.price;
3332
- const prices = this.levels.filter((l) => l.enabled).map((l) => a.price + l.ratio * delta);
3335
+ const prices = this.levels.filter((l) => l.enabled).map((l) => this.levelPrice(l.ratio, a.price, b.price));
3333
3336
  if (prices.length === 0) return null;
3334
3337
  return { min: Math.min(...prices), max: Math.max(...prices) };
3335
3338
  }
@@ -3415,13 +3418,24 @@ var Vela = (function (exports) {
3415
3418
  // src/core/drawings/types/FibRetracement.ts
3416
3419
  var LEVELS = fibLevels([0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]);
3417
3420
  var FibRetracement = class extends FibLevels {
3418
- constructor() {
3419
- super(...arguments);
3421
+ constructor(init) {
3422
+ super(init);
3420
3423
  this.type = "fibretracement";
3424
+ if (this.reverse === void 0) this.reverse = false;
3421
3425
  }
3422
3426
  defaultLevels() {
3423
3427
  return LEVELS;
3424
3428
  }
3429
+ levelPrice(ratio, p1, p2) {
3430
+ return this.reverse ? p1 + ratio * (p2 - p1) : p2 + ratio * (p1 - p2);
3431
+ }
3432
+ writeProps() {
3433
+ return { ...super.writeProps(), reverse: this.reverse };
3434
+ }
3435
+ readProps(props) {
3436
+ super.readProps(props);
3437
+ this.reverse = typeof props.reverse === "boolean" ? props.reverse : true;
3438
+ }
3425
3439
  };
3426
3440
 
3427
3441
  // src/core/drawings/types/FibExtension.ts
@@ -9234,20 +9248,26 @@ var Vela = (function (exports) {
9234
9248
  /**
9235
9249
  * IndicatorController: replace a SCRIPT indicator's source in place (see
9236
9250
  * {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
9237
- * computing until the new source has compiled, so a broken edit costs the user
9238
- * nothing but an `error` event. Natives have no script — warn and leave them alone.
9251
+ * computing until the new source has compiled; then the new code runs on TRIAL until
9252
+ * its first model lands, and a failure before that puts the previous code back.
9253
+ * Natives have no script — warn and leave them alone.
9239
9254
  */
9240
9255
  updateCode(id, source) {
9241
9256
  const record = this.registry.get(id);
9242
9257
  const handle = this.handles.get(id);
9243
- if (!record || !handle) return;
9258
+ if (!record || !handle) return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator is no longer on the chart.`) });
9244
9259
  if (record.native) {
9245
9260
  console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
9246
- return;
9261
+ return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`) });
9247
9262
  }
9248
- if (source === record.source && record.pendingSource === void 0) return;
9249
- record.pendingSource = source;
9250
- void this.swapSource(id, source, handle);
9263
+ const busy = record.pendingSource !== void 0 || record.codeTrial !== void 0;
9264
+ if (!busy && source === record.source) return Promise.resolve({ ok: true, error: null });
9265
+ return new Promise((resolve) => {
9266
+ (record.codeWaiters ?? (record.codeWaiters = [])).push(resolve);
9267
+ if (record.pendingSource === source || record.pendingSource === void 0 && record.codeTrial && record.source === source) return;
9268
+ record.pendingSource = source;
9269
+ void this.swapSource(id, source, handle);
9270
+ });
9251
9271
  }
9252
9272
  async swapSource(id, source, handle) {
9253
9273
  const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
@@ -9258,26 +9278,41 @@ var Vela = (function (exports) {
9258
9278
  const record2 = this.registry.get(id);
9259
9279
  if (record2?.pendingSource === source) {
9260
9280
  record2.pendingSource = void 0;
9261
- this.fail(id, handle, err);
9281
+ this.rejectCode(id, record2, handle, err);
9262
9282
  }
9263
9283
  return;
9264
9284
  }
9265
9285
  const record = this.registry.get(id);
9266
9286
  if (!record || record.pendingSource !== source) return;
9267
9287
  record.pendingSource = void 0;
9288
+ const previous = record.codeTrial?.previous ?? {
9289
+ source: record.source,
9290
+ engine: record.engine,
9291
+ prepared: record.prepared,
9292
+ inputValues: record.inputValues,
9293
+ propValues: record.propValues,
9294
+ inputs: [...handle.inputs],
9295
+ props: [...handle.props]
9296
+ };
9297
+ record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues, record.prepared?.inputs);
9298
+ record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues, record.prepared?.props);
9268
9299
  record.source = source;
9269
9300
  record.engine = engine;
9270
9301
  record.prepared = prepared;
9271
9302
  handle.setSource(source);
9272
- record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
9273
- record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
9274
9303
  handle.setSchema(prepared.inputs);
9275
9304
  handle.setPropsSchema(prepared.props ?? []);
9276
9305
  record.session?.stop();
9277
9306
  record.session = void 0;
9278
9307
  record.pendingStructural = true;
9279
9308
  record.pendingCause = "code";
9280
- if (record.hidden) return;
9309
+ if (record.hidden) {
9310
+ record.codeTrial = void 0;
9311
+ if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
9312
+ this.settleCode(record, { ok: true, error: null });
9313
+ return;
9314
+ }
9315
+ record.codeTrial = { previous };
9281
9316
  if (record.renderHandle) {
9282
9317
  this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
9283
9318
  this.setLoading(record, true);
@@ -9285,6 +9320,46 @@ var Vela = (function (exports) {
9285
9320
  this.mountLoadingPlaceholder(id, record);
9286
9321
  }
9287
9322
  this.executeIndicator(id, handle);
9323
+ if (this.bars.length === 0 && record.codeTrial) {
9324
+ record.codeTrial = void 0;
9325
+ this.settleCode(record, { ok: true, error: null });
9326
+ }
9327
+ }
9328
+ /** Resolve every `updateCode` caller waiting on this record's chain of edits. */
9329
+ settleCode(record, result) {
9330
+ const waiters = record.codeWaiters;
9331
+ record.codeWaiters = void 0;
9332
+ if (waiters) for (const resolve of waiters) resolve(result);
9333
+ }
9334
+ /**
9335
+ * An update failed — at prepare, or on trial before its first model. A trial puts the
9336
+ * previous code back (source, engine, prepared script, values, schema) and restarts it;
9337
+ * its visuals were never dropped, so the chart keeps painting throughout. Then the
9338
+ * failure reports the usual way and every waiting caller learns it.
9339
+ */
9340
+ rejectCode(id, record, handle, err) {
9341
+ const trial = record.codeTrial;
9342
+ record.codeTrial = void 0;
9343
+ this.fail(id, handle, err);
9344
+ if (trial) {
9345
+ const p = trial.previous;
9346
+ record.session?.stop();
9347
+ record.session = void 0;
9348
+ record.source = p.source;
9349
+ record.engine = p.engine;
9350
+ record.prepared = p.prepared;
9351
+ record.inputValues = p.inputValues;
9352
+ record.propValues = p.propValues;
9353
+ handle.setSource(p.source);
9354
+ handle.setSchema(p.inputs);
9355
+ handle.setPropsSchema(p.props);
9356
+ record.pendingStructural = true;
9357
+ record.pendingCause = void 0;
9358
+ if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
9359
+ if (!record.hidden) this.executeIndicator(id, handle);
9360
+ }
9361
+ const error = err instanceof Error ? err : new Error(String(err));
9362
+ this.settleCode(record, { ok: false, error });
9288
9363
  }
9289
9364
  /** IndicatorController: tear down an indicator and (if now empty) its pane. */
9290
9365
  /** Live handles of every indicator on the chart (script + native), insertion order. */
@@ -9299,6 +9374,7 @@ var Vela = (function (exports) {
9299
9374
  removeIndicator(id) {
9300
9375
  const record = this.registry.remove(id);
9301
9376
  if (record?.native?.type === "volume") this.volumeOptedOut = true;
9377
+ if (record) this.settleCode(record, { ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator was removed.`) });
9302
9378
  record?.session?.stop();
9303
9379
  if (record?.native?.started) record.native.instance.stop();
9304
9380
  this.handles.delete(id);
@@ -9326,6 +9402,10 @@ var Vela = (function (exports) {
9326
9402
  if (!visible) {
9327
9403
  record.session?.stop();
9328
9404
  record.session = void 0;
9405
+ if (record.codeTrial) {
9406
+ record.codeTrial = void 0;
9407
+ this.settleCode(record, { ok: true, error: null });
9408
+ }
9329
9409
  if (record.native?.started) record.native.instance.suspend();
9330
9410
  if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
9331
9411
  else if (record.native) this.mountHiddenNativeRow(id, record);
@@ -9413,6 +9493,7 @@ var Vela = (function (exports) {
9413
9493
  this.viewportUnsub?.();
9414
9494
  this.paneActionUnsub?.();
9415
9495
  for (const record of this.registry.all()) {
9496
+ this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
9416
9497
  record.session?.stop();
9417
9498
  if (record.native?.started) record.native.instance.stop();
9418
9499
  }
@@ -9481,7 +9562,8 @@ var Vela = (function (exports) {
9481
9562
  const record = this.registry.get(id);
9482
9563
  if (!record || !record.engine || !record.prepared || record.hidden) return;
9483
9564
  const mode = this.config.live && !record.prepared.reactsToViewport && record.engine.capabilities.streaming ? "live" : "static";
9484
- record.session = record.engine.execute(
9565
+ const prepared = record.prepared;
9566
+ const session = record.engine.execute(
9485
9567
  {
9486
9568
  prepared: record.prepared,
9487
9569
  market: this.market(),
@@ -9502,6 +9584,10 @@ var Vela = (function (exports) {
9502
9584
  const cause = record.pendingCause ?? "history";
9503
9585
  if (!this.applyModel(id, model)) return;
9504
9586
  record.pendingCause = void 0;
9587
+ if (record.codeTrial) {
9588
+ record.codeTrial = void 0;
9589
+ this.settleCode(record, { ok: true, error: null });
9590
+ }
9505
9591
  this.emitContextChanged(id);
9506
9592
  this.emitScriptRun(id, cause, first2);
9507
9593
  },
@@ -9512,9 +9598,17 @@ var Vela = (function (exports) {
9512
9598
  handle.emit("alert", { id: a.id, message: a.message, title: a.title, time: a.time });
9513
9599
  },
9514
9600
  onWarning: (w) => this.events.emit("warning", w),
9515
- onError: (err) => this.fail(id, handle, err)
9601
+ onError: (err) => {
9602
+ if (record.codeTrial) this.rejectCode(id, record, handle, err);
9603
+ else this.fail(id, handle, err);
9604
+ }
9516
9605
  }
9517
9606
  );
9607
+ if (record.prepared !== prepared) {
9608
+ session.stop();
9609
+ return;
9610
+ }
9611
+ record.session = session;
9518
9612
  this.emitContextChanged(id);
9519
9613
  }
9520
9614
  /**
@@ -10015,7 +10109,7 @@ var Vela = (function (exports) {
10015
10109
  }
10016
10110
  return new Promise((resolve) => setTimeout(resolve, 0));
10017
10111
  }
10018
- function valuesOnSchema(schema, previous) {
10112
+ function valuesOnSchema(schema, previous, oldSchema = []) {
10019
10113
  const out = {};
10020
10114
  const declared = /* @__PURE__ */ new Set();
10021
10115
  for (const s of schema) {
@@ -10023,9 +10117,22 @@ var Vela = (function (exports) {
10023
10117
  declared.add(s.key);
10024
10118
  declared.add(s.title);
10025
10119
  }
10026
- for (const [k, v] of Object.entries(previous)) if (declared.has(k)) out[k] = v;
10120
+ const oldDefaults = /* @__PURE__ */ new Map();
10121
+ for (const s of oldSchema) {
10122
+ oldDefaults.set(s.key, s.defval);
10123
+ if (!oldDefaults.has(s.title)) oldDefaults.set(s.title, s.defval);
10124
+ }
10125
+ for (const [k, v] of Object.entries(previous)) {
10126
+ if (!declared.has(k)) continue;
10127
+ if (oldDefaults.has(k) && sameInputValue(oldDefaults.get(k), v)) continue;
10128
+ out[k] = v;
10129
+ }
10027
10130
  return out;
10028
10131
  }
10132
+ function sameInputValue(a, b) {
10133
+ if (a === b) return true;
10134
+ return typeof a === "object" && a !== null && typeof b === "object" && b !== null && JSON.stringify(a) === JSON.stringify(b);
10135
+ }
10029
10136
  function blankedModel(model) {
10030
10137
  return {
10031
10138
  ...model,
@@ -13587,6 +13694,17 @@ ${STATIC_DECLS}
13587
13694
  return span;
13588
13695
  }
13589
13696
 
13697
+ // src/ui/surface-events.ts
13698
+ var SURFACE_OPEN_EVENT = "vela:surface-open";
13699
+ var SURFACE_CLOSE_EVENT = "vela:surface-close";
13700
+ function announceSurface(el, open2, kind, trigger = null) {
13701
+ const View = el.ownerDocument.defaultView;
13702
+ if (!View) return;
13703
+ el.dispatchEvent(
13704
+ new View.CustomEvent(open2 ? SURFACE_OPEN_EVENT : SURFACE_CLOSE_EVENT, { bubbles: true, composed: true, detail: { kind, trigger } })
13705
+ );
13706
+ }
13707
+
13590
13708
  // node_modules/@zag-js/anatomy/dist/create-anatomy.mjs
13591
13709
  var createAnatomy = (name, parts3 = []) => ({
13592
13710
  parts: (...values) => {
@@ -18065,6 +18183,8 @@ ${STATIC_DECLS}
18065
18183
  /** Trigger rect captured when this level opened — the list stays put if the
18066
18184
  * trigger then moves (favorite chips shifting the caret). */
18067
18185
  this.pinnedAnchor = null;
18186
+ /** The open state last announced — see {@link announce}. */
18187
+ this.shown = false;
18068
18188
  this.doc = doc;
18069
18189
  this.host = opts.host;
18070
18190
  this.onSelect = opts.onSelect;
@@ -18078,6 +18198,7 @@ ${STATIC_DECLS}
18078
18198
  this.positioner.appendChild(this.list);
18079
18199
  this.host.appendChild(this.positioner);
18080
18200
  const trigger = opts.trigger;
18201
+ this.opener = trigger ?? opts.opener ?? null;
18081
18202
  this.ctrl = menuController({
18082
18203
  items: [],
18083
18204
  id: opts.id,
@@ -18099,15 +18220,21 @@ ${STATIC_DECLS}
18099
18220
  if (opts.triggerId && trigger) trigger.id = opts.triggerId;
18100
18221
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
18101
18222
  const api = this.ctrl.connect(service);
18223
+ if (this.shown && !api.open) this.announce(false);
18102
18224
  if (trigger) spreadProps(trigger, api.getTriggerProps(), this.mid);
18103
18225
  spreadProps(this.positioner, api.getPositionerProps(), this.mid);
18104
18226
  spreadProps(this.list, api.getContentProps(), this.mid);
18105
18227
  this.project(api);
18228
+ if (api.open && !this.shown) this.announce(true);
18106
18229
  });
18107
18230
  }
18108
18231
  get api() {
18109
18232
  return this.ctrl.connect(this.handle.service);
18110
18233
  }
18234
+ announce(open2) {
18235
+ this.shown = open2;
18236
+ announceSurface(this.list, open2, "menu", this.opener);
18237
+ }
18111
18238
  setItems(items) {
18112
18239
  this.items = items;
18113
18240
  for (const sub2 of this.subs.values()) sub2.destroy();
@@ -18125,6 +18252,7 @@ ${STATIC_DECLS}
18125
18252
  for (const sub2 of this.subs.values()) sub2.destroy();
18126
18253
  this.subs.clear();
18127
18254
  this.handle.stop();
18255
+ if (this.shown) this.announce(false);
18128
18256
  this.positioner.remove();
18129
18257
  }
18130
18258
  byId(id) {
@@ -18218,6 +18346,7 @@ ${STATIC_DECLS}
18218
18346
  const sub2 = new _Surface(doc, {
18219
18347
  host: this.host,
18220
18348
  placement: "right-start",
18349
+ opener: li,
18221
18350
  onSelect: this.onSelect,
18222
18351
  onFavorite: this.onFavorite,
18223
18352
  checkmarks: this.checkmarks,
@@ -18437,16 +18566,21 @@ ${STATIC_DECLS}
18437
18566
  this.hide();
18438
18567
  };
18439
18568
  const onReflow = () => this.place();
18440
- setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
18569
+ setTimeout(() => {
18570
+ if (this.onOutside === onOutside) document.addEventListener("pointerdown", onOutside, true);
18571
+ }, 0);
18441
18572
  document.addEventListener("keydown", onKey, true);
18442
18573
  window.addEventListener("resize", onReflow, true);
18443
18574
  document.addEventListener("scroll", onReflow, true);
18444
18575
  this.onOutside = onOutside;
18445
18576
  this.onKey = onKey;
18446
18577
  this.onReflow = onReflow;
18578
+ announceSurface(this.el, true, "popover", this.trigger);
18447
18579
  }
18448
18580
  hide() {
18449
18581
  if (!this.shown) return;
18582
+ this.shown = false;
18583
+ announceSurface(this.el, false, "popover", this.trigger);
18450
18584
  if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
18451
18585
  if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
18452
18586
  if (this.onReflow) {
@@ -18466,7 +18600,6 @@ ${STATIC_DECLS}
18466
18600
  } else {
18467
18601
  this.el.remove();
18468
18602
  }
18469
- this.shown = false;
18470
18603
  if (open === this) open = null;
18471
18604
  this.ctrl.onClose?.();
18472
18605
  }
@@ -20356,6 +20489,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
20356
20489
  // src/ui/components/dialog/view.ts
20357
20490
  var Dialog = class {
20358
20491
  constructor(opts = {}) {
20492
+ this.opener = null;
20493
+ this.wasOpen = false;
20359
20494
  const doc = (opts.host ?? document.body).ownerDocument;
20360
20495
  injectStyles(DIALOG_STYLE_ID, DIALOG_CSS, doc);
20361
20496
  const host = opts.host ?? doc.body;
@@ -20452,6 +20587,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
20452
20587
  const mid = String(this.ctrl.props.id);
20453
20588
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
20454
20589
  const api = this.ctrl.connect(service);
20590
+ if (this.wasOpen && !api.open) this.announce(false);
20455
20591
  spreadProps(this.backdrop, api.getBackdropProps(), mid);
20456
20592
  spreadProps(this.positioner, api.getPositionerProps(), mid);
20457
20593
  spreadProps(this.panel, api.getContentProps(), mid);
@@ -20459,12 +20595,27 @@ ${overlayScrollbarCss(".vela-dialog *")}
20459
20595
  spreadProps(close, api.getCloseTriggerProps(), mid);
20460
20596
  this.backdrop.style.display = api.open ? "" : "none";
20461
20597
  this.positioner.style.display = api.open ? "" : "none";
20598
+ if (api.open && !this.wasOpen) this.announce(true);
20599
+ if (this.wasOpen && !api.open) this.restoreOpener();
20600
+ this.wasOpen = api.open;
20462
20601
  });
20463
20602
  }
20603
+ /** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
20604
+ * teardown that outruns the machine's close notification). */
20605
+ restoreOpener() {
20606
+ const opener = this.opener;
20607
+ this.opener = null;
20608
+ if (opener?.isConnected) opener.focus({ preventScroll: true });
20609
+ }
20610
+ announce(open2) {
20611
+ const opener = this.opener === this.panel.ownerDocument.body ? null : this.opener;
20612
+ announceSurface(this.panel, open2, "dialog", opener);
20613
+ }
20464
20614
  get open() {
20465
20615
  return this.ctrl.connect(this.handle.service).open;
20466
20616
  }
20467
20617
  show() {
20618
+ this.opener = this.panel.ownerDocument.activeElement;
20468
20619
  this.ctrl.connect(this.handle.service).setOpen(true);
20469
20620
  }
20470
20621
  hide() {
@@ -20479,6 +20630,11 @@ ${overlayScrollbarCss(".vela-dialog *")}
20479
20630
  destroy() {
20480
20631
  if (this.open) this.hide();
20481
20632
  this.handle.stop();
20633
+ if (this.wasOpen) {
20634
+ this.wasOpen = false;
20635
+ this.announce(false);
20636
+ }
20637
+ this.restoreOpener();
20482
20638
  this.backdrop.remove();
20483
20639
  this.positioner.remove();
20484
20640
  }
@@ -24648,15 +24804,23 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
24648
24804
  this.seg(x + w, y + h, x, y + h, lineWidth, c);
24649
24805
  this.seg(x, y + h, x, y, lineWidth, c);
24650
24806
  }
24651
- /** Filled circle (triangle fan). */
24807
+ /**
24808
+ * Filled circle (triangle fan), feathered like a line: the rim is padded by {@link AA_PAD} and
24809
+ * edgeDist runs from 0 at the center to the padded radius at the rim, with edgeHalf = r. The
24810
+ * distance grows linearly along each spoke, so the shader's line feather gives a round soft edge.
24811
+ */
24652
24812
  circle(cx, cy, r, c, segments = 16) {
24653
- let px = cx + r;
24813
+ const ext = r + AA_PAD;
24814
+ let px = cx + ext;
24654
24815
  let py = cy;
24816
+ this.ensure(segments * 3);
24655
24817
  for (let i = 1; i <= segments; i += 1) {
24656
24818
  const a = i / segments * Math.PI * 2;
24657
- const nx = cx + Math.cos(a) * r;
24658
- const ny = cy + Math.sin(a) * r;
24659
- this.tri(cx, cy, c, px, py, c, nx, ny, c);
24819
+ const nx = cx + Math.cos(a) * ext;
24820
+ const ny = cy + Math.sin(a) * ext;
24821
+ this.v(cx, cy, c, 0, r);
24822
+ this.v(px, py, c, ext, r);
24823
+ this.v(nx, ny, c, ext, r);
24660
24824
  px = nx;
24661
24825
  py = ny;
24662
24826
  }
@@ -24837,7 +25001,7 @@ void main() {
24837
25001
  }
24838
25002
  mount(canvas) {
24839
25003
  this.canvas = canvas;
24840
- this.gl = canvas.getContext("webgl2", { alpha: true, antialias: true, depth: false, stencil: false, premultipliedAlpha: true });
25004
+ this.gl = canvas.getContext("webgl2", { alpha: true, antialias: false, depth: false, stencil: false, premultipliedAlpha: true });
24841
25005
  if (!this.gl) return;
24842
25006
  if (!this.initGL()) {
24843
25007
  this.gl = null;
@@ -26289,7 +26453,7 @@ void main() {
26289
26453
  this.deps.drawingsPointerUp?.(x, y, this.snapMode(e));
26290
26454
  } else if (tapRelease && this.region === "data") {
26291
26455
  this.deps.onClick(x, y);
26292
- } else if (this.dragging && this.region === "data") {
26456
+ } else if (this.dragging && this.region === "data" && wasTouch) {
26293
26457
  const stale = e.timeStamp - this.lastT > FLING_STALE_MS;
26294
26458
  if (!stale && Math.abs(this.vx) > FLING_MIN_SPEED) {
26295
26459
  const pitch = this.deps.getCoords().pxPerBar();
@@ -32745,6 +32909,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
32745
32909
  activeDragId() {
32746
32910
  return this.state.kind === "pressed" && this.state.moved ? this.state.id : null;
32747
32911
  }
32912
+ /** The store drawings a live drag is moving — the pressed one and its riders — or none
32913
+ * before the drag passes the slop. A Ctrl-drag moves copies, so its sources stay put. */
32914
+ movingIds() {
32915
+ if (this.state.kind !== "pressed" || !this.state.moved || this.state.clones) return /* @__PURE__ */ new Set();
32916
+ return /* @__PURE__ */ new Set([this.state.id, ...this.state.riders.map((r) => r.id)]);
32917
+ }
32748
32918
  /** The drawing under a press that has not been released yet (drag or click undecided), or null. */
32749
32919
  pressedId() {
32750
32920
  return this.state.kind === "pressed" ? this.state.id : null;
@@ -33403,6 +33573,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33403
33573
  }
33404
33574
  }));
33405
33575
  }
33576
+ if (drawing instanceof FibRetracement) {
33577
+ const fib = drawing;
33578
+ grid.appendChild(fieldRow({
33579
+ label: "Reverse",
33580
+ bool: true,
33581
+ toggle: {
33582
+ checked: fib.reverse,
33583
+ onChange: (v) => actions.patch({ reverse: v })
33584
+ }
33585
+ }));
33586
+ }
33406
33587
  levels.forEach((lv, i) => {
33407
33588
  const row = document.createElement("div");
33408
33589
  row.className = "vela-field-span";
@@ -34770,6 +34951,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34770
34951
  );
34771
34952
  }
34772
34953
  }
34954
+ announceSurface(fly, true, "menu", cell);
34773
34955
  }
34774
34956
  openMagnetFlyout() {
34775
34957
  const cell = this.magnetCell;
@@ -34793,6 +34975,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34793
34975
  })
34794
34976
  );
34795
34977
  }
34978
+ announceSurface(fly, true, "menu", cell);
34796
34979
  }
34797
34980
  flyoutHeader(text) {
34798
34981
  const t = this.theme;
@@ -34861,9 +35044,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34861
35044
  }
34862
35045
  closeFlyout() {
34863
35046
  this.starEls.clear();
34864
- if (this.flyout) {
34865
- this.flyout.remove();
35047
+ const fly = this.flyout;
35048
+ if (fly) {
34866
35049
  this.flyout = null;
35050
+ announceSurface(fly, false, "menu", this.flyoutCell);
35051
+ fly.remove();
34867
35052
  this.flyoutCell?.classList.remove("vela-open");
34868
35053
  this.flyoutCell = null;
34869
35054
  this.flyoutOwnerId = null;
@@ -35188,6 +35373,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35188
35373
  this.drawings = [];
35189
35374
  /** Ids painted on an interleave layer this frame — the top canvas paints only their handles. */
35190
35375
  this.sliced = /* @__PURE__ */ new Set();
35376
+ /** Interleaved drawings mid-drag: painted on the top canvas, left out of their slice. */
35377
+ this.lifted = /* @__PURE__ */ new Set();
35191
35378
  /** Cached slice canvases, keyed `paneId|beforeZ`, reused across frames to avoid churn. */
35192
35379
  this.sliceCache = /* @__PURE__ */ new Map();
35193
35380
  /** Series boundaries per pane as of the last `prepareSlices` — lets a repaint between data
@@ -35275,7 +35462,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35275
35462
  selectedIds: () => this.selectedIds,
35276
35463
  emit: (i) => this.emit(i),
35277
35464
  changed: () => {
35278
- this.invalidateSlices();
35465
+ this.syncLift();
35279
35466
  this.render();
35280
35467
  },
35281
35468
  openSettings: (id, x, y) => this.openSettingsById(id, x, y),
@@ -35901,6 +36088,20 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35901
36088
  /** A drawing that paints inside the series stack changed (content, not hover): its pixels
35902
36089
  * live in the backend composite, so this layer alone can't show the change — ask for a
35903
36090
  * data frame, which re-runs `prepareSlices` before the backend composites. */
36091
+ /** A drawing being dragged leaves its interleave slice for the top canvas, so each pointer
36092
+ * move repaints one 2D layer instead of re-rasterizing and re-uploading a plot-sized slice
36093
+ * texture through the data frame. The slices rebuild once as the drag starts and once as it
36094
+ * ends (release, cancel), when the lifted set changes. */
36095
+ syncLift() {
36096
+ const moving = this.interaction.movingIds();
36097
+ let same = moving.size === this.lifted.size;
36098
+ if (same) {
36099
+ for (const id of moving) if (!this.lifted.has(id)) same = false;
36100
+ }
36101
+ if (same) return;
36102
+ this.lifted = new Set(moving);
36103
+ this.invalidateSlices();
36104
+ }
35904
36105
  invalidateSlices() {
35905
36106
  if (this.sliced.size > 0 || this.drawings.some((d) => this.isInterleaved(d))) this.deps.requestDataPaint();
35906
36107
  }
@@ -35919,7 +36120,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35919
36120
  const theme = this.deps.theme();
35920
36121
  const buckets = /* @__PURE__ */ new Map();
35921
36122
  for (const d of this.drawings) {
35922
- if (!d.visible) continue;
36123
+ if (!d.visible || this.lifted.has(d.id)) continue;
35923
36124
  const beforeZ = sliceKeyFor(d.zIndex, this.lastBounds.get(d.paneId) ?? []);
35924
36125
  if (beforeZ === null) continue;
35925
36126
  const key = `${d.paneId}|${beforeZ}`;
@@ -35971,8 +36172,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35971
36172
  mutedLabel: edited instanceof TextLabel ? edited.id : null
35972
36173
  };
35973
36174
  this.painter.seriesLook = this.deps.seriesLook();
35974
- this.painter.paintAll(ctx, this.drawings.filter((d) => !this.isInterleaved(d)), proj, this.deps.theme(), targets);
35975
- this.painter.paintHighlights(ctx, this.drawings.filter((d) => this.isInterleaved(d)), proj, handleIdsFor(targets));
36175
+ const onTop = (d) => !this.isInterleaved(d) || this.lifted.has(d.id);
36176
+ this.painter.paintAll(ctx, this.drawings.filter(onTop), proj, this.deps.theme(), targets);
36177
+ this.painter.paintHighlights(ctx, this.drawings.filter((d) => !onTop(d)), proj, handleIdsFor(targets));
35976
36178
  const clones = this.interaction.dragClones();
35977
36179
  if (clones) this.painter.paintAll(ctx, clones, proj, this.deps.theme(), { selected: new Set(clones.map((c) => c.id)) });
35978
36180
  this.layoutTextEditor();