@luxalgo/vela 0.6.1 → 0.6.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 (44) hide show
  1. package/dist/{DataProvider-BNKtYU5V.d.cts → DataProvider-CNmk84SH.d.cts} +14 -0
  2. package/dist/{DataProvider-gJjN_0eh.d.ts → DataProvider-DHX4x6-r.d.ts} +14 -0
  3. package/dist/{chunk-4MQEG67Z.js → chunk-7D6YIF34.js} +2135 -2452
  4. package/dist/{chunk-ZNL2X6U2.js → chunk-7Y7CJ7EN.js} +1 -1
  5. package/dist/{chunk-EMS6PO2T.js → chunk-PN5KWFZ4.js} +6 -1
  6. package/dist/chunk-QHZ7IXFL.js +2598 -0
  7. package/dist/chunk-TP56QRMK.js +503 -0
  8. package/dist/{chunk-6LZJRO2S.js → chunk-U5KCQHAC.js} +108 -5
  9. package/dist/{contributions-D64UA74H.d.cts → contributions-DDpv7hEL.d.cts} +5 -1
  10. package/dist/{contributions-lPAgo9Cu.d.ts → contributions-ciV6Neyd.d.ts} +5 -1
  11. package/dist/{history-CVoFDJ2D.d.ts → history-CFqZm5re.d.ts} +2 -2
  12. package/dist/{history-fSMXVLt1.d.cts → history-ChoT34nz.d.cts} +2 -2
  13. package/dist/index.cjs +3708 -2730
  14. package/dist/index.d.cts +4 -4
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.js +4 -4
  17. package/dist/{plugin-o5HJH46Z.d.ts → plugin-C97gUCVf.d.ts} +6 -2
  18. package/dist/{plugin-D5ni4WEJ.d.cts → plugin-CSb_sTiN.d.cts} +6 -2
  19. package/dist/plugin.d.cts +3 -3
  20. package/dist/plugin.d.ts +3 -3
  21. package/dist/plugin.js +2 -2
  22. package/dist/providers/binance.d.cts +1 -1
  23. package/dist/providers/binance.d.ts +1 -1
  24. package/dist/providers/coinbase.d.cts +1 -1
  25. package/dist/providers/coinbase.d.ts +1 -1
  26. package/dist/providers/hyperliquid.d.cts +1 -1
  27. package/dist/providers/hyperliquid.d.ts +1 -1
  28. package/dist/ui.cjs +1001 -247
  29. package/dist/ui.d.cts +257 -10
  30. package/dist/ui.d.ts +257 -10
  31. package/dist/ui.js +3 -3
  32. package/dist/vela.global.js +12345 -3952
  33. package/dist/vela.global.min.js +479 -124
  34. package/dist/widget.cjs +3388 -2865
  35. package/dist/widget.d.cts +7 -5
  36. package/dist/widget.d.ts +7 -5
  37. package/dist/widget.js +13 -9
  38. package/dist/workspace.cjs +3051 -2528
  39. package/dist/workspace.d.cts +6 -4
  40. package/dist/workspace.d.ts +6 -4
  41. package/dist/workspace.js +11 -7
  42. package/package.json +1 -1
  43. package/dist/chunk-OGRQW3M6.js +0 -1328
  44. package/dist/chunk-QWIEKZRE.js +0 -1042
@@ -0,0 +1,503 @@
1
+ import { normalizeProps, nextUid, runMachine, spreadProps } from './chunk-QHZ7IXFL.js';
2
+ import { injectStyles } from './chunk-PN5KWFZ4.js';
3
+ import * as tooltip from '@zag-js/tooltip';
4
+ import * as dialog from '@zag-js/dialog';
5
+
6
+ // src/ui/keymap.ts
7
+ var KEY_ALIASES = {
8
+ esc: "escape",
9
+ space: " ",
10
+ plus: "+",
11
+ minus: "-",
12
+ del: "delete",
13
+ return: "enter",
14
+ left: "arrowleft",
15
+ right: "arrowright",
16
+ up: "arrowup",
17
+ down: "arrowdown"
18
+ };
19
+ var MAC_GLYPHS = { meta: "\u2318", ctrl: "\u2303", alt: "\u2325", shift: "\u21E7" };
20
+ function parseChord(spec, mac) {
21
+ const parts = spec.toLowerCase().split("+").map((p) => p.trim()).filter((p, i, a) => p !== "" || a[i - 1] === "");
22
+ const chord = { ctrl: false, meta: false, alt: false, shift: false, key: "" };
23
+ for (const raw of parts) {
24
+ const p = raw === "" ? "+" : raw;
25
+ if (p === "mod") mac ? chord.meta = true : chord.ctrl = true;
26
+ else if (p === "ctrl" || p === "control") chord.ctrl = true;
27
+ else if (p === "meta" || p === "cmd" || p === "command") chord.meta = true;
28
+ else if (p === "alt" || p === "option") chord.alt = true;
29
+ else if (p === "shift") chord.shift = true;
30
+ else chord.key = KEY_ALIASES[p] ?? p;
31
+ }
32
+ return chord;
33
+ }
34
+ function eventMatches(ev, c) {
35
+ return ev.ctrlKey === c.ctrl && ev.metaKey === c.meta && ev.altKey === c.alt && // Shift is part of producing many printable keys ('?', '+') — only enforce it
36
+ // when the chord names a non-printable/letter key where shift is a real modifier.
37
+ (c.key.length > 1 || /^[a-z0-9 ]$/.test(c.key) ? ev.shiftKey === c.shift : true) && ev.key.toLowerCase() === c.key;
38
+ }
39
+ function isEditableTarget(ev) {
40
+ const t = ev.target;
41
+ if (!t || typeof t !== "object") return false;
42
+ const tag = (t.tagName ?? "").toLowerCase();
43
+ if (tag === "input" || tag === "textarea" || tag === "select") return true;
44
+ if (t.isContentEditable === true) return true;
45
+ return (typeof t.getAttribute === "function" ? t.getAttribute("role") : null) === "textbox";
46
+ }
47
+ function displayChord(spec, mac) {
48
+ const c = parseChord(spec, mac);
49
+ const keyLabel = c.key === " " ? "Space" : c.key.length === 1 ? c.key.toUpperCase() : c.key.charAt(0).toUpperCase() + c.key.slice(1);
50
+ if (mac) {
51
+ return (c.ctrl ? MAC_GLYPHS.ctrl : "") + (c.alt ? MAC_GLYPHS.alt : "") + (c.shift ? MAC_GLYPHS.shift : "") + (c.meta ? MAC_GLYPHS.meta : "") + keyLabel;
52
+ }
53
+ const mods = [c.ctrl && "Ctrl", c.alt && "Alt", c.shift && "Shift", c.meta && "Win"].filter(Boolean);
54
+ return [...mods, keyLabel].join("+");
55
+ }
56
+ var KeymapManager = class {
57
+ constructor(opts = {}) {
58
+ this.descriptors = /* @__PURE__ */ new Map();
59
+ this.rebinds = /* @__PURE__ */ new Map();
60
+ this.scopeStack = [];
61
+ this.target = null;
62
+ this.onKeydown = (ev) => {
63
+ this.handleKeydown(ev);
64
+ };
65
+ this.mac = opts.platform !== void 0 ? opts.platform === "mac" : typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.platform ?? "");
66
+ this.baseScope = opts.baseScope ?? "chart";
67
+ }
68
+ /** Register (or replace, by id) a binding. Returns a disposer. */
69
+ register(desc) {
70
+ this.descriptors.set(desc.id, desc);
71
+ return () => {
72
+ if (this.descriptors.get(desc.id) === desc) this.descriptors.delete(desc.id);
73
+ };
74
+ }
75
+ unregister(id) {
76
+ this.descriptors.delete(id);
77
+ this.rebinds.delete(id);
78
+ }
79
+ /** User-level rebinding: overrides the descriptor's default chords (null resets). */
80
+ rebind(id, keys) {
81
+ if (keys === null) this.rebinds.delete(id);
82
+ else this.rebinds.set(id, Array.isArray(keys) ? [...keys] : [keys]);
83
+ }
84
+ /** Snapshot for a shortcuts help panel / rebinding UI. */
85
+ bindings() {
86
+ return [...this.descriptors.values()].map((d) => {
87
+ const keys = this.activeKeys(d);
88
+ return {
89
+ id: d.id,
90
+ label: d.label,
91
+ category: d.category ?? "General",
92
+ scope: d.scope ?? this.baseScope,
93
+ keys,
94
+ display: keys.map((k) => displayChord(k, this.mac))
95
+ };
96
+ });
97
+ }
98
+ pushScope(scope) {
99
+ this.scopeStack.push(scope);
100
+ return () => this.popScope(scope);
101
+ }
102
+ /** Pops the TOPMOST occurrence of `scope` (tolerates out-of-order teardown). */
103
+ popScope(scope) {
104
+ const i = this.scopeStack.lastIndexOf(scope);
105
+ if (i >= 0) this.scopeStack.splice(i, 1);
106
+ }
107
+ get activeScope() {
108
+ return this.scopeStack[this.scopeStack.length - 1] ?? this.baseScope;
109
+ }
110
+ attach(target) {
111
+ this.detach();
112
+ this.target = target;
113
+ target.addEventListener("keydown", this.onKeydown);
114
+ }
115
+ detach() {
116
+ this.target?.removeEventListener("keydown", this.onKeydown);
117
+ this.target = null;
118
+ }
119
+ /** The matcher — public so hosts/tests can feed events from their own listeners. */
120
+ handleKeydown(ev) {
121
+ const editable = isEditableTarget(ev);
122
+ for (const d of this.descriptors.values()) {
123
+ const scope = d.scope ?? this.baseScope;
124
+ if (scope !== "global" && scope !== this.activeScope) continue;
125
+ if (editable && !d.allowInInput) continue;
126
+ if (d.when && !d.when()) continue;
127
+ for (const spec of this.activeKeys(d)) {
128
+ if (eventMatches(ev, parseChord(spec, this.mac))) {
129
+ if (d.preventDefault !== false) {
130
+ ev.preventDefault?.();
131
+ ev.stopPropagation?.();
132
+ }
133
+ d.run(ev);
134
+ return true;
135
+ }
136
+ }
137
+ }
138
+ return false;
139
+ }
140
+ destroy() {
141
+ this.detach();
142
+ this.descriptors.clear();
143
+ this.rebinds.clear();
144
+ this.scopeStack.length = 0;
145
+ }
146
+ activeKeys(d) {
147
+ return this.rebinds.get(d.id) ?? (Array.isArray(d.keys) ? d.keys : [d.keys]);
148
+ }
149
+ };
150
+ function tooltipController(opts = {}) {
151
+ return {
152
+ machine: tooltip.machine,
153
+ props: {
154
+ id: nextUid("vela-tooltip"),
155
+ openDelay: opts.openDelay ?? 0,
156
+ closeDelay: opts.closeDelay ?? 0,
157
+ interactive: opts.interactive ?? false,
158
+ ids: opts.triggerId ? { trigger: opts.triggerId } : void 0,
159
+ positioning: { placement: opts.placement ?? "top" }
160
+ },
161
+ connect: (service) => tooltip.connect(service, normalizeProps)
162
+ };
163
+ }
164
+
165
+ // src/ui/components/tooltip/styles.ts
166
+ var TOOLTIP_STYLE_ID = "vela-ui-tooltip";
167
+ var TOOLTIP_CSS = `
168
+ .vela-tooltip {
169
+ background: var(--vela-bg);
170
+ color: var(--vela-fg);
171
+ border: 1px solid var(--vela-border-soft);
172
+ border-radius: var(--vela-radius-md);
173
+ box-shadow: var(--vela-shadow);
174
+ font-size: var(--vela-font-size-md);
175
+ line-height: 1.4;
176
+ padding: var(--vela-space-1) var(--vela-space-2);
177
+ max-width: 280px;
178
+ pointer-events: none;
179
+ z-index: var(--vela-z-tooltip);
180
+ }
181
+ .vela-tooltip[data-interactive] { pointer-events: auto; }
182
+ .vela-tooltip[data-state='open'] { animation: vela-tooltip-in 0.12s ease; }
183
+ @keyframes vela-tooltip-in {
184
+ from { opacity: 0; transform: scale(0.97); }
185
+ to { opacity: 1; transform: scale(1); }
186
+ }
187
+ `;
188
+ function resolveHost(trigger, host) {
189
+ return host ?? trigger.closest(".vela-ui") ?? trigger.ownerDocument.body;
190
+ }
191
+ var Tooltip = class {
192
+ constructor(trigger, opts) {
193
+ this.trigger = trigger;
194
+ const doc = trigger.ownerDocument;
195
+ injectStyles(TOOLTIP_STYLE_ID, TOOLTIP_CSS, doc);
196
+ this.positioner = doc.createElement("div");
197
+ this.positioner.className = "vela-ui-layer";
198
+ this.content = doc.createElement("div");
199
+ this.content.className = "vela-tooltip";
200
+ this.positioner.appendChild(this.content);
201
+ resolveHost(trigger, opts.host).appendChild(this.positioner);
202
+ this.setContent(opts.content);
203
+ const ctrl = tooltipController(opts);
204
+ const mid = String(ctrl.props.id);
205
+ if (opts.triggerId) trigger.id = opts.triggerId;
206
+ this.handle = runMachine(ctrl.machine, ctrl.props, (service) => {
207
+ const api = ctrl.connect(service);
208
+ spreadProps(trigger, api.getTriggerProps(), mid);
209
+ spreadProps(this.positioner, api.getPositionerProps(), mid);
210
+ spreadProps(this.content, api.getContentProps(), mid);
211
+ });
212
+ }
213
+ setContent(content) {
214
+ this.content.replaceChildren(typeof content === "function" ? content() : content);
215
+ }
216
+ destroy() {
217
+ this.handle.stop();
218
+ this.positioner.remove();
219
+ this.trigger.removeAttribute("data-scope");
220
+ }
221
+ };
222
+ function drawerController(opts = {}) {
223
+ return {
224
+ machine: dialog.machine,
225
+ props: {
226
+ id: nextUid("vela-drawer"),
227
+ modal: true,
228
+ closeOnEscape: opts.closeOnEscape ?? true,
229
+ closeOnInteractOutside: opts.closeOnInteractOutside ?? true,
230
+ initialFocusEl: opts.initialFocusEl,
231
+ onOpenChange: (d) => opts.onOpenChange?.(d.open)
232
+ },
233
+ connect: (service) => dialog.connect(service, normalizeProps)
234
+ };
235
+ }
236
+
237
+ // src/ui/components/drawer/styles.ts
238
+ var DRAWER_STYLE_ID = "vela-ui-drawer";
239
+ var DRAWER_CSS = `
240
+ .vela-drawer-backdrop {
241
+ position: fixed;
242
+ inset: 0;
243
+ background: var(--vela-backdrop);
244
+ z-index: var(--vela-z-dialog);
245
+ }
246
+ .vela-drawer-positioner {
247
+ position: fixed;
248
+ inset: 0;
249
+ display: flex;
250
+ align-items: flex-end;
251
+ justify-content: center;
252
+ z-index: var(--vela-z-dialog);
253
+ }
254
+ /* Inside a shell that declares a size class, the sheet scopes to the SHELL's bounds
255
+ (the widget root is position:relative) instead of the whole viewport \u2014 an embedded
256
+ chart must not curtain the host page. */
257
+ [data-layout] .vela-drawer-backdrop, [data-layout] .vela-drawer-positioner { position: absolute; }
258
+ .vela-drawer {
259
+ background: var(--vela-surface);
260
+ color: var(--vela-fg);
261
+ border: 1px solid var(--vela-border-strong);
262
+ border-bottom: none;
263
+ border-radius: 14px 14px 0 0;
264
+ box-shadow: var(--vela-shadow-dialog);
265
+ font-size: 13px;
266
+ width: 100%;
267
+ max-height: 85%;
268
+ display: flex;
269
+ flex-direction: column;
270
+ overflow: hidden;
271
+ outline: none;
272
+ }
273
+ .vela-drawer[data-state='open'] { animation: vela-drawer-in var(--vela-dur-med) var(--vela-ease); }
274
+ @keyframes vela-drawer-in {
275
+ from { transform: translateY(100%); }
276
+ to { transform: translateY(0); }
277
+ }
278
+ /* The grab zone owns its touches (drag-to-dismiss), so the browser must not scroll it. */
279
+ .vela-drawer-grab {
280
+ flex: none;
281
+ display: flex;
282
+ align-items: center;
283
+ justify-content: center;
284
+ padding: 10px 0 6px;
285
+ cursor: grab;
286
+ touch-action: none;
287
+ user-select: none;
288
+ }
289
+ .vela-drawer-grab::before {
290
+ content: '';
291
+ width: 36px;
292
+ height: 4px;
293
+ border-radius: 2px;
294
+ background: var(--vela-border-strong);
295
+ }
296
+ .vela-drawer-title {
297
+ flex: none;
298
+ padding: 0 16px 10px;
299
+ font-size: 15px;
300
+ font-weight: 600;
301
+ letter-spacing: 0.2px;
302
+ color: var(--vela-fg-bright);
303
+ user-select: none;
304
+ }
305
+ .vela-drawer-title:empty { display: none; }
306
+ .vela-drawer-body {
307
+ flex: 1 1 auto;
308
+ min-height: 0;
309
+ overflow-y: auto;
310
+ overscroll-behavior: contain;
311
+ -webkit-overflow-scrolling: touch;
312
+ /* Vertical pans stay native scrolling; horizontal moves reach the sheet's gesture
313
+ recognizer as pointer events (tab swipes). Without this the browser claims a
314
+ sideways touch as a scroll attempt and CANCELS the pointer stream, so swipes
315
+ never registered on real touch devices. Sideways-scrolling strips inside the
316
+ body opt back in with their own touch-action: pan-x. */
317
+ touch-action: pan-y;
318
+ padding: 0 var(--vela-space-3) calc(var(--vela-space-3) + env(safe-area-inset-bottom, 0px));
319
+ }
320
+ .vela-drawer-body::-webkit-scrollbar { width: 8px; }
321
+ .vela-drawer-body::-webkit-scrollbar-thumb {
322
+ background: var(--vela-scroll);
323
+ border-radius: 4px;
324
+ border: 2px solid transparent;
325
+ background-clip: padding-box;
326
+ }
327
+ `;
328
+ var DISMISS_FRACTION = 0.33;
329
+ var DISMISS_PX = 96;
330
+ var SLOP_PX = 8;
331
+ var HSWIPE_MIN_PX = 48;
332
+ function classifyGesture(dx, dy, ctx) {
333
+ if (Math.max(Math.abs(dx), Math.abs(dy)) < SLOP_PX) return "pending";
334
+ if (Math.abs(dy) > Math.abs(dx)) return dy > 0 && !ctx.scrolled ? "drag" : "scroll";
335
+ return ctx.canSwipe && !ctx.hScrollable ? "hswipe" : "scroll";
336
+ }
337
+ function dragDismisses(dy, panelHeightPx) {
338
+ return dy >= Math.min(DISMISS_PX, panelHeightPx * DISMISS_FRACTION);
339
+ }
340
+ function swipeDirection(dx, dy) {
341
+ if (Math.abs(dx) < HSWIPE_MIN_PX || Math.abs(dx) <= Math.abs(dy)) return null;
342
+ return dx < 0 ? "left" : "right";
343
+ }
344
+ var Drawer = class {
345
+ constructor(opts = {}) {
346
+ const doc = (opts.host ?? document.body).ownerDocument;
347
+ injectStyles(DRAWER_STYLE_ID, DRAWER_CSS, doc);
348
+ const host = opts.host ?? doc.body;
349
+ this.backdrop = doc.createElement("div");
350
+ this.backdrop.className = "vela-drawer-backdrop vela-ui-layer";
351
+ this.positioner = doc.createElement("div");
352
+ this.positioner.className = "vela-drawer-positioner vela-ui-layer";
353
+ this.panel = doc.createElement("div");
354
+ this.panel.className = "vela-drawer";
355
+ this.panel.tabIndex = -1;
356
+ const grab = doc.createElement("div");
357
+ grab.className = "vela-drawer-grab";
358
+ this.titleEl = doc.createElement("div");
359
+ this.titleEl.className = "vela-drawer-title";
360
+ this.titleEl.textContent = opts.title ?? "";
361
+ this.body = doc.createElement("div");
362
+ this.body.className = "vela-drawer-body";
363
+ if (opts.content instanceof Node) this.body.appendChild(opts.content);
364
+ else if (typeof opts.content === "function") opts.content(this.body);
365
+ this.panel.append(grab, this.titleEl, this.body);
366
+ this.positioner.appendChild(this.panel);
367
+ host.append(this.backdrop, this.positioner);
368
+ this.wireGestures(grab, opts.onSwipe);
369
+ this.ctrl = drawerController({ ...opts, initialFocusEl: () => this.panel });
370
+ const mid = String(this.ctrl.props.id);
371
+ this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
372
+ const api = this.ctrl.connect(service);
373
+ spreadProps(this.backdrop, api.getBackdropProps(), mid);
374
+ spreadProps(this.positioner, api.getPositionerProps(), mid);
375
+ spreadProps(this.panel, api.getContentProps(), mid);
376
+ spreadProps(this.titleEl, api.getTitleProps(), mid);
377
+ this.backdrop.style.display = api.open ? "" : "none";
378
+ this.positioner.style.display = api.open ? "" : "none";
379
+ });
380
+ }
381
+ /** Any element between `from` and the panel that has already been scrolled down —
382
+ * a downward pull there must scroll it back up, never drag the sheet. */
383
+ scrolledAncestor(from) {
384
+ let el = from instanceof Element ? from : null;
385
+ while (el && el !== this.panel) {
386
+ if (el.scrollTop > 0) return true;
387
+ el = el.parentElement;
388
+ }
389
+ return false;
390
+ }
391
+ /** Any element between `from` and the panel that scrolls horizontally on its own
392
+ * (the tab strip, chip rows) — a sideways move there is ITS scroll, not a swipe. */
393
+ hScrollableAncestor(from) {
394
+ let el = from instanceof Element ? from : null;
395
+ while (el && el !== this.panel) {
396
+ if (el.scrollWidth > el.clientWidth + 1) return true;
397
+ el = el.parentElement;
398
+ }
399
+ return false;
400
+ }
401
+ /**
402
+ * One gesture recognizer for the whole sheet. A downward pull dismisses from
403
+ * anywhere — the grab handle immediately, the content once it is decidedly vertical
404
+ * and its scroller is at rest (a scrolled list keeps native scrolling). A decidedly
405
+ * horizontal move becomes an `onSwipe` (tabbed drawers flip pages with it). The
406
+ * non-passive touchmove hook is what keeps the browser from claiming the pull as a
407
+ * scroll once the sheet is (or may become) the drag target.
408
+ */
409
+ wireGestures(grab, onSwipe) {
410
+ let startX = 0;
411
+ let startY = 0;
412
+ let dx = 0;
413
+ let dy = 0;
414
+ let mode = "idle";
415
+ const beginDrag = (e) => {
416
+ mode = "drag";
417
+ startY = e.clientY;
418
+ this.panel.style.transition = "none";
419
+ try {
420
+ this.panel.setPointerCapture(e.pointerId);
421
+ } catch {
422
+ }
423
+ };
424
+ this.panel.addEventListener("pointerdown", (e) => {
425
+ if (e.isPrimary === false) return;
426
+ startX = e.clientX;
427
+ startY = e.clientY;
428
+ dx = 0;
429
+ dy = 0;
430
+ if (grab.contains(e.target)) beginDrag(e);
431
+ else mode = "pending";
432
+ });
433
+ this.panel.addEventListener("pointermove", (e) => {
434
+ if (mode === "idle" || mode === "scroll") return;
435
+ dx = e.clientX - startX;
436
+ dy = e.clientY - startY;
437
+ if (mode === "pending") {
438
+ const intent = classifyGesture(dx, dy, {
439
+ canSwipe: !!onSwipe,
440
+ scrolled: this.scrolledAncestor(e.target),
441
+ hScrollable: this.hScrollableAncestor(e.target)
442
+ });
443
+ if (intent === "pending") return;
444
+ if (intent === "drag") beginDrag(e);
445
+ else mode = intent;
446
+ }
447
+ if (mode === "drag") {
448
+ dy = Math.max(0, e.clientY - startY);
449
+ this.panel.style.transform = dy > 0 ? `translateY(${dy}px)` : "";
450
+ }
451
+ });
452
+ this.panel.addEventListener(
453
+ "touchmove",
454
+ (e) => {
455
+ if (mode === "drag" || mode === "hswipe") {
456
+ e.preventDefault();
457
+ return;
458
+ }
459
+ if (mode !== "pending") return;
460
+ const t = e.touches[0];
461
+ if (!t) return;
462
+ const mdx = t.clientX - startX;
463
+ const mdy = t.clientY - startY;
464
+ if (mdy > Math.abs(mdx) && !this.scrolledAncestor(e.target)) e.preventDefault();
465
+ },
466
+ { passive: false }
467
+ );
468
+ const settle = () => {
469
+ if (mode === "idle") return;
470
+ const finished = mode;
471
+ mode = "idle";
472
+ if (finished === "drag") {
473
+ this.panel.style.transition = "";
474
+ this.panel.style.transform = "";
475
+ if (dragDismisses(dy, this.panel.getBoundingClientRect().height)) this.hide();
476
+ } else if (finished === "hswipe") {
477
+ const dir = swipeDirection(dx, dy);
478
+ if (dir) onSwipe?.(dir);
479
+ }
480
+ };
481
+ this.panel.addEventListener("pointerup", settle);
482
+ this.panel.addEventListener("pointercancel", settle);
483
+ }
484
+ setTitle(title) {
485
+ this.titleEl.textContent = title;
486
+ }
487
+ get open() {
488
+ return this.ctrl.connect(this.handle.service).open;
489
+ }
490
+ show() {
491
+ this.ctrl.connect(this.handle.service).setOpen(true);
492
+ }
493
+ hide() {
494
+ this.ctrl.connect(this.handle.service).setOpen(false);
495
+ }
496
+ destroy() {
497
+ this.handle.stop();
498
+ this.backdrop.remove();
499
+ this.positioner.remove();
500
+ }
501
+ };
502
+
503
+ export { Drawer, KeymapManager, Tooltip, drawerController, isEditableTarget, tooltipController };
@@ -1,7 +1,8 @@
1
- import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel } from './chunk-4MQEG67Z.js';
2
- import { chartType, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, getDrawingType } from './chunk-ZNL2X6U2.js';
3
- import { Tooltip, Menu, Dialog, Drawer } from './chunk-QWIEKZRE.js';
4
- import { iconMarkup, registerIcon, injectStyles, iconEl, SESSION_PRE, SESSION_POST, SESSION_OFF, icon, categoricalColor } from './chunk-EMS6PO2T.js';
1
+ import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel } from './chunk-7D6YIF34.js';
2
+ import { chartType, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, getDrawingType } from './chunk-7Y7CJ7EN.js';
3
+ import { Tooltip, Drawer } from './chunk-TP56QRMK.js';
4
+ import { Menu, Dialog } from './chunk-QHZ7IXFL.js';
5
+ import { iconMarkup, registerIcon, injectStyles, iconEl, SESSION_PRE, SESSION_POST, SESSION_OFF, icon, categoricalColor } from './chunk-PN5KWFZ4.js';
5
6
 
6
7
  // src/widget/timeframe.ts
7
8
  var UNIT_MS = {
@@ -4403,6 +4404,108 @@ function indicatorLedger(i) {
4403
4404
  // src/core/options.ts
4404
4405
  var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
4405
4406
 
4407
+ // src/widget/market-status.ts
4408
+ function civilDate(ms, tz) {
4409
+ try {
4410
+ return new Intl.DateTimeFormat("en-CA", { timeZone: tz }).format(ms);
4411
+ } catch {
4412
+ return "";
4413
+ }
4414
+ }
4415
+ function isWeekday(ms, tz) {
4416
+ try {
4417
+ const wd = new Intl.DateTimeFormat("en-US", { timeZone: tz, weekday: "short" }).format(ms);
4418
+ return wd !== "Sat" && wd !== "Sun";
4419
+ } catch {
4420
+ return false;
4421
+ }
4422
+ }
4423
+ function deriveMarketStatus(now, w, tz) {
4424
+ const within = (ws) => ws.find(([s, e]) => now >= s && now < e);
4425
+ if (within(w.regular)) return "open";
4426
+ const ext = within(w.extended);
4427
+ if (ext) return w.regular.some(([s]) => s >= now && s < ext[1]) ? "pre" : "post";
4428
+ if (isWeekday(now, tz)) {
4429
+ const today = civilDate(now, tz);
4430
+ if (today !== "" && !w.extended.some(([s]) => civilDate(s, tz) === today)) return "holiday";
4431
+ }
4432
+ return "closed";
4433
+ }
4434
+ function nextStatusBoundary(now, w) {
4435
+ let next = null;
4436
+ for (const ws of [w.regular, w.extended]) {
4437
+ for (const [s, e] of ws) {
4438
+ for (const t of [s, e]) {
4439
+ if (t > now && (next == null || t < next)) next = t;
4440
+ }
4441
+ }
4442
+ }
4443
+ return next;
4444
+ }
4445
+ var FETCH_BACK_MS = 4 * 864e5;
4446
+ var FETCH_AHEAD_MS = 10 * 864e5;
4447
+ var MIN_TIMER_MS = 15e3;
4448
+ var MAX_TIMER_MS = 36e5;
4449
+ var RETRY_MS = 6e4;
4450
+ var MarketStatusTracker = class {
4451
+ constructor(onStatus) {
4452
+ this.onStatus = onStatus;
4453
+ /** Invalidates detached async work — bumped by every track()/stop(). */
4454
+ this.epoch = 0;
4455
+ this.timer = null;
4456
+ }
4457
+ /** (Re)bind to a chart's data surface + symbol and start evaluating. */
4458
+ track(data, symbol) {
4459
+ const my = ++this.epoch;
4460
+ this.clearTimer();
4461
+ void this.evaluate(my, data, symbol);
4462
+ }
4463
+ stop() {
4464
+ this.epoch += 1;
4465
+ this.clearTimer();
4466
+ }
4467
+ async evaluate(my, data, symbol) {
4468
+ const resolved = data.resolve(symbol);
4469
+ const provider = resolved ? data.providerInstance(resolved.provider) : void 0;
4470
+ const si = await data.symbolInfo(symbol).catch(() => void 0);
4471
+ if (my !== this.epoch) return;
4472
+ const hasSessions = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
4473
+ if (!provider?.getCalendar || !hasSessions || !resolved) {
4474
+ this.onStatus("open");
4475
+ return;
4476
+ }
4477
+ const tz = typeof si?.timezone === "string" && si.timezone !== "" ? si.timezone : "Etc/UTC";
4478
+ const now = Date.now();
4479
+ const range = { from: now - FETCH_BACK_MS, to: now + FETCH_AHEAD_MS };
4480
+ const [regular, extended] = await Promise.all([
4481
+ provider.getCalendar(resolved.ticker, { ...range, session: "regular" }).catch(() => null),
4482
+ provider.getCalendar(resolved.ticker, { ...range, session: "extended" }).catch(() => null)
4483
+ ]);
4484
+ if (my !== this.epoch) return;
4485
+ if (!regular || !extended) {
4486
+ this.arm(my, data, symbol, RETRY_MS);
4487
+ return;
4488
+ }
4489
+ const w = { regular, extended };
4490
+ this.onStatus(deriveMarketStatus(now, w, tz));
4491
+ const boundary = nextStatusBoundary(now, w);
4492
+ const delay = Math.min(boundary != null ? boundary - now : MAX_TIMER_MS, MAX_TIMER_MS);
4493
+ this.arm(my, data, symbol, Math.max(delay, MIN_TIMER_MS));
4494
+ }
4495
+ arm(my, data, symbol, delay) {
4496
+ this.clearTimer();
4497
+ this.timer = setTimeout(() => {
4498
+ this.timer = null;
4499
+ if (my !== this.epoch) return;
4500
+ void this.evaluate(my, data, symbol);
4501
+ }, delay);
4502
+ }
4503
+ clearTimer() {
4504
+ if (this.timer != null) clearTimeout(this.timer);
4505
+ this.timer = null;
4506
+ }
4507
+ };
4508
+
4406
4509
  // src/widget/layout-mode.ts
4407
4510
  var MOBILE_BREAKPOINT_PX = 640;
4408
4511
  var COARSE_BREAKPOINT_PX = 920;
@@ -5769,4 +5872,4 @@ var Toast = class {
5769
5872
  }
5770
5873
  };
5771
5874
 
5772
- export { Bottombar, ChartContextMenu, DataWindow, DrawingPill, DrawingsDrawer, Glider, IndicatorPicker, LayoutModeController, MobileBar, MoreDrawer, ObjectTree, PAN_FAST, PanelDock, PriceScaleDrawer, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeDrawer, TimeframeQuick, TimezoneDrawer, Toast, Topbar, Watermark, WidgetHistory, ZOOM_IN, ZOOM_OUT, dataWindowSections, decimalsFor, decodeState, encodeState, filterSymbols, fmtChange, fmtPrice, indicatorLedger, legacyWidgetState, loadPersisted, localStorageAdapter, normalizeSession, parsePersisted, parseTimeframe, prefixedSymbol, priceStyleIcon, priceStyleLabel, resolveIndicators, sanitizeState, savePersisted, statuslineInkOf, timeframeLabel, timeframeMs, toolShortcutHints };
5875
+ export { Bottombar, ChartContextMenu, DataWindow, DrawingPill, DrawingsDrawer, Glider, IndicatorPicker, LayoutModeController, MarketStatusTracker, MobileBar, MoreDrawer, ObjectTree, PAN_FAST, PanelDock, PriceScaleDrawer, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeDrawer, TimeframeQuick, TimezoneDrawer, Toast, Topbar, Watermark, WidgetHistory, ZOOM_IN, ZOOM_OUT, dataWindowSections, decimalsFor, decodeState, encodeState, filterSymbols, fmtChange, fmtPrice, indicatorLedger, legacyWidgetState, loadPersisted, localStorageAdapter, normalizeSession, parsePersisted, parseTimeframe, prefixedSymbol, priceStyleIcon, priceStyleLabel, resolveIndicators, sanitizeState, savePersisted, statuslineInkOf, timeframeLabel, timeframeMs, toolShortcutHints };
@@ -1,5 +1,5 @@
1
1
  import { t as Millis, ad as InputSchema, ac as IndicatorMeta, P as PriceStyle, O as OHLCV, m as InputValue, k as IndicatorModel, j as MoveTarget, az as SeriesSpec, aa as Fill, B as Background, ar as PriceLine, a0 as DrawingLine, Y as DrawingBox, $ as DrawingLabel, a4 as DrawingPolyline, a1 as DrawingLinefill, a6 as DrawingTable, g as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, v as DrawingTypeKey, u as SnapMode, a2 as DrawingMode, I as IChartRenderer, R as RendererCapabilities, o as LegendActionView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, r as DataWindowReadout, n as SymbolPickerFn, aL as PaneInfo, a9 as DrawingsOption, D as Drawing, b as VelaOptions, x as AddIndicatorOptions, an as MarketSwitch, am as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-BqGeFHtp.cjs';
2
- import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-BNKtYU5V.cjs';
2
+ import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-CNmk84SH.cjs';
3
3
 
4
4
  /**
5
5
  * A strategy's broker state at ONE bar — the flat summary a host reads while a script
@@ -398,6 +398,10 @@ interface NativeIndicatorContext {
398
398
  readonly symbol: string;
399
399
  readonly timeframe: string;
400
400
  readonly live: boolean;
401
+ /** The chart's trading session (`'regular'` | `'extended'`); undefined = regular /
402
+ * no session model. A session switch reloads the market and RESTARTS the
403
+ * indicator, so this never changes within one context's lifetime. */
404
+ readonly session?: string;
401
405
  /** The canonical bar array (a live accessor — always current). */
402
406
  bars(): readonly OHLCV[];
403
407
  /** Market-data access (trades / capabilities) for data-driven natives. */
@@ -1,5 +1,5 @@
1
1
  import { t as Millis, ad as InputSchema, ac as IndicatorMeta, P as PriceStyle, O as OHLCV, m as InputValue, k as IndicatorModel, j as MoveTarget, az as SeriesSpec, aa as Fill, B as Background, ar as PriceLine, a0 as DrawingLine, Y as DrawingBox, $ as DrawingLabel, a4 as DrawingPolyline, a1 as DrawingLinefill, a6 as DrawingTable, g as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, v as DrawingTypeKey, u as SnapMode, a2 as DrawingMode, I as IChartRenderer, R as RendererCapabilities, o as LegendActionView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, r as DataWindowReadout, n as SymbolPickerFn, aL as PaneInfo, a9 as DrawingsOption, D as Drawing, b as VelaOptions, x as AddIndicatorOptions, an as MarketSwitch, am as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-BqGeFHtp.js';
2
- import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-gJjN_0eh.js';
2
+ import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-DHX4x6-r.js';
3
3
 
4
4
  /**
5
5
  * A strategy's broker state at ONE bar — the flat summary a host reads while a script
@@ -398,6 +398,10 @@ interface NativeIndicatorContext {
398
398
  readonly symbol: string;
399
399
  readonly timeframe: string;
400
400
  readonly live: boolean;
401
+ /** The chart's trading session (`'regular'` | `'extended'`); undefined = regular /
402
+ * no session model. A session switch reloads the market and RESTARTS the
403
+ * indicator, so this never changes within one context's lifetime. */
404
+ readonly session?: string;
401
405
  /** The canonical bar array (a live accessor — always current). */
402
406
  bars(): readonly OHLCV[];
403
407
  /** Market-data access (trades / capabilities) for data-driven natives. */
@@ -1,6 +1,6 @@
1
1
  import { V as VisibleRangePreset } from './options-BqGeFHtp.js';
2
- import { D as DataProvider } from './DataProvider-gJjN_0eh.js';
3
- import { S as ScriptingEngine, V as Vela } from './contributions-lPAgo9Cu.js';
2
+ import { D as DataProvider } from './DataProvider-DHX4x6-r.js';
3
+ import { S as ScriptingEngine, V as Vela } from './contributions-ciV6Neyd.js';
4
4
 
5
5
  interface RangePreset {
6
6
  /** Button label. */
@@ -1,6 +1,6 @@
1
1
  import { V as VisibleRangePreset } from './options-BqGeFHtp.cjs';
2
- import { D as DataProvider } from './DataProvider-BNKtYU5V.cjs';
3
- import { S as ScriptingEngine, V as Vela } from './contributions-D64UA74H.cjs';
2
+ import { D as DataProvider } from './DataProvider-CNmk84SH.cjs';
3
+ import { S as ScriptingEngine, V as Vela } from './contributions-DDpv7hEL.cjs';
4
4
 
5
5
  interface RangePreset {
6
6
  /** Button label. */