@luxalgo/vela 0.8.5 → 0.9.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.
Files changed (139) hide show
  1. package/dist/{DataProvider-BUdlkx9I.d.ts → DataProvider-BGlB1zSc.d.ts} +1 -1
  2. package/dist/{DataProvider-DdqMxKdl.d.cts → DataProvider-zJGp58kb.d.cts} +1 -1
  3. package/dist/{chunk-BN4DNS3O.js → chunk-4WAHYL7Y.js} +1125 -468
  4. package/dist/chunk-O6L27KRB.js +1331 -0
  5. package/dist/{chunk-WY5H4S4I.js → chunk-PZN4RS4T.js} +91 -36
  6. package/dist/{chunk-AUOB7A5Y.js → chunk-WHPQQYH7.js} +29 -13
  7. package/dist/{chunk-QYIO37K7.js → chunk-XIRSR3D6.js} +2374 -1529
  8. package/dist/{chunk-ALTGHKIM.js → chunk-YRCL3WMD.js} +510 -406
  9. package/dist/{contributions-D8xuYc9U.d.cts → contributions-BjefWpxQ.d.ts} +71 -22
  10. package/dist/{contributions-P9fBBuY1.d.ts → contributions-CpCJisTC.d.cts} +71 -22
  11. package/dist/index.cjs +4022 -2088
  12. package/dist/index.d.cts +51 -25
  13. package/dist/index.d.ts +51 -25
  14. package/dist/index.js +5 -5
  15. package/dist/{keymap-CGOz5F5f.d.ts → keymap-B9_FLhfJ.d.cts} +9 -6
  16. package/dist/{keymap-CGOz5F5f.d.cts → keymap-UolhuXH1.d.ts} +9 -6
  17. package/dist/locales/ar.cjs +2260 -0
  18. package/dist/locales/ar.d.cts +5 -0
  19. package/dist/locales/ar.d.ts +5 -0
  20. package/dist/locales/ar.js +2258 -0
  21. package/dist/locales/de.cjs +2260 -0
  22. package/dist/locales/de.d.cts +5 -0
  23. package/dist/locales/de.d.ts +5 -0
  24. package/dist/locales/de.js +2258 -0
  25. package/dist/locales/en.cjs +2260 -0
  26. package/dist/locales/en.d.cts +5 -0
  27. package/dist/locales/en.d.ts +5 -0
  28. package/dist/locales/en.js +2258 -0
  29. package/dist/locales/es.cjs +2260 -0
  30. package/dist/locales/es.d.cts +5 -0
  31. package/dist/locales/es.d.ts +5 -0
  32. package/dist/locales/es.js +2258 -0
  33. package/dist/locales/fr.cjs +2260 -0
  34. package/dist/locales/fr.d.cts +5 -0
  35. package/dist/locales/fr.d.ts +5 -0
  36. package/dist/locales/fr.js +2258 -0
  37. package/dist/locales/he.cjs +2260 -0
  38. package/dist/locales/he.d.cts +5 -0
  39. package/dist/locales/he.d.ts +5 -0
  40. package/dist/locales/he.js +2258 -0
  41. package/dist/locales/hi.cjs +2260 -0
  42. package/dist/locales/hi.d.cts +5 -0
  43. package/dist/locales/hi.d.ts +5 -0
  44. package/dist/locales/hi.js +2258 -0
  45. package/dist/locales/id.cjs +2260 -0
  46. package/dist/locales/id.d.cts +5 -0
  47. package/dist/locales/id.d.ts +5 -0
  48. package/dist/locales/id.js +2258 -0
  49. package/dist/locales/it.cjs +2260 -0
  50. package/dist/locales/it.d.cts +5 -0
  51. package/dist/locales/it.d.ts +5 -0
  52. package/dist/locales/it.js +2258 -0
  53. package/dist/locales/ja.cjs +2260 -0
  54. package/dist/locales/ja.d.cts +5 -0
  55. package/dist/locales/ja.d.ts +5 -0
  56. package/dist/locales/ja.js +2258 -0
  57. package/dist/locales/ko.cjs +2260 -0
  58. package/dist/locales/ko.d.cts +5 -0
  59. package/dist/locales/ko.d.ts +5 -0
  60. package/dist/locales/ko.js +2258 -0
  61. package/dist/locales/ms.cjs +2260 -0
  62. package/dist/locales/ms.d.cts +5 -0
  63. package/dist/locales/ms.d.ts +5 -0
  64. package/dist/locales/ms.js +2258 -0
  65. package/dist/locales/pl.cjs +2260 -0
  66. package/dist/locales/pl.d.cts +5 -0
  67. package/dist/locales/pl.d.ts +5 -0
  68. package/dist/locales/pl.js +2258 -0
  69. package/dist/locales/pt-BR.cjs +2260 -0
  70. package/dist/locales/pt-BR.d.cts +5 -0
  71. package/dist/locales/pt-BR.d.ts +5 -0
  72. package/dist/locales/pt-BR.js +2258 -0
  73. package/dist/locales/ru.cjs +2260 -0
  74. package/dist/locales/ru.d.cts +5 -0
  75. package/dist/locales/ru.d.ts +5 -0
  76. package/dist/locales/ru.js +2258 -0
  77. package/dist/locales/th.cjs +2260 -0
  78. package/dist/locales/th.d.cts +5 -0
  79. package/dist/locales/th.d.ts +5 -0
  80. package/dist/locales/th.js +2258 -0
  81. package/dist/locales/tr.cjs +2260 -0
  82. package/dist/locales/tr.d.cts +5 -0
  83. package/dist/locales/tr.d.ts +5 -0
  84. package/dist/locales/tr.js +2258 -0
  85. package/dist/locales/vi.cjs +2260 -0
  86. package/dist/locales/vi.d.cts +5 -0
  87. package/dist/locales/vi.d.ts +5 -0
  88. package/dist/locales/vi.js +2258 -0
  89. package/dist/locales/zh-Hans.cjs +2260 -0
  90. package/dist/locales/zh-Hans.d.cts +5 -0
  91. package/dist/locales/zh-Hans.d.ts +5 -0
  92. package/dist/locales/zh-Hans.js +2258 -0
  93. package/dist/locales/zh-Hant.cjs +2260 -0
  94. package/dist/locales/zh-Hant.d.cts +5 -0
  95. package/dist/locales/zh-Hant.d.ts +5 -0
  96. package/dist/locales/zh-Hant.js +2258 -0
  97. package/dist/{options-BI5Tp32b.d.cts → options-CpegHml_.d.cts} +74 -9
  98. package/dist/{options-BI5Tp32b.d.ts → options-D4L3_hv_.d.ts} +74 -9
  99. package/dist/{plugin-D5Z-Q6TX.d.ts → plugin-DTmulpKM.d.ts} +81 -35
  100. package/dist/{plugin-B8F1nor8.d.cts → plugin-DpNp4aBi.d.cts} +81 -35
  101. package/dist/plugin.cjs +1267 -319
  102. package/dist/plugin.d.cts +7 -6
  103. package/dist/plugin.d.ts +7 -6
  104. package/dist/plugin.js +3 -3
  105. package/dist/providers/binance.cjs +1 -1
  106. package/dist/providers/binance.d.cts +3 -2
  107. package/dist/providers/binance.d.ts +3 -2
  108. package/dist/providers/binance.js +1 -1
  109. package/dist/providers/coinbase.cjs +1 -1
  110. package/dist/providers/coinbase.d.cts +3 -2
  111. package/dist/providers/coinbase.d.ts +3 -2
  112. package/dist/providers/coinbase.js +1 -1
  113. package/dist/providers/hyperliquid.cjs +1 -1
  114. package/dist/providers/hyperliquid.d.cts +3 -2
  115. package/dist/providers/hyperliquid.d.ts +3 -2
  116. package/dist/providers/hyperliquid.js +1 -1
  117. package/dist/{side-panel-_2UK1gSm.d.ts → side-panel-BqUnpfev.d.cts} +13 -2
  118. package/dist/{side-panel-_2UK1gSm.d.cts → side-panel-CYmH1Opm.d.ts} +13 -2
  119. package/dist/{statusline-model-CTwtDIca.d.cts → statusline-model-C3yIY9WE.d.ts} +11 -3
  120. package/dist/{statusline-model-D9kWC9X5.d.ts → statusline-model-pGs3Xvp-.d.cts} +11 -3
  121. package/dist/types-BlrXRLIR.d.cts +2515 -0
  122. package/dist/types-BlrXRLIR.d.ts +2515 -0
  123. package/dist/ui.cjs +1008 -56
  124. package/dist/ui.d.cts +21 -2
  125. package/dist/ui.d.ts +21 -2
  126. package/dist/ui.js +3 -3
  127. package/dist/vela.global.js +3987 -2055
  128. package/dist/vela.global.min.js +57 -57
  129. package/dist/widget.cjs +5330 -2699
  130. package/dist/widget.d.cts +100 -26
  131. package/dist/widget.d.ts +100 -26
  132. package/dist/widget.js +19 -11
  133. package/dist/workspace.cjs +5319 -2706
  134. package/dist/workspace.d.cts +63 -15
  135. package/dist/workspace.d.ts +63 -15
  136. package/dist/workspace.js +6 -6
  137. package/package.json +9 -2
  138. package/dist/chunk-I2U4NZD2.js +0 -340
  139. /package/dist/{chunk-W5YPYZ2P.js → chunk-3EYCX2L4.js} +0 -0
package/dist/ui.cjs CHANGED
@@ -186,7 +186,7 @@ var STATIC_CSS = `
186
186
  ${STATIC_DECLS}
187
187
  font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
188
188
  box-sizing: border-box;
189
- /* Chrome text (titles, buttons, menus, readouts) is UI, not copy \u2014 never selectable. */
189
+ /* Chrome text (titles, buttons, menus, readouts) is UI, not copy — never selectable. */
190
190
  user-select: none;
191
191
  -webkit-user-select: none;
192
192
  }
@@ -195,7 +195,7 @@ ${STATIC_DECLS}
195
195
  .vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
196
196
  /* iOS Safari zooms the page when a focused field is under 16px, and often
197
197
  keeps that zoom after the field blurs or a dialog closes. The shell's
198
- mobile size class is the honest gate \u2014 not a media query \u2014 so an embedded
198
+ mobile size class is the honest gate — not a media query — so an embedded
199
199
  chart on a wide desktop still gets the rule when it is in the phone layout.
200
200
  Scoped to the kit root so a host page's own [data-layout] is never touched. */
201
201
  .vela-ui[data-layout='mobile'] :is(input, textarea, select) { font-size: 16px; }
@@ -383,7 +383,7 @@ registerIcon(
383
383
  "send-back",
384
384
  svg24('<rect x="8.5" y="8.5" width="7" height="7" rx="1.5" fill="currentColor" stroke="none" opacity="0.35"/><path d="M4.5 10.5V6a1.5 1.5 0 0 1 1.5-1.5h4.5"/><path d="M19.5 13.5V18a1.5 1.5 0 0 1-1.5 1.5h-4.5"/><rect x="8.5" y="8.5" width="7" height="7" rx="1.5"/>')
385
385
  );
386
- registerIcon("r-squared", '<svg viewBox="0 0 24 24" width="1em" height="1em" fill="currentColor" stroke="none"><text x="2.5" y="17.5" font-size="14" font-family="serif">R\xB2</text></svg>');
386
+ registerIcon("r-squared", '<svg viewBox="0 0 24 24" width="1em" height="1em" fill="currentColor" stroke="none"><text x="2.5" y="17.5" font-size="14" font-family="serif">R²</text></svg>');
387
387
  registerIcon("bands", svg24('<path d="M3 6h18"/><path d="M3 18h18"/><path d="M3 12h18" stroke-dasharray="3 3"/>'));
388
388
  registerIcon("dedekind", svg24('<path d="M2 20h20"/><path d="M4 20a8 8 0 0 1 16 0"/><path d="M8 20a4 4 0 0 1 8 0"/><path d="M12 4v16"/>'));
389
389
  registerIcon("sonic", svg24('<circle cx="15" cy="12" r="4"/><circle cx="12" cy="12" r="2.5"/><path d="M8 5v14"/>'));
@@ -398,14 +398,14 @@ function iconEl(id, doc = document) {
398
398
  if (svg) span.innerHTML = svg;
399
399
  return span;
400
400
  }
401
- function runMachine(machine5, props, render) {
401
+ function runMachine(machine5, props, render2) {
402
402
  const m = new vanilla.VanillaMachine(machine5, props);
403
- const unsub = m.subscribe(render);
403
+ const unsub = m.subscribe(render2);
404
404
  m.start();
405
- render(m.service);
405
+ render2(m.service);
406
406
  return {
407
407
  service: m.service,
408
- flush: () => render(m.service),
408
+ flush: () => render2(m.service),
409
409
  stop: () => {
410
410
  unsub();
411
411
  m.stop();
@@ -428,6 +428,896 @@ function announceSurface(el, open2, kind, trigger = null) {
428
428
  );
429
429
  }
430
430
 
431
+ // src/core/i18n/message.ts
432
+ function msg(id, defaultMessage) {
433
+ return Object.freeze({ id, defaultMessage });
434
+ }
435
+
436
+ // src/core/format/numbers.ts
437
+ var BIDI_MARKS = /[‎‏؜]/g;
438
+ function stripBidiMarks(text) {
439
+ return text.replace(BIDI_MARKS, "");
440
+ }
441
+ var numberFormats = /* @__PURE__ */ new Map();
442
+ function numberFormat(settings, options) {
443
+ const key = `${settings.locale}|${settings.numberingSystem}|${JSON.stringify(options)}`;
444
+ let nf = numberFormats.get(key);
445
+ if (!nf) {
446
+ try {
447
+ nf = new Intl.NumberFormat(settings.locale, { ...options, numberingSystem: settings.numberingSystem });
448
+ } catch {
449
+ nf = new Intl.NumberFormat("en", options);
450
+ }
451
+ numberFormats.set(key, nf);
452
+ }
453
+ return nf;
454
+ }
455
+ var symbolCache = /* @__PURE__ */ new Map();
456
+ function numberSymbols(settings) {
457
+ const key = `${settings.locale}|${settings.numberingSystem}`;
458
+ let sym = symbolCache.get(key);
459
+ if (sym) return sym;
460
+ const parts = numberFormat(settings, { useGrouping: false, minimumFractionDigits: 1, maximumFractionDigits: 1 }).formatToParts(-1.5);
461
+ const decimal = stripBidiMarks(parts.find((p) => p.type === "decimal")?.value ?? ".") || ".";
462
+ const minus = stripBidiMarks(parts.find((p) => p.type === "minusSign")?.value ?? "-") || "-";
463
+ const plain = numberFormat(settings, { useGrouping: false, maximumFractionDigits: 0 });
464
+ const digits = Array.from({ length: 10 }, (_, d) => stripBidiMarks(plain.format(d)));
465
+ const ascii = decimal === "." && minus === "-" && digits.every((d, i) => d === String(i));
466
+ sym = { ascii, decimal, minus, digits };
467
+ symbolCache.set(key, sym);
468
+ return sym;
469
+ }
470
+ function localizeAscii(text, settings) {
471
+ const sym = numberSymbols(settings);
472
+ if (sym.ascii) return text;
473
+ let out = "";
474
+ for (const ch of text) {
475
+ const code = ch.charCodeAt(0);
476
+ if (code >= 48 && code <= 57) out += sym.digits[code - 48];
477
+ else if (ch === ".") out += sym.decimal;
478
+ else if (ch === "-") out += sym.minus;
479
+ else out += ch;
480
+ }
481
+ return out;
482
+ }
483
+ function localizeDigits(text, settings) {
484
+ const sym = numberSymbols(settings);
485
+ if (sym.digits.every((d, i) => d === String(i))) return text;
486
+ return text.replace(/[0-9]/g, (d) => sym.digits[Number(d)]);
487
+ }
488
+ var clampDecimals = (d) => Number.isFinite(d) ? Math.max(0, Math.min(20, Math.trunc(d))) : 2;
489
+ function formatFixed(value, decimals, settings) {
490
+ return localizeAscii(value.toFixed(clampDecimals(decimals)), settings);
491
+ }
492
+ function formatGrouped(value, decimals, settings) {
493
+ const d = clampDecimals(decimals);
494
+ return stripBidiMarks(numberFormat(settings, { minimumFractionDigits: d, maximumFractionDigits: d }).format(value));
495
+ }
496
+ function formatMessageNumber(value, settings, style) {
497
+ if (style === "integer") return stripBidiMarks(numberFormat(settings, { maximumFractionDigits: 0 }).format(value));
498
+ if (style === "percent") return stripBidiMarks(numberFormat(settings, { style: "percent" }).format(value));
499
+ return stripBidiMarks(numberFormat(settings, {}).format(value));
500
+ }
501
+ function formatPercent(value, options, settings) {
502
+ const d = clampDecimals(options?.decimals ?? 2);
503
+ const signed = options?.signed ?? true;
504
+ if (settings.english) {
505
+ const text = signed ? `${value >= 0 ? "+" : "-"}${Math.abs(value).toFixed(d)}%` : `${value.toFixed(d)}%`;
506
+ return localizeAscii(text, settings);
507
+ }
508
+ const nf = numberFormat(settings, { style: "percent", minimumFractionDigits: d, maximumFractionDigits: d, signDisplay: signed ? "always" : "auto" });
509
+ return stripBidiMarks(nf.format(value / 100));
510
+ }
511
+ function formatPrice(value, options, settings) {
512
+ const mode = options.mode ?? "price";
513
+ if (mode === "percent") return formatPercent(value, { decimals: options.decimals, signed: true }, settings);
514
+ if (mode === "indexed" || !options.grouping) return formatFixed(value, options.decimals, settings);
515
+ return formatGrouped(value, options.decimals, settings);
516
+ }
517
+ var COMPACT_STEPS = [
518
+ [1e9, "B"],
519
+ [1e6, "M"],
520
+ [1e3, "K"]
521
+ ];
522
+ function formatCompact(value, options, settings) {
523
+ const d = clampDecimals(options?.decimals ?? 2);
524
+ const trim = options?.trimZeros ?? true;
525
+ if (settings.english) {
526
+ const a = Math.abs(value);
527
+ let div = 1;
528
+ let suffix = "";
529
+ for (const [step, s] of COMPACT_STEPS) {
530
+ if (a >= step) {
531
+ div = step;
532
+ suffix = s;
533
+ break;
534
+ }
535
+ }
536
+ const scaled = value / div;
537
+ const text = trim ? Number(scaled.toFixed(d)).toString() : scaled.toFixed(d);
538
+ return localizeAscii(text, settings) + suffix;
539
+ }
540
+ const nf = numberFormat(settings, { notation: "compact", compactDisplay: "short", maximumFractionDigits: d, minimumFractionDigits: trim ? 0 : d });
541
+ return stripBidiMarks(nf.format(value));
542
+ }
543
+
544
+ // src/core/format/dates.ts
545
+ var EN_MONTHS_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
546
+ var EN_WEEKDAYS_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
547
+ var pad2 = (n) => n < 10 ? `0${n}` : String(n);
548
+ var DAY_MS = 864e5;
549
+ var zoneParsers = /* @__PURE__ */ new Map();
550
+ function zoneOffsetMs(ms, timeZone) {
551
+ if (!timeZone || timeZone === "UTC") return 0;
552
+ let dtf = zoneParsers.get(timeZone);
553
+ if (dtf === void 0) {
554
+ try {
555
+ dtf = new Intl.DateTimeFormat("en-US", {
556
+ timeZone,
557
+ hourCycle: "h23",
558
+ year: "numeric",
559
+ month: "2-digit",
560
+ day: "2-digit",
561
+ hour: "2-digit",
562
+ minute: "2-digit",
563
+ second: "2-digit"
564
+ });
565
+ } catch {
566
+ dtf = null;
567
+ }
568
+ zoneParsers.set(timeZone, dtf);
569
+ }
570
+ if (!dtf) return 0;
571
+ try {
572
+ const parts = dtf.formatToParts(new Date(ms));
573
+ const get = (t) => Number(parts.find((p) => p.type === t)?.value);
574
+ const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
575
+ return asUTC - ms;
576
+ } catch {
577
+ return 0;
578
+ }
579
+ }
580
+ var dateFormats = /* @__PURE__ */ new Map();
581
+ function dateFormat(settings, options) {
582
+ const key = `${settings.locale}|${settings.numberingSystem}|${JSON.stringify(options)}`;
583
+ let dtf = dateFormats.get(key);
584
+ if (!dtf) {
585
+ const full = { ...options, timeZone: "UTC", calendar: "gregory", numberingSystem: settings.numberingSystem };
586
+ try {
587
+ dtf = new Intl.DateTimeFormat(settings.locale, full);
588
+ } catch {
589
+ dtf = new Intl.DateTimeFormat("en", { ...options, timeZone: "UTC", calendar: "gregory" });
590
+ }
591
+ dateFormats.set(key, dtf);
592
+ }
593
+ return dtf;
594
+ }
595
+ function clockOptions(settings) {
596
+ if (settings.hourCycle === "h23") return { hour: "2-digit", minute: "2-digit", hourCycle: "h23" };
597
+ if (settings.hourCycle === "h12") return { hour: "numeric", minute: "2-digit", hourCycle: "h12" };
598
+ return { hour: "numeric", minute: "2-digit" };
599
+ }
600
+ function englishClock(wall, settings) {
601
+ if (settings.hourCycle === "h23") return localizeAscii(`${pad2(wall.getUTCHours())}:${pad2(wall.getUTCMinutes())}`, settings);
602
+ return stripBidiMarks(dateFormat(settings, clockOptions(settings)).format(wall));
603
+ }
604
+ var TICK_OPTIONS = {
605
+ year: { year: "numeric" },
606
+ month: { month: "short" },
607
+ day: { month: "short", day: "numeric" }
608
+ };
609
+ var isClockUnit = (unit) => unit === "hour" || unit === "minute" || unit === "second";
610
+ function formatTimeTick(ms, options, settings) {
611
+ const wall = new Date(ms + (options.offsetMs ?? 0));
612
+ const unit = options.unit;
613
+ if (settings.english) {
614
+ if (isClockUnit(unit)) return englishClock(wall, settings);
615
+ const month = EN_MONTHS_SHORT[wall.getUTCMonth()];
616
+ if (unit === "year") return localizeAscii(String(wall.getUTCFullYear()), settings);
617
+ if (unit === "month") return month;
618
+ return localizeAscii(`${month} ${wall.getUTCDate()}`, settings);
619
+ }
620
+ const opts = isClockUnit(unit) ? clockOptions(settings) : TICK_OPTIONS[unit];
621
+ return stripBidiMarks(dateFormat(settings, opts).format(wall));
622
+ }
623
+ function formatTimeStamp(ms, options, settings) {
624
+ const wall = new Date(ms + zoneOffsetMs(ms, options?.timeZone ?? "UTC"));
625
+ const withTime = options?.withTime ?? true;
626
+ if (settings.english) {
627
+ const date = localizeAscii(
628
+ `${EN_WEEKDAYS_SHORT[wall.getUTCDay()]} ${wall.getUTCDate()} ${EN_MONTHS_SHORT[wall.getUTCMonth()]} '${pad2(wall.getUTCFullYear() % 100)}`,
629
+ settings
630
+ );
631
+ return withTime ? `${date} ${englishClock(wall, settings)}` : date;
632
+ }
633
+ const opts = { weekday: "short", day: "numeric", month: "short", year: "2-digit", ...withTime ? clockOptions(settings) : {} };
634
+ return stripBidiMarks(dateFormat(settings, opts).format(wall));
635
+ }
636
+ function countdownText(ms) {
637
+ const total = Math.max(0, Math.ceil(ms / 1e3));
638
+ const s = total % 60;
639
+ const m = Math.floor(total / 60) % 60;
640
+ const h = Math.floor(total / 3600);
641
+ const pad = (v) => String(v).padStart(2, "0");
642
+ return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
643
+ }
644
+ function spanParts(ms) {
645
+ const m = Math.abs(ms);
646
+ const HR = 36e5;
647
+ const MIN = 6e4;
648
+ if (m >= DAY_MS) {
649
+ const d = Math.floor(m / DAY_MS);
650
+ const h = Math.round(m % DAY_MS / HR);
651
+ return h ? [[d, "day"], [h, "hour"]] : [[d, "day"]];
652
+ }
653
+ if (m >= HR) {
654
+ const h = Math.floor(m / HR);
655
+ const mn = Math.round(m % HR / MIN);
656
+ return mn ? [[h, "hour"], [mn, "minute"]] : [[h, "hour"]];
657
+ }
658
+ if (m >= MIN) return [[Math.round(m / MIN), "minute"]];
659
+ return [[Math.round(m / 1e3), "second"]];
660
+ }
661
+ var EN_UNIT = { day: "d", hour: "h", minute: "m", second: "s" };
662
+ function formatDuration(ms, options, settings) {
663
+ if ((options?.style ?? "countdown") === "countdown") return localizeDigits(countdownText(ms), settings);
664
+ const parts = spanParts(ms);
665
+ if (settings.english) return localizeDigits(parts.map(([n, u]) => `${n}${EN_UNIT[u]}`).join(" "), settings);
666
+ return parts.map(([n, unit]) => stripBidiMarks(numberFormat(settings, { style: "unit", unit, unitDisplay: "narrow" }).format(n))).join(" ");
667
+ }
668
+ var clockFormats = /* @__PURE__ */ new Map();
669
+ function formatClock(ms, options, settings) {
670
+ const timeZone = options?.timeZone ?? "UTC";
671
+ const seconds = options?.seconds ?? true;
672
+ const english2 = settings.english && settings.hourCycle === "h23";
673
+ const key = `${english2 ? "en-GB" : settings.locale}|${settings.numberingSystem}|${settings.hourCycle}|${timeZone}|${seconds}`;
674
+ let dtf = clockFormats.get(key);
675
+ if (dtf === void 0) {
676
+ const base = { hour: "2-digit", minute: "2-digit", ...seconds ? { second: "2-digit" } : {}, timeZone };
677
+ try {
678
+ dtf = english2 ? new Intl.DateTimeFormat("en-GB", { ...base, hour12: false }) : new Intl.DateTimeFormat(settings.locale, {
679
+ ...base,
680
+ calendar: "gregory",
681
+ numberingSystem: settings.numberingSystem,
682
+ ...settings.hourCycle === "locale" ? {} : { hourCycle: settings.hourCycle }
683
+ });
684
+ } catch {
685
+ dtf = null;
686
+ }
687
+ clockFormats.set(key, dtf);
688
+ }
689
+ if (!dtf) return "";
690
+ try {
691
+ return stripBidiMarks(dtf.format(new Date(ms)));
692
+ } catch {
693
+ return "";
694
+ }
695
+ }
696
+
697
+ // src/core/format/formatters.ts
698
+ function builtinFormatters(settings) {
699
+ return {
700
+ price: (value, options) => formatPrice(value, options, settings),
701
+ percent: (value, options) => formatPercent(value, options, settings),
702
+ compact: (value, options) => formatCompact(value, options, settings),
703
+ timeTick: (ms, options) => formatTimeTick(ms, options, settings),
704
+ timeStamp: (ms, options) => formatTimeStamp(ms, options, settings),
705
+ duration: (ms, options) => formatDuration(ms, options, settings),
706
+ clock: (ms, options) => formatClock(ms, options, settings)
707
+ };
708
+ }
709
+ var warned = false;
710
+ function viaHost(host, value, ctx, fallback) {
711
+ try {
712
+ const out = host(value, ctx);
713
+ if (typeof out === "string") return out;
714
+ } catch (err) {
715
+ if (!warned) {
716
+ warned = true;
717
+ console.warn("[vela] a host formatter threw; the built-in formatting is used instead.", err);
718
+ }
719
+ }
720
+ return fallback();
721
+ }
722
+ function resolveFormatters(settings, overrides) {
723
+ const base = builtinFormatters(settings);
724
+ if (!overrides) return base;
725
+ const ctx = (surface, defaultSurface, timeZone, fallback) => ({
726
+ locale: settings.locale,
727
+ timeZone: timeZone ?? "UTC",
728
+ surface: surface ?? defaultSurface,
729
+ fallback
730
+ });
731
+ const o = overrides;
732
+ return {
733
+ price(value, options) {
734
+ const fallback = () => base.price(value, options);
735
+ if (typeof o.price !== "function") return fallback();
736
+ return viaHost(o.price, value, { ...ctx(options.surface, "priceAxis", void 0, fallback), decimals: options.decimals, mintick: options.mintick, mode: options.mode ?? "price", paneId: options.paneId, grouping: options.grouping ?? false }, fallback);
737
+ },
738
+ percent(value, options) {
739
+ const fallback = () => base.percent(value, options);
740
+ if (typeof o.percent !== "function") return fallback();
741
+ return viaHost(o.percent, value, { ...ctx(options?.surface, "statusLine", void 0, fallback), decimals: options?.decimals ?? 2, signed: options?.signed ?? true }, fallback);
742
+ },
743
+ compact(value, options) {
744
+ const fallback = () => base.compact(value, options);
745
+ if (typeof o.compact !== "function") return fallback();
746
+ return viaHost(o.compact, value, { ...ctx(options?.surface, "priceAxis", void 0, fallback), decimals: options?.decimals ?? 2, trimZeros: options?.trimZeros ?? true }, fallback);
747
+ },
748
+ timeTick(ms, options) {
749
+ const fallback = () => base.timeTick(ms, options);
750
+ if (typeof o.timeTick !== "function") return fallback();
751
+ return viaHost(o.timeTick, ms, { ...ctx(options.surface, "timeAxis", options.timeZone, fallback), unit: options.unit, major: options.major, offsetMs: options.offsetMs ?? 0 }, fallback);
752
+ },
753
+ timeStamp(ms, options) {
754
+ const fallback = () => base.timeStamp(ms, options);
755
+ if (typeof o.timeStamp !== "function") return fallback();
756
+ return viaHost(o.timeStamp, ms, { ...ctx(options?.surface, "crosshair", options?.timeZone, fallback), withTime: options?.withTime ?? true }, fallback);
757
+ },
758
+ duration(ms, options) {
759
+ const fallback = () => base.duration(ms, options);
760
+ if (typeof o.duration !== "function") return fallback();
761
+ return viaHost(o.duration, ms, { ...ctx(options?.surface, "lastPrice", void 0, fallback), style: options?.style ?? "countdown" }, fallback);
762
+ },
763
+ clock(ms, options) {
764
+ const fallback = () => base.clock(ms, options);
765
+ if (typeof o.clock !== "function") return fallback();
766
+ return viaHost(o.clock, ms, { ...ctx(options?.surface, "clock", options?.timeZone, fallback), seconds: options?.seconds ?? true }, fallback);
767
+ }
768
+ };
769
+ }
770
+
771
+ // src/core/i18n/icu.ts
772
+ var Parser = class {
773
+ constructor(src) {
774
+ this.src = src;
775
+ this.i = 0;
776
+ }
777
+ parse() {
778
+ const nodes = this.message(false, 0);
779
+ if (this.i < this.src.length) throw this.error("unmatched }");
780
+ return nodes;
781
+ }
782
+ error(what) {
783
+ return new SyntaxError(`[vela] message syntax: ${what} at ${this.i} in "${this.src}"`);
784
+ }
785
+ /** Text and arguments up to an unmatched `}` (left unread) or the end. */
786
+ message(inPlural, depth) {
787
+ if (depth > 8) throw this.error("nesting too deep");
788
+ const src = this.src;
789
+ const out = [];
790
+ let text = "";
791
+ const flush = () => {
792
+ if (text) out.push(text);
793
+ text = "";
794
+ };
795
+ while (this.i < src.length) {
796
+ const ch = src[this.i];
797
+ if (ch === "'") {
798
+ const next = src[this.i + 1];
799
+ if (next === "'") {
800
+ text += "'";
801
+ this.i += 2;
802
+ } else if (next === "{" || next === "}" || inPlural && next === "#") {
803
+ this.i += 1;
804
+ while (this.i < src.length) {
805
+ const c = src[this.i];
806
+ if (c === "'") {
807
+ if (src[this.i + 1] === "'") {
808
+ text += "'";
809
+ this.i += 2;
810
+ continue;
811
+ }
812
+ this.i += 1;
813
+ break;
814
+ }
815
+ text += c;
816
+ this.i += 1;
817
+ }
818
+ } else {
819
+ text += "'";
820
+ this.i += 1;
821
+ }
822
+ } else if (ch === "{") {
823
+ flush();
824
+ this.i += 1;
825
+ out.push(this.argument(depth));
826
+ } else if (ch === "}") {
827
+ break;
828
+ } else if (ch === "#" && inPlural) {
829
+ flush();
830
+ out.push({ kind: "pound" });
831
+ this.i += 1;
832
+ } else {
833
+ text += ch;
834
+ this.i += 1;
835
+ }
836
+ }
837
+ flush();
838
+ return out;
839
+ }
840
+ skipSpace() {
841
+ while (this.i < this.src.length && /\s/.test(this.src[this.i])) this.i += 1;
842
+ }
843
+ word() {
844
+ this.skipSpace();
845
+ const start = this.i;
846
+ while (this.i < this.src.length && !/[\s,{}]/.test(this.src[this.i])) this.i += 1;
847
+ return this.src.slice(start, this.i);
848
+ }
849
+ expect(ch) {
850
+ this.skipSpace();
851
+ if (this.src[this.i] !== ch) throw this.error(`expected "${ch}"`);
852
+ this.i += 1;
853
+ }
854
+ /** After a `{`: the argument through its closing `}`. */
855
+ argument(depth) {
856
+ const name = this.word();
857
+ if (!name) throw this.error("empty argument name");
858
+ this.skipSpace();
859
+ if (this.src[this.i] === "}") {
860
+ this.i += 1;
861
+ return { kind: "arg", name };
862
+ }
863
+ this.expect(",");
864
+ const type = this.word();
865
+ this.skipSpace();
866
+ if (type === "number") {
867
+ let style;
868
+ if (this.src[this.i] === ",") {
869
+ this.i += 1;
870
+ style = this.word() || void 0;
871
+ }
872
+ this.expect("}");
873
+ return { kind: "arg", name, format: "number", style };
874
+ }
875
+ if (type === "plural" || type === "selectordinal" || type === "select") {
876
+ this.expect(",");
877
+ let offset = 0;
878
+ this.skipSpace();
879
+ if (type !== "select" && this.src.startsWith("offset:", this.i)) {
880
+ this.i += "offset:".length;
881
+ offset = Number(this.word());
882
+ if (!Number.isFinite(offset)) throw this.error("bad offset");
883
+ }
884
+ const options = {};
885
+ for (; ; ) {
886
+ this.skipSpace();
887
+ if (this.src[this.i] === "}") {
888
+ this.i += 1;
889
+ break;
890
+ }
891
+ const key = this.word();
892
+ if (!key) throw this.error("expected a selector");
893
+ this.expect("{");
894
+ options[key] = this.message(type !== "select", depth + 1);
895
+ this.expect("}");
896
+ }
897
+ if (!options.other) throw this.error(`"${name}" has no "other" option`);
898
+ return type === "select" ? { kind: "select", name, options } : { kind: "plural", name, ordinal: type === "selectordinal", offset, options };
899
+ }
900
+ throw this.error(`unknown argument type "${type}"`);
901
+ }
902
+ };
903
+ var parsed = /* @__PURE__ */ new Map();
904
+ var PARSE_CACHE_CAP = 2e3;
905
+ function parseMessage(source) {
906
+ let nodes = parsed.get(source);
907
+ if (!nodes) {
908
+ nodes = new Parser(source).parse();
909
+ if (parsed.size >= PARSE_CACHE_CAP) parsed.clear();
910
+ parsed.set(source, nodes);
911
+ }
912
+ return nodes;
913
+ }
914
+ var pluralRules = /* @__PURE__ */ new Map();
915
+ function rulesFor(locale, ordinal) {
916
+ const key = `${locale}|${ordinal ? "o" : "c"}`;
917
+ let pr = pluralRules.get(key);
918
+ if (!pr) {
919
+ try {
920
+ pr = new Intl.PluralRules(locale, { type: ordinal ? "ordinal" : "cardinal" });
921
+ } catch {
922
+ pr = new Intl.PluralRules("en", { type: ordinal ? "ordinal" : "cardinal" });
923
+ }
924
+ pluralRules.set(key, pr);
925
+ }
926
+ return pr;
927
+ }
928
+ function render(nodes, values, rt, pound) {
929
+ let out = "";
930
+ for (const node of nodes) {
931
+ if (typeof node === "string") {
932
+ out += node;
933
+ continue;
934
+ }
935
+ if (node.kind === "pound") {
936
+ out += pound == null ? "#" : rt.formatNumber(pound);
937
+ continue;
938
+ }
939
+ const raw = values?.[node.name];
940
+ if (node.kind === "arg") {
941
+ if (raw === void 0) out += `{${node.name}}`;
942
+ else if (typeof raw === "number") out += rt.formatNumber(raw, node.format === "number" ? node.style : void 0);
943
+ else if (node.format === "number" && raw.trim() !== "" && Number.isFinite(Number(raw))) out += rt.formatNumber(Number(raw), node.style);
944
+ else out += raw;
945
+ continue;
946
+ }
947
+ if (node.kind === "select") {
948
+ const branch2 = node.options[String(raw)] ?? node.options.other;
949
+ out += render(branch2, values, rt, pound);
950
+ continue;
951
+ }
952
+ const n = typeof raw === "number" ? raw : Number(raw);
953
+ let branch;
954
+ if (Number.isFinite(n)) {
955
+ branch = node.options[`=${n}`] ?? node.options[rulesFor(rt.locale, node.ordinal).select(n - node.offset)];
956
+ }
957
+ out += render(branch ?? node.options.other, values, rt, Number.isFinite(n) ? n - node.offset : null);
958
+ }
959
+ return out;
960
+ }
961
+ function formatMessage(source, values, rt) {
962
+ if (source.indexOf("{") < 0 && source.indexOf("'") < 0) return source;
963
+ return render(parseMessage(source), values, rt, null);
964
+ }
965
+
966
+ // src/core/format/calendar.ts
967
+ var EN_MONTHS_LONG = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
968
+ var EN_MONTHS_SHORT_PICKER = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
969
+ var EN_WEEKDAYS_MIN = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
970
+ function monthNames(settings, width) {
971
+ if (settings.english) return [...width === "long" ? EN_MONTHS_LONG : EN_MONTHS_SHORT_PICKER];
972
+ const dtf = dateFormat(settings, { month: width });
973
+ return Array.from({ length: 12 }, (_, m) => stripBidiMarks(dtf.format(Date.UTC(2e3, m, 15))));
974
+ }
975
+ function weekdayNames(settings) {
976
+ if (settings.english) return [...EN_WEEKDAYS_MIN];
977
+ const dtf = dateFormat(settings, { weekday: "short" });
978
+ return Array.from({ length: 7 }, (_, d) => stripBidiMarks(dtf.format(Date.UTC(2e3, 0, 2 + d))));
979
+ }
980
+ function yearLabel(settings, year) {
981
+ if (settings.english) return localizeAscii(String(year), settings);
982
+ return stripBidiMarks(dateFormat(settings, { year: "numeric" }).format(Date.UTC(year, 6, 1)));
983
+ }
984
+ function plainInteger(settings, n) {
985
+ return localizeAscii(String(Math.trunc(n)), settings);
986
+ }
987
+ var yearFirstCache = /* @__PURE__ */ new Map();
988
+ function yearBeforeMonth(settings) {
989
+ if (settings.english) return false;
990
+ const key = `${settings.locale}|${settings.numberingSystem}`;
991
+ let first = yearFirstCache.get(key);
992
+ if (first === void 0) {
993
+ const types = dateFormat(settings, { year: "numeric", month: "long" }).formatToParts(Date.UTC(2026, 8, 15)).map((p) => p.type);
994
+ first = types.indexOf("year") < types.indexOf("month");
995
+ yearFirstCache.set(key, first);
996
+ }
997
+ return first;
998
+ }
999
+ var SUNDAY_REGIONS = /* @__PURE__ */ new Set([
1000
+ "AG",
1001
+ "AS",
1002
+ "BD",
1003
+ "BR",
1004
+ "BS",
1005
+ "BT",
1006
+ "BW",
1007
+ "BZ",
1008
+ "CA",
1009
+ "CN",
1010
+ "CO",
1011
+ "DM",
1012
+ "DO",
1013
+ "ET",
1014
+ "GT",
1015
+ "GU",
1016
+ "HK",
1017
+ "HN",
1018
+ "ID",
1019
+ "IL",
1020
+ "IN",
1021
+ "JM",
1022
+ "JP",
1023
+ "KE",
1024
+ "KH",
1025
+ "KR",
1026
+ "LA",
1027
+ "MH",
1028
+ "MM",
1029
+ "MO",
1030
+ "MT",
1031
+ "MX",
1032
+ "MZ",
1033
+ "NI",
1034
+ "NP",
1035
+ "PA",
1036
+ "PE",
1037
+ "PH",
1038
+ "PK",
1039
+ "PR",
1040
+ "PT",
1041
+ "PY",
1042
+ "SA",
1043
+ "SG",
1044
+ "SV",
1045
+ "TH",
1046
+ "TT",
1047
+ "TW",
1048
+ "UM",
1049
+ "US",
1050
+ "VE",
1051
+ "VI",
1052
+ "WS",
1053
+ "YE",
1054
+ "ZA",
1055
+ "ZW"
1056
+ ]);
1057
+ var SATURDAY_REGIONS = /* @__PURE__ */ new Set(["AE", "AF", "BH", "DJ", "DZ", "EG", "IQ", "IR", "JO", "KW", "LY", "OM", "QA", "SD", "SY"]);
1058
+ function localeWeekStart(locale) {
1059
+ let loc;
1060
+ try {
1061
+ loc = new Intl.Locale(locale);
1062
+ } catch {
1063
+ return 1;
1064
+ }
1065
+ try {
1066
+ const wl = loc;
1067
+ const info = typeof wl.getWeekInfo === "function" ? wl.getWeekInfo() : wl.weekInfo;
1068
+ const firstDay = info?.firstDay;
1069
+ if (typeof firstDay === "number") return firstDay === 7 ? 0 : firstDay === 6 ? 6 : 1;
1070
+ } catch {
1071
+ }
1072
+ let region = loc.region;
1073
+ if (!region) {
1074
+ try {
1075
+ region = loc.maximize().region;
1076
+ } catch {
1077
+ region = void 0;
1078
+ }
1079
+ }
1080
+ if (region && SUNDAY_REGIONS.has(region)) return 0;
1081
+ if (region && SATURDAY_REGIONS.has(region)) return 6;
1082
+ return 1;
1083
+ }
1084
+
1085
+ // src/core/i18n/locale.ts
1086
+ function canonicalLocale(tag) {
1087
+ const raw = typeof tag === "string" ? tag.trim() : "";
1088
+ if (!raw) return "en";
1089
+ try {
1090
+ return Intl.getCanonicalLocales(raw)[0] ?? raw;
1091
+ } catch {
1092
+ return raw;
1093
+ }
1094
+ }
1095
+ function parseLocale(tag) {
1096
+ try {
1097
+ return new Intl.Locale(tag);
1098
+ } catch {
1099
+ return null;
1100
+ }
1101
+ }
1102
+ function languageOf(tag) {
1103
+ return (parseLocale(tag)?.language ?? tag.split(/[-_]/)[0] ?? tag).toLowerCase();
1104
+ }
1105
+ function fallbackChain(tag) {
1106
+ const out = [];
1107
+ const add = (t) => {
1108
+ if (t && !out.includes(t)) out.push(t);
1109
+ };
1110
+ add(tag);
1111
+ const loc = parseLocale(tag);
1112
+ if (loc) {
1113
+ const base = loc.baseName;
1114
+ add(base);
1115
+ const parts = base.split("-");
1116
+ let impliedScript;
1117
+ if (!loc.script) {
1118
+ try {
1119
+ impliedScript = loc.maximize().script;
1120
+ } catch {
1121
+ impliedScript = void 0;
1122
+ }
1123
+ }
1124
+ for (let i = parts.length - 1; i >= 1; i -= 1) {
1125
+ if (i === 1 && impliedScript && loc.region) add(`${loc.language}-${impliedScript}`);
1126
+ add(parts.slice(0, i).join("-"));
1127
+ }
1128
+ if (impliedScript && !loc.region) add(`${loc.language}-${impliedScript}`);
1129
+ try {
1130
+ const usual = new Intl.Locale(loc.language).maximize();
1131
+ if (usual.region && usual.script === (loc.script ?? impliedScript)) add(`${loc.language}-${usual.region}`);
1132
+ } catch {
1133
+ }
1134
+ }
1135
+ add("en");
1136
+ return out;
1137
+ }
1138
+ var RTL_SCRIPTS = /* @__PURE__ */ new Set(["Arab", "Hebr", "Thaa", "Syrc", "Nkoo", "Adlm", "Rohg", "Mand", "Samr", "Yezi"]);
1139
+ var RTL_LANGUAGES = /* @__PURE__ */ new Set(["ar", "he", "iw", "fa", "ur", "ps", "sd", "ug", "yi", "dv", "ckb", "ks"]);
1140
+ function localeDirection(tag) {
1141
+ const loc = parseLocale(tag);
1142
+ if (loc) {
1143
+ try {
1144
+ const tl = loc;
1145
+ const info = typeof tl.getTextInfo === "function" ? tl.getTextInfo() : tl.textInfo;
1146
+ if (info?.direction === "rtl" || info?.direction === "ltr") return info.direction;
1147
+ } catch {
1148
+ }
1149
+ try {
1150
+ const script = loc.script ?? loc.maximize().script;
1151
+ if (script) return RTL_SCRIPTS.has(script) ? "rtl" : "ltr";
1152
+ } catch {
1153
+ }
1154
+ }
1155
+ return RTL_LANGUAGES.has(languageOf(tag)) ? "rtl" : "ltr";
1156
+ }
1157
+ var numberingCache = /* @__PURE__ */ new Map();
1158
+ function resolveNumberingSystem(tag, requested) {
1159
+ const key = `${tag}|${requested ?? ""}`;
1160
+ const cached = numberingCache.get(key);
1161
+ if (cached) return cached;
1162
+ let nu = "latn";
1163
+ try {
1164
+ if (requested === "locale") nu = new Intl.NumberFormat(tag).resolvedOptions().numberingSystem;
1165
+ else if (requested) nu = new Intl.NumberFormat(tag, { numberingSystem: requested }).resolvedOptions().numberingSystem;
1166
+ else {
1167
+ const ext = parseLocale(tag)?.numberingSystem;
1168
+ if (ext) nu = new Intl.NumberFormat(tag).resolvedOptions().numberingSystem;
1169
+ }
1170
+ } catch {
1171
+ nu = "latn";
1172
+ }
1173
+ numberingCache.set(key, nu);
1174
+ return nu;
1175
+ }
1176
+
1177
+ // src/core/i18n/registry.ts
1178
+ var entries = [];
1179
+ var version = 0;
1180
+ var merged = /* @__PURE__ */ new Map();
1181
+ function messagesFor(chain) {
1182
+ const key = chain.join("|");
1183
+ const hit = merged.get(key);
1184
+ if (hit && hit.version === version) return hit.map;
1185
+ const map = /* @__PURE__ */ new Map();
1186
+ for (let c = chain.length - 1; c >= 0; c -= 1) {
1187
+ const tag = chain[c];
1188
+ for (const e of entries) {
1189
+ if (e.tag !== tag) continue;
1190
+ for (const [id, text] of Object.entries(e.messages)) {
1191
+ if (typeof text === "string") map.set(id, text);
1192
+ }
1193
+ }
1194
+ }
1195
+ merged.set(key, { version, map });
1196
+ return map;
1197
+ }
1198
+
1199
+ // src/core/i18n/translator.ts
1200
+ function isMessageDescriptor(m) {
1201
+ return typeof m === "object" && m !== null && typeof m.id === "string" && typeof m.defaultMessage === "string";
1202
+ }
1203
+ var warnedSyntax = false;
1204
+ function resolveArgs(args, resolve) {
1205
+ if (!args) return void 0;
1206
+ let out = null;
1207
+ for (const key of Object.keys(args)) {
1208
+ const v = args[key];
1209
+ if (typeof v === "object" && v !== null) {
1210
+ out ?? (out = { ...args });
1211
+ out[key] = resolve(v);
1212
+ }
1213
+ }
1214
+ return out ?? args;
1215
+ }
1216
+ var LocaleState = class {
1217
+ constructor(options = {}) {
1218
+ this.runtime = {
1219
+ locale: "en",
1220
+ formatNumber: (value, style) => formatMessageNumber(value, this.settings, style)
1221
+ };
1222
+ this.opts = { ...options };
1223
+ this.resolve();
1224
+ }
1225
+ /** The options as last set (unresolved). */
1226
+ get options() {
1227
+ return this.opts;
1228
+ }
1229
+ /** Whether the host chose a locale; Vela stamps `lang` only then. */
1230
+ get localeExplicit() {
1231
+ return this.opts.locale !== void 0;
1232
+ }
1233
+ /** Whether the host chose a direction; Vela stamps `dir` only then. */
1234
+ get directionExplicit() {
1235
+ return this.opts.direction !== void 0;
1236
+ }
1237
+ /** Apply a change: fields present in `next` replace (an explicit `undefined` restores
1238
+ * the default), absent fields keep their current value. */
1239
+ update(next) {
1240
+ const patch = typeof next === "string" ? { locale: next } : next;
1241
+ this.opts = { ...this.opts, ...patch };
1242
+ for (const key of Object.keys(this.opts)) {
1243
+ if (this.opts[key] === void 0) delete this.opts[key];
1244
+ }
1245
+ this.resolve();
1246
+ }
1247
+ resolve() {
1248
+ const o = this.opts;
1249
+ const locale = canonicalLocale(o.locale);
1250
+ const numberingSystem = resolveNumberingSystem(locale, o.numberingSystem);
1251
+ const hourCycle = o.hourCycle === "h12" || o.hourCycle === "locale" ? o.hourCycle : "h23";
1252
+ this.settings = { locale, numberingSystem, hourCycle, english: languageOf(locale) === "en" };
1253
+ this.chain = fallbackChain(locale);
1254
+ this.resolvedDirection = o.direction === "rtl" || o.direction === "ltr" ? o.direction : o.direction === "auto" ? localeDirection(locale) : "ltr";
1255
+ this.resolvedWeekStart = o.weekStart === 0 || o.weekStart === 1 || o.weekStart === 6 ? o.weekStart : localeWeekStart(locale);
1256
+ this.runtime.locale = locale;
1257
+ this.formatters = resolveFormatters(this.settings, o.formatters);
1258
+ }
1259
+ get locale() {
1260
+ return this.settings.locale;
1261
+ }
1262
+ get direction() {
1263
+ return this.resolvedDirection;
1264
+ }
1265
+ get numberingSystem() {
1266
+ return this.settings.numberingSystem;
1267
+ }
1268
+ get hourCycle() {
1269
+ return this.settings.hourCycle;
1270
+ }
1271
+ get weekStart() {
1272
+ return this.resolvedWeekStart;
1273
+ }
1274
+ get format() {
1275
+ return this.formatters;
1276
+ }
1277
+ /** The resolved settings the built-in formatters and calendar helpers read. */
1278
+ get formatSettings() {
1279
+ return this.settings;
1280
+ }
1281
+ t(message, args) {
1282
+ if (typeof message === "string") return message;
1283
+ if (!isMessageDescriptor(message)) return message == null ? "" : String(message);
1284
+ const { id, defaultMessage } = message;
1285
+ const bound = message.values;
1286
+ const values = resolveArgs(bound ? args ? { ...bound, ...args } : bound : args, (m) => this.t(m));
1287
+ const override = this.opts.messages?.[id];
1288
+ const source = typeof override === "string" ? override : messagesFor(this.chain).get(id) ?? defaultMessage;
1289
+ try {
1290
+ return formatMessage(source, values, this.runtime);
1291
+ } catch (err) {
1292
+ if (!warnedSyntax) {
1293
+ warnedSyntax = true;
1294
+ console.warn(`[vela] message "${id}" could not be formatted; the English text is used instead.`, err);
1295
+ }
1296
+ if (source === defaultMessage) return defaultMessage;
1297
+ try {
1298
+ return formatMessage(defaultMessage, values, this.runtime);
1299
+ } catch {
1300
+ return defaultMessage;
1301
+ }
1302
+ }
1303
+ }
1304
+ };
1305
+ var english = null;
1306
+ function englishTranslator() {
1307
+ english ?? (english = new LocaleState());
1308
+ return english;
1309
+ }
1310
+ function formatSettingsOf(i18n) {
1311
+ if (i18n instanceof LocaleState) return i18n.formatSettings;
1312
+ if (!i18n) return englishTranslator().formatSettings;
1313
+ return { locale: i18n.locale, numberingSystem: i18n.numberingSystem, hourCycle: i18n.hourCycle, english: languageOf(i18n.locale) === "en" };
1314
+ }
1315
+
1316
+ // src/core/i18n/text.ts
1317
+ function textOf(i18n, message, values) {
1318
+ return (i18n ?? englishTranslator()).t(message, values);
1319
+ }
1320
+
431
1321
  // src/ui/keymap.ts
432
1322
  var KEY_ALIASES = {
433
1323
  esc: "escape",
@@ -441,7 +1331,15 @@ var KEY_ALIASES = {
441
1331
  up: "arrowup",
442
1332
  down: "arrowdown"
443
1333
  };
444
- var MAC_GLYPHS = { meta: "\u2318", ctrl: "\u2303", alt: "\u2325", shift: "\u21E7" };
1334
+ var MAC_GLYPHS = { meta: "⌘", ctrl: "⌃", alt: "⌥", shift: "⇧" };
1335
+ var KEY_TEXT = {
1336
+ ctrl: msg("ui.keys.ctrl", "Ctrl"),
1337
+ alt: msg("ui.keys.alt", "Alt"),
1338
+ shift: msg("ui.keys.shift", "Shift"),
1339
+ meta: msg("ui.keys.meta", "Win"),
1340
+ space: msg("ui.keys.space", "Space"),
1341
+ general: msg("ui.keys.general", "General")
1342
+ };
445
1343
  function parseChord(spec, mac) {
446
1344
  const parts = spec.toLowerCase().split("+").map((p) => p.trim()).filter((p, i, a) => p !== "" || a[i - 1] === "");
447
1345
  const chord = { ctrl: false, meta: false, alt: false, shift: false, key: "" };
@@ -456,10 +1354,16 @@ function parseChord(spec, mac) {
456
1354
  }
457
1355
  return chord;
458
1356
  }
1357
+ function chordKeyOf(ev) {
1358
+ const key = String(ev.key ?? "").toLowerCase();
1359
+ if (key.length !== 1 || /^[a-z0-9]$/.test(key) || !/\p{L}/u.test(key)) return key;
1360
+ const m = /^Key([A-Z])$|^Digit([0-9])$/.exec(ev.code ?? "");
1361
+ return m ? (m[1] ?? m[2]).toLowerCase() : key;
1362
+ }
459
1363
  function eventMatches(ev, c) {
460
1364
  return ev.ctrlKey === c.ctrl && ev.metaKey === c.meta && ev.altKey === c.alt && // Shift is part of producing many printable keys ('?', '+') — only enforce it
461
1365
  // when the chord names a non-printable/letter key where shift is a real modifier.
462
- (c.key.length > 1 || /^[a-z0-9 ]$/.test(c.key) ? ev.shiftKey === c.shift : true) && ev.key.toLowerCase() === c.key;
1366
+ (c.key.length > 1 || /^[a-z0-9 ]$/.test(c.key) ? ev.shiftKey === c.shift : true) && chordKeyOf(ev) === c.key;
463
1367
  }
464
1368
  function isEditableTarget(ev) {
465
1369
  const t = ev.target;
@@ -469,13 +1373,14 @@ function isEditableTarget(ev) {
469
1373
  if (t.isContentEditable === true) return true;
470
1374
  return (typeof t.getAttribute === "function" ? t.getAttribute("role") : null) === "textbox";
471
1375
  }
472
- function displayChord(spec, mac) {
1376
+ function displayChord(spec, mac, i18n) {
473
1377
  const c = parseChord(spec, mac);
474
- const keyLabel = c.key === " " ? "Space" : c.key.length === 1 ? c.key.toUpperCase() : c.key.charAt(0).toUpperCase() + c.key.slice(1);
1378
+ const name = (m) => textOf(i18n, m);
1379
+ const keyLabel = c.key === " " ? name(KEY_TEXT.space) : c.key.length === 1 ? c.key.toUpperCase() : c.key.charAt(0).toUpperCase() + c.key.slice(1);
475
1380
  if (mac) {
476
1381
  return (c.ctrl ? MAC_GLYPHS.ctrl : "") + (c.alt ? MAC_GLYPHS.alt : "") + (c.shift ? MAC_GLYPHS.shift : "") + (c.meta ? MAC_GLYPHS.meta : "") + keyLabel;
477
1382
  }
478
- const mods = [c.ctrl && "Ctrl", c.alt && "Alt", c.shift && "Shift", c.meta && "Win"].filter(Boolean);
1383
+ const mods = [c.ctrl && name(KEY_TEXT.ctrl), c.alt && name(KEY_TEXT.alt), c.shift && name(KEY_TEXT.shift), c.meta && name(KEY_TEXT.meta)].filter(Boolean);
479
1384
  return [...mods, keyLabel].join("+");
480
1385
  }
481
1386
  var KeymapManager = class {
@@ -506,17 +1411,18 @@ var KeymapManager = class {
506
1411
  if (keys === null) this.rebinds.delete(id);
507
1412
  else this.rebinds.set(id, Array.isArray(keys) ? [...keys] : [keys]);
508
1413
  }
509
- /** Snapshot for a shortcuts help panel / rebinding UI. */
510
- bindings() {
1414
+ /** Snapshot for a shortcuts help panel / rebinding UI: labels, categories and key
1415
+ * names in `i18n`'s language (English when absent). */
1416
+ bindings(i18n) {
511
1417
  return [...this.descriptors.values()].map((d) => {
512
1418
  const keys = this.activeKeys(d);
513
1419
  return {
514
1420
  id: d.id,
515
- label: d.label,
516
- category: d.category ?? "General",
1421
+ label: textOf(i18n, d.label),
1422
+ category: textOf(i18n, d.category ?? KEY_TEXT.general),
517
1423
  scope: d.scope ?? this.baseScope,
518
1424
  keys,
519
- display: keys.map((k) => displayChord(k, this.mac))
1425
+ display: keys.map((k) => displayChord(k, this.mac, i18n))
520
1426
  };
521
1427
  });
522
1428
  }
@@ -750,7 +1656,7 @@ var MENU_CSS = `
750
1656
  .vela-menu-item .vela-icon { width: 16px; height: 16px; font-size: 16px; justify-content: center; color: var(--vela-fg-muted); }
751
1657
  .vela-menu-item .vela-menu-label { flex: 1 1 auto; }
752
1658
  .vela-menu-item .vela-menu-hint { color: var(--vela-fg-faint); font-size: var(--vela-font-size-sm); }
753
- /* Active entry: the row surface carries the selection \u2014 a stronger background wash
1659
+ /* Active entry: the row surface carries the selection — a stronger background wash
754
1660
  than the hover one, plus bright ink. Declared after the hover rule so a selected
755
1661
  row stays visibly selected while highlighted. */
756
1662
  .vela-menu-item[data-checked] {
@@ -758,7 +1664,7 @@ var MENU_CSS = `
758
1664
  color: var(--vela-fg-bright);
759
1665
  }
760
1666
  /* Checkmark mode (context/action menus): every row reserves the leading mark column so
761
- labels align; a checked row fills it with a \u2713 and brightens its ink \u2014 the row surface
1667
+ labels align; a checked row fills it with a ✓ and brightens its ink — the row surface
762
1668
  stays free for the hover wash. */
763
1669
  .vela-menu-item .vela-menu-mark {
764
1670
  width: 14px;
@@ -803,7 +1709,7 @@ var MENU_CSS = `
803
1709
  border-color: color-mix(in srgb, var(--vela-fg-bright) 40%, transparent);
804
1710
  color: var(--vela-surface);
805
1711
  }
806
- /* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
1712
+ /* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill — the
807
1713
  same control language as the settings dialog's toggles. */
808
1714
  .vela-menu-switch {
809
1715
  order: 99;
@@ -830,8 +1736,8 @@ var MENU_CSS = `
830
1736
  }
831
1737
  .vela-menu-switch.on { background: var(--vela-accent-bright, var(--vela-accent)); border-color: var(--vela-accent-bright, var(--vela-accent)); }
832
1738
  .vela-menu-switch.on::after { transform: translateX(16px); background: var(--vela-bg); }
833
- /* Favorite star (rows carrying \`favorite\`): reserved space always \u2014 it fades in on row
834
- hover instead of shifting the layout \u2014 and a starred row keeps its filled star visible.
1739
+ /* Favorite star (rows carrying \`favorite\`): reserved space always — it fades in on row
1740
+ hover instead of shifting the layout — and a starred row keeps its filled star visible.
835
1741
  Same control language as the drawing toolbar's flyout stars. */
836
1742
  .vela-menu-item .vela-menu-star {
837
1743
  order: 98;
@@ -859,6 +1765,18 @@ var MENU_CSS = `
859
1765
  }
860
1766
  .vela-menu-item[data-state='open'] { background: var(--vela-hover); }
861
1767
  `;
1768
+
1769
+ // src/ui/kit-text.ts
1770
+ var scoped = null;
1771
+ function kitTranslator() {
1772
+ return scoped;
1773
+ }
1774
+
1775
+ // src/ui/components/menu/view.ts
1776
+ var TEXT = {
1777
+ addFavorite: msg("ui.menu.addFavorite", "Add to favorites"),
1778
+ removeFavorite: msg("ui.menu.removeFavorite", "Remove from favorites")
1779
+ };
862
1780
  var Surface = class _Surface {
863
1781
  constructor(doc, opts) {
864
1782
  this.items = [];
@@ -875,6 +1793,7 @@ var Surface = class _Surface {
875
1793
  this.host = opts.host;
876
1794
  this.onSelect = opts.onSelect;
877
1795
  this.onFavorite = opts.onFavorite;
1796
+ this.i18n = opts.i18n;
878
1797
  this.checkmarks = opts.checkmarks === true;
879
1798
  this.iconBadges = opts.iconBadges === true;
880
1799
  this.positioner = doc.createElement("div");
@@ -1037,7 +1956,7 @@ var Surface = class _Surface {
1037
1956
  if (on) star.classList.add("vela-fav");
1038
1957
  star.removeAttribute("aria-hidden");
1039
1958
  star.setAttribute("role", "button");
1040
- star.setAttribute("aria-label", on ? "Remove from favorites" : "Add to favorites");
1959
+ star.setAttribute("aria-label", textOf(this.i18n, on ? TEXT.removeFavorite : TEXT.addFavorite));
1041
1960
  star.setAttribute("aria-pressed", on ? "true" : "false");
1042
1961
  for (const ev of ["pointerdown", "pointerup", "mousedown", "mouseup"]) {
1043
1962
  star.addEventListener(ev, (e) => e.stopPropagation());
@@ -1066,6 +1985,7 @@ var Surface = class _Surface {
1066
1985
  opener: li,
1067
1986
  onSelect: this.onSelect,
1068
1987
  onFavorite: this.onFavorite,
1988
+ i18n: this.i18n,
1069
1989
  checkmarks: this.checkmarks,
1070
1990
  iconBadges: this.iconBadges,
1071
1991
  id: `${this.mid}--${item.id}`
@@ -1093,6 +2013,7 @@ var Menu = class {
1093
2013
  id: opts.id,
1094
2014
  minWidth: opts.minWidth,
1095
2015
  onFavorite: opts.onFavorite,
2016
+ i18n: opts.i18n ?? kitTranslator(),
1096
2017
  checkmarks: opts.checkmarks,
1097
2018
  iconBadges: opts.iconBadges
1098
2019
  });
@@ -1508,13 +2429,13 @@ var DIALOG_CSS = `
1508
2429
  .vela-dialog-btn-primary:hover { background: var(--vela-selected-bg); color: var(--vela-selected-fg); opacity: 0.85; border-color: var(--vela-selected-bg); }
1509
2430
  ${overlayScrollbarCss(".vela-dialog-body")}
1510
2431
  /* Scrollers NESTED in a dialog (a flush body's grid, a tab pane) get the same thin
1511
- overlay bars \u2014 the two-class selectors below stay overridable by more specific
2432
+ overlay bars — the two-class selectors below stay overridable by more specific
1512
2433
  per-dialog sheets. */
1513
2434
  ${overlayScrollbarCss(".vela-dialog *")}
1514
- /* \u2500\u2500 mobile chrome: dialogs fill the shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2435
+ /* ── mobile chrome: dialogs fill the shell ─────────────────────────────────────────
1515
2436
  Inside a shell in the mobile size class ([data-layout='mobile'] on the widget root,
1516
2437
  which is position:relative) every kit dialog presents fullscreen: desktop cards are
1517
- unusable at phone widths, and the shell's bounds \u2014 not the viewport \u2014 are the honest
2438
+ unusable at phone widths, and the shell's bounds — not the viewport — are the honest
1518
2439
  "screen" for an embedded chart. */
1519
2440
  [data-layout='mobile'] .vela-dialog-backdrop { position: absolute; }
1520
2441
  [data-layout='mobile'] .vela-dialog-positioner {
@@ -1762,7 +2683,7 @@ var DRAWER_CSS = `
1762
2683
  z-index: var(--vela-z-dialog);
1763
2684
  }
1764
2685
  /* Inside a shell that declares a size class, the sheet scopes to the SHELL's bounds
1765
- (the widget root is position:relative) instead of the whole viewport \u2014 an embedded
2686
+ (the widget root is position:relative) instead of the whole viewport — an embedded
1766
2687
  chart must not curtain the host page. */
1767
2688
  [data-layout] .vela-drawer-backdrop, [data-layout] .vela-drawer-positioner { position: absolute; }
1768
2689
  .vela-drawer {
@@ -2562,7 +3483,7 @@ var NUMBER_CSS = `
2562
3483
  .vela-num input::-webkit-inner-spin-button, .vela-num input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
2563
3484
  .vela-num input:hover { border-color: var(--vela-fg-muted); }
2564
3485
  .vela-num input:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
2565
- /* The stepper gutter exists only while the steppers do (hover) \u2014 an idle field keeps
3486
+ /* The stepper gutter exists only while the steppers do (hover) — an idle field keeps
2566
3487
  its full width so long values aren't cut under an invisible arrow column. */
2567
3488
  .vela-num[data-steppers]:hover input { padding-right: 26px; }
2568
3489
  .vela-num[data-size='sm'] { width: 64px; flex: none; }
@@ -2609,10 +3530,15 @@ var NUMBER_CSS = `
2609
3530
  `;
2610
3531
 
2611
3532
  // src/ui/components/number-input/view.ts
3533
+ var TEXT2 = {
3534
+ increase: msg("ui.numberInput.increase", "Increase"),
3535
+ decrease: msg("ui.numberInput.decrease", "Decrease")
3536
+ };
2612
3537
  var NumberInput = class {
2613
3538
  constructor(opts) {
2614
3539
  const doc = document;
2615
3540
  injectStyles(NUMBER_STYLE_ID, NUMBER_CSS, doc);
3541
+ this.i18n = opts.i18n ?? kitTranslator();
2616
3542
  this.ctrl = numberInputController(opts);
2617
3543
  this.placeholderMode = opts.placeholder !== void 0;
2618
3544
  this.emptyValue = opts.emptyValue ?? opts.value;
@@ -2717,7 +3643,7 @@ var NumberInput = class {
2717
3643
  b.addEventListener("pointercancel", stop);
2718
3644
  return b;
2719
3645
  };
2720
- steps.append(mk(1, "chevron-up", "Increase"), mk(-1, "chevron-down", "Decrease"));
3646
+ steps.append(mk(1, "chevron-up", textOf(this.i18n, TEXT2.increase)), mk(-1, "chevron-down", textOf(this.i18n, TEXT2.decrease)));
2721
3647
  return steps;
2722
3648
  }
2723
3649
  };
@@ -2886,9 +3812,9 @@ function transparencyChecker(size) {
2886
3812
  return `repeating-conic-gradient(#9aa0a6 0% 25%, #d3d6da 0% 50%) 0 0 / ${size}px ${size}px`;
2887
3813
  }
2888
3814
  function colorPickerController(opts = {}) {
2889
- const parsed = splitColor(opts.color ?? ACCENT_BRIGHT);
2890
- let hex6 = parsed.hex6;
2891
- let alpha = parsed.alpha;
3815
+ const parsed2 = splitColor(opts.color ?? ACCENT_BRIGHT);
3816
+ let hex6 = parsed2.hex6;
3817
+ let alpha = parsed2.alpha;
2892
3818
  return {
2893
3819
  get hex6() {
2894
3820
  return hex6;
@@ -2920,6 +3846,10 @@ var COLOR_CSS = `
2920
3846
  `;
2921
3847
 
2922
3848
  // src/ui/components/color-picker/view.ts
3849
+ var TEXT3 = {
3850
+ opacity: msg("ui.colorPicker.opacity", "Opacity"),
3851
+ opacityPercent: msg("ui.colorPicker.opacityPercent", "Opacity (%)")
3852
+ };
2923
3853
  var PALETTE = buildPalette();
2924
3854
  var CHECKER = transparencyChecker(10);
2925
3855
  var FIELD_CHECKER = transparencyChecker(8);
@@ -2932,9 +3862,10 @@ function addRecent(hex6) {
2932
3862
  }
2933
3863
  function buildColorPicker(color, theme, onChange, options = {}) {
2934
3864
  const commit = options.commit ?? "live";
2935
- const parsed = splitColor(color);
2936
- let curHex = parsed.hex6;
2937
- let curAlpha = parsed.alpha;
3865
+ const i18n = options.i18n ?? kitTranslator();
3866
+ const parsed2 = splitColor(color);
3867
+ let curHex = parsed2.hex6;
3868
+ let curAlpha = parsed2.alpha;
2938
3869
  const root = document.createElement("div");
2939
3870
  root.style.cssText = "display:flex;flex-direction:column;gap:9px;width:236px;";
2940
3871
  const grid = document.createElement("div");
@@ -2992,7 +3923,7 @@ function buildColorPicker(color, theme, onChange, options = {}) {
2992
3923
  }
2993
3924
  };
2994
3925
  const opLabel = document.createElement("div");
2995
- opLabel.textContent = "Opacity";
3926
+ opLabel.textContent = textOf(i18n, TEXT3.opacity);
2996
3927
  opLabel.style.cssText = `font:11px ${theme.fontFamily};color:var(--vela-fg-muted);`;
2997
3928
  const opRow = document.createElement("div");
2998
3929
  opRow.style.cssText = "display:flex;align-items:center;gap:10px;";
@@ -3004,7 +3935,7 @@ function buildColorPicker(color, theme, onChange, options = {}) {
3004
3935
  const pctBox = document.createElement("input");
3005
3936
  pctBox.type = "text";
3006
3937
  pctBox.inputMode = "numeric";
3007
- pctBox.title = "Opacity (%)";
3938
+ pctBox.title = textOf(i18n, TEXT3.opacityPercent);
3008
3939
  pctBox.style.cssText = `width:46px;box-sizing:border-box;text-align:center;font:var(--vela-font-size-md) ${theme.fontFamily};color:var(--vela-fg);background:transparent;border:1px solid var(--vela-border);border-radius:5px;padding:3px 4px;outline:none;`;
3009
3940
  pctBox.addEventListener("focus", () => {
3010
3941
  pctBox.style.borderColor = "var(--vela-border-strong)";
@@ -3099,6 +4030,7 @@ var ColorField = class {
3099
4030
  this.onVal = opts.onVal;
3100
4031
  this.popoverOpts = opts.popover;
3101
4032
  this.commit = opts.commit;
4033
+ this.i18n = opts.i18n ?? kitTranslator();
3102
4034
  const trigger = document.createElement("button");
3103
4035
  trigger.type = "button";
3104
4036
  if (opts.id) trigger.id = opts.id;
@@ -3145,7 +4077,7 @@ var ColorField = class {
3145
4077
  this.onVal(val);
3146
4078
  this.paint();
3147
4079
  },
3148
- { commit: this.commit }
4080
+ { commit: this.commit, i18n: this.i18n }
3149
4081
  )
3150
4082
  });
3151
4083
  pop.show();
@@ -3477,7 +4409,7 @@ var FIELD_CSS = `
3477
4409
  font-size: 14px;
3478
4410
  }
3479
4411
  .vela-field-label[data-size='sm'] { font-size: inherit; opacity: 0.85; }
3480
- /* Titles are fully inert: spans, not label[for] \u2014 native labels propagate :hover and
4412
+ /* Titles are fully inert: spans, not label[for] — native labels propagate :hover and
3481
4413
  clicks onto their control, and both belong to the input alone. */
3482
4414
  .vela-field-cell { display: flex; align-items: center; gap: 8px; justify-self: start; }
3483
4415
  .vela-field-bool { display: flex; align-items: center; gap: 8px; min-height: 22px; }
@@ -3817,7 +4749,7 @@ var CALLOUT_CSS = `
3817
4749
  }
3818
4750
  .vela-callout[role='button'] { cursor: pointer; }
3819
4751
  .vela-callout svg { display: block; }
3820
- /* The deployed panel \u2014 carries the kit's elevated-card look itself (the popover
4752
+ /* The deployed panel — carries the kit's elevated-card look itself (the popover
3821
4753
  shell is bare positioning chrome). */
3822
4754
  .vela-callout-panel {
3823
4755
  display: flex;
@@ -3974,9 +4906,14 @@ var CalloutBubble = class {
3974
4906
  };
3975
4907
 
3976
4908
  // src/ui/components/date-picker/controller.ts
3977
- var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
3978
- var MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
3979
- var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
4909
+ var NAV = {
4910
+ prevMonth: msg("ui.datePicker.previousMonth", "Previous month"),
4911
+ nextMonth: msg("ui.datePicker.nextMonth", "Next month"),
4912
+ prevYear: msg("ui.datePicker.previousYear", "Previous year"),
4913
+ nextYear: msg("ui.datePicker.nextYear", "Next year"),
4914
+ prevDecade: msg("ui.datePicker.previousDecade", "Previous decade"),
4915
+ nextDecade: msg("ui.datePicker.nextDecade", "Next decade")
4916
+ };
3980
4917
  function isoDate(d) {
3981
4918
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
3982
4919
  }
@@ -4002,6 +4939,12 @@ function datePickerController(opts = {}) {
4002
4939
  let year = selected?.getFullYear() ?? today.getFullYear();
4003
4940
  let month = selected?.getMonth() ?? today.getMonth();
4004
4941
  let panel = "date";
4942
+ const i18n = opts.i18n ?? englishTranslator();
4943
+ const settings = formatSettingsOf(i18n);
4944
+ const weekStart = opts.i18n ? i18n.weekStart : 0;
4945
+ const monthsLong = monthNames(settings, "long");
4946
+ const monthsShort = monthNames(settings, "short");
4947
+ const num = (n) => plainInteger(settings, n);
4005
4948
  const decade = () => Math.floor(year / 10) * 10;
4006
4949
  return {
4007
4950
  get panel() {
@@ -4016,38 +4959,43 @@ function datePickerController(opts = {}) {
4016
4959
  get value() {
4017
4960
  return selected ? isoDate(selected) : null;
4018
4961
  },
4962
+ yearFirst: yearBeforeMonth(settings),
4963
+ weekdays() {
4964
+ const names = weekdayNames(settings);
4965
+ return Array.from({ length: 7 }, (_, i) => names[(weekStart + i) % 7]);
4966
+ },
4019
4967
  header() {
4020
4968
  return {
4021
- month: MONTH_LABELS[month] ?? "",
4969
+ month: monthsLong[month] ?? "",
4022
4970
  monthVisible: panel === "date",
4023
- year: panel === "year" ? `${decade()}-${decade() + 9}` : String(year),
4971
+ year: panel === "year" ? `${num(decade())}-${num(decade() + 9)}` : yearLabel(settings, year),
4024
4972
  yearDisabled: panel === "year"
4025
4973
  // in the decade panel the year reads as its title
4026
4974
  };
4027
4975
  },
4028
4976
  navLabels() {
4029
- if (panel === "date") return { prev: "Previous month", next: "Next month" };
4030
- if (panel === "month") return { prev: "Previous year", next: "Next year" };
4031
- return { prev: "Previous decade", next: "Next decade" };
4977
+ if (panel === "date") return { prev: i18n.t(NAV.prevMonth), next: i18n.t(NAV.nextMonth) };
4978
+ if (panel === "month") return { prev: i18n.t(NAV.prevYear), next: i18n.t(NAV.nextYear) };
4979
+ return { prev: i18n.t(NAV.prevDecade), next: i18n.t(NAV.nextDecade) };
4032
4980
  },
4033
4981
  cells() {
4034
4982
  const out = [];
4035
4983
  if (panel === "date") {
4036
- const startPad = new Date(year, month, 1).getDay();
4984
+ const startPad = (new Date(year, month, 1).getDay() - weekStart + 7) % 7;
4037
4985
  const days = new Date(year, month + 1, 0).getDate();
4038
4986
  const todayIso = isoDate(today);
4039
4987
  const selectedIso = selected ? isoDate(selected) : "";
4040
4988
  for (let i = 0; i < startPad; i++) out.push({ label: "", value: "", checked: false, today: false, blank: true });
4041
4989
  for (let day = 1; day <= days; day++) {
4042
4990
  const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
4043
- out.push({ label: String(day), value: iso, checked: iso === selectedIso, today: iso === todayIso });
4991
+ out.push({ label: num(day), value: iso, checked: iso === selectedIso, today: iso === todayIso });
4044
4992
  }
4045
4993
  return out;
4046
4994
  }
4047
4995
  if (panel === "month") {
4048
4996
  for (let m = 0; m < 12; m++) {
4049
4997
  out.push({
4050
- label: MONTH_SHORT[m] ?? "",
4998
+ label: monthsShort[m] ?? "",
4051
4999
  value: m,
4052
5000
  checked: selected?.getFullYear() === year && selected.getMonth() === m,
4053
5001
  today: today.getFullYear() === year && today.getMonth() === m
@@ -4057,7 +5005,7 @@ function datePickerController(opts = {}) {
4057
5005
  }
4058
5006
  const d = decade();
4059
5007
  for (let y = d - 1; y <= d + 10; y++) {
4060
- out.push({ label: String(y), value: y, checked: selected?.getFullYear() === y, today: today.getFullYear() === y, outside: y < d || y > d + 9 });
5008
+ out.push({ label: num(y), value: y, checked: selected?.getFullYear() === y, today: today.getFullYear() === y, outside: y < d || y > d + 9 });
4061
5009
  }
4062
5010
  return out;
4063
5011
  },
@@ -4137,6 +5085,8 @@ var DATE_PICKER_CSS = `
4137
5085
  `;
4138
5086
 
4139
5087
  // src/ui/components/date-picker/view.ts
5088
+ var CHOOSE_MONTH = msg("ui.datePicker.chooseMonth", "Choose month");
5089
+ var CHOOSE_YEAR = msg("ui.datePicker.chooseYear", "Choose year");
4140
5090
  var DatePicker = class {
4141
5091
  constructor(opts = {}) {
4142
5092
  this.opts = opts;
@@ -4153,19 +5103,21 @@ var DatePicker = class {
4153
5103
  this.prev.append(iconEl("chevron-left", doc));
4154
5104
  this.next = button("vela-date-picker-nav");
4155
5105
  this.next.append(iconEl("chevron-right", doc));
5106
+ const i18n = opts.i18n ?? englishTranslator();
4156
5107
  this.monthBtn = button("vela-date-picker-switch");
4157
- this.monthBtn.setAttribute("aria-label", "Choose month");
5108
+ this.monthBtn.setAttribute("aria-label", i18n.t(CHOOSE_MONTH));
4158
5109
  this.yearBtn = button("vela-date-picker-switch");
4159
- this.yearBtn.setAttribute("aria-label", "Choose year");
5110
+ this.yearBtn.setAttribute("aria-label", i18n.t(CHOOSE_YEAR));
4160
5111
  const title = doc.createElement("div");
4161
5112
  title.className = "vela-date-picker-title";
4162
- title.append(this.monthBtn, this.yearBtn);
5113
+ if (this.ctrl.yearFirst) title.append(this.yearBtn, this.monthBtn);
5114
+ else title.append(this.monthBtn, this.yearBtn);
4163
5115
  const head = doc.createElement("div");
4164
5116
  head.className = "vela-date-picker-head";
4165
5117
  head.append(this.prev, title, this.next);
4166
5118
  this.week = doc.createElement("div");
4167
5119
  this.week.className = "vela-date-picker-week";
4168
- for (const d of WEEKDAY_LABELS) {
5120
+ for (const d of this.ctrl.weekdays()) {
4169
5121
  const cell = doc.createElement("span");
4170
5122
  cell.textContent = d;
4171
5123
  this.week.appendChild(cell);