@luxalgo/vela 0.6.0 → 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 (46) hide show
  1. package/dist/{DataProvider-DNx9ntBD.d.ts → DataProvider-CNmk84SH.d.cts} +27 -3
  2. package/dist/{DataProvider-Ce6PuCzQ.d.cts → DataProvider-DHX4x6-r.d.ts} +27 -3
  3. package/dist/{chunk-CA3N3PYT.js → chunk-7D6YIF34.js} +2151 -2464
  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-7ROONW6H.js → chunk-U5KCQHAC.js} +141 -10
  9. package/dist/{contributions-Dlc7ZWZz.d.cts → contributions-DDpv7hEL.d.cts} +6 -2
  10. package/dist/{contributions-DToMhiw3.d.ts → contributions-ciV6Neyd.d.ts} +6 -2
  11. package/dist/{history-BZADxpKx.d.cts → history-CFqZm5re.d.ts} +21 -3
  12. package/dist/{history-BuHXJZJ2.d.ts → history-ChoT34nz.d.cts} +21 -3
  13. package/dist/index.cjs +3724 -2742
  14. package/dist/index.d.cts +6 -6
  15. package/dist/index.d.ts +6 -6
  16. package/dist/index.js +4 -4
  17. package/dist/{options-BlQ00Fju.d.ts → options-BqGeFHtp.d.cts} +15 -1
  18. package/dist/{options-BlQ00Fju.d.cts → options-BqGeFHtp.d.ts} +15 -1
  19. package/dist/{plugin-BqjEa5r3.d.ts → plugin-C97gUCVf.d.ts} +7 -3
  20. package/dist/{plugin-BNPLOQO1.d.cts → plugin-CSb_sTiN.d.cts} +7 -3
  21. package/dist/plugin.d.cts +4 -4
  22. package/dist/plugin.d.ts +4 -4
  23. package/dist/plugin.js +2 -2
  24. package/dist/providers/binance.d.cts +2 -2
  25. package/dist/providers/binance.d.ts +2 -2
  26. package/dist/providers/coinbase.d.cts +2 -2
  27. package/dist/providers/coinbase.d.ts +2 -2
  28. package/dist/providers/hyperliquid.d.cts +2 -2
  29. package/dist/providers/hyperliquid.d.ts +2 -2
  30. package/dist/ui.cjs +1001 -247
  31. package/dist/ui.d.cts +258 -11
  32. package/dist/ui.d.ts +258 -11
  33. package/dist/ui.js +3 -3
  34. package/dist/vela.global.js +12358 -3961
  35. package/dist/vela.global.min.js +479 -124
  36. package/dist/widget.cjs +3463 -2867
  37. package/dist/widget.d.cts +20 -6
  38. package/dist/widget.d.ts +20 -6
  39. package/dist/widget.js +56 -11
  40. package/dist/workspace.cjs +3137 -2538
  41. package/dist/workspace.d.cts +22 -5
  42. package/dist/workspace.d.ts +22 -5
  43. package/dist/workspace.js +56 -8
  44. package/package.json +1 -1
  45. package/dist/chunk-OGRQW3M6.js +0 -1328
  46. package/dist/chunk-QWIEKZRE.js +0 -1042
package/dist/ui.cjs CHANGED
@@ -73,6 +73,11 @@ function isDarkColor(color) {
73
73
  }
74
74
 
75
75
  // src/ui/styles.ts
76
+ function overlayScrollbarCss(selector, width = 8) {
77
+ return `${selector}::-webkit-scrollbar{width:${width}px;height:${width}px;}${selector}::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:4px;border:2px solid transparent;background-clip:padding-box;}${selector}::-webkit-scrollbar-track{background:transparent;}${selector}::-webkit-scrollbar-button{display:none;width:0;height:0;}`;
78
+ }
79
+ var FIELD_FOCUS_CSS = "outline:none;transition:border-color .12s ease,box-shadow .12s ease;";
80
+ var FIELD_FOCUS_RING = "border-color:var(--vela-focus);box-shadow:0 0 0 3px var(--vela-focus-soft);";
76
81
  function injectStyles(id, css, root = document) {
77
82
  if (typeof document === "undefined") return;
78
83
  const host = root instanceof Document ? root.head : root;
@@ -920,6 +925,223 @@ var Menu = class {
920
925
  this.root.destroy();
921
926
  }
922
927
  };
928
+
929
+ // src/ui/components/popover/controller.ts
930
+ function insetRect(r, inset) {
931
+ return {
932
+ left: r.left + inset,
933
+ top: r.top + inset,
934
+ right: r.right - inset,
935
+ bottom: r.bottom - inset,
936
+ width: r.width - inset * 2,
937
+ height: r.height - inset * 2
938
+ };
939
+ }
940
+ function viewportRect(width, height, inset) {
941
+ return {
942
+ left: inset,
943
+ top: inset,
944
+ right: width - inset,
945
+ bottom: height - inset,
946
+ width: width - inset * 2,
947
+ height: height - inset * 2
948
+ };
949
+ }
950
+ function intersectRects(a, b) {
951
+ const left = Math.max(a.left, b.left);
952
+ const top = Math.max(a.top, b.top);
953
+ const right = Math.min(a.right, b.right);
954
+ const bottom = Math.min(a.bottom, b.bottom);
955
+ return { left, top, right, bottom, width: right - left, height: bottom - top };
956
+ }
957
+ function placePopover(a) {
958
+ let left = a.align === "end" ? a.trigger.right - a.pop.width : a.trigger.left;
959
+ const below = a.trigger.bottom + a.gap;
960
+ const above = a.trigger.top - a.pop.height - a.gap;
961
+ const fitsBelow = below + a.pop.height <= a.clamp.bottom;
962
+ const fitsAbove = above >= a.clamp.top;
963
+ let top = below;
964
+ if (!fitsBelow && (fitsAbove || a.trigger.top - a.clamp.top > a.clamp.bottom - a.trigger.bottom)) {
965
+ top = Math.max(a.clamp.top, above);
966
+ }
967
+ if (left + a.pop.width > a.clamp.right) left = a.clamp.right - a.pop.width;
968
+ if (left < a.clamp.left) left = a.clamp.left;
969
+ if (top + a.pop.height > a.clamp.bottom) top = a.clamp.bottom - a.pop.height;
970
+ if (top < a.clamp.top) top = a.clamp.top;
971
+ return {
972
+ left: Math.round(left - a.originX),
973
+ top: Math.round(top - a.originY)
974
+ };
975
+ }
976
+ function popoverController(opts = {}) {
977
+ return {
978
+ gap: opts.gap ?? 4,
979
+ align: opts.align ?? "start",
980
+ matchWidth: opts.matchWidth ?? false,
981
+ position: opts.position ?? "fixed",
982
+ boundaryInset: opts.boundaryInset ?? 0,
983
+ viewportInset: opts.viewportInset ?? 6,
984
+ onClose: opts.onClose
985
+ };
986
+ }
987
+
988
+ // src/ui/components/popover/styles.ts
989
+ var POPOVER_STYLE_ID = "vela-ui-popover";
990
+ var POPOVER_CSS = `
991
+ .vela-popover {
992
+ position: fixed;
993
+ z-index: var(--vela-z-popover);
994
+ box-sizing: border-box;
995
+ /* Never serve as a scroll anchor: the shell is portaled and re-placed between
996
+ gestures, and anchoring against it jumps the scroller underneath. */
997
+ overflow-anchor: none;
998
+ }
999
+ .vela-popover[data-position='absolute'] { position: absolute; }
1000
+ .vela-popover[hidden] { display: none !important; }
1001
+ `;
1002
+
1003
+ // src/ui/components/popover/view.ts
1004
+ var open = null;
1005
+ function closeOpenPopovers() {
1006
+ open?.hide();
1007
+ }
1008
+ function isPopoverOpen() {
1009
+ return open !== null;
1010
+ }
1011
+ function openPopoverTrigger() {
1012
+ return open?.trigger ?? null;
1013
+ }
1014
+ var POPOVER_DISMISS_FLAG = "__velaPopoverDismiss";
1015
+ function markPopoverDismiss(e) {
1016
+ e[POPOVER_DISMISS_FLAG] = true;
1017
+ }
1018
+ function eventDismissedPopover(e) {
1019
+ return e[POPOVER_DISMISS_FLAG] === true;
1020
+ }
1021
+ function toRect(r) {
1022
+ return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
1023
+ }
1024
+ var Popover = class {
1025
+ constructor(opts) {
1026
+ this.onOutside = null;
1027
+ this.onKey = null;
1028
+ this.onReflow = null;
1029
+ this.shown = false;
1030
+ const doc = opts.trigger.ownerDocument;
1031
+ injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
1032
+ this.trigger = opts.trigger;
1033
+ this.host = opts.host ?? doc.body;
1034
+ this.ctrl = popoverController(opts);
1035
+ this.boundary = opts.boundary ?? "viewport";
1036
+ this.theme = opts.theme;
1037
+ this.el = doc.createElement("div");
1038
+ this.el.className = "vela-popover vela-ui-layer" + (opts.className ? ` ${opts.className}` : "");
1039
+ this.el.dataset.position = this.ctrl.position;
1040
+ if (opts.zIndex !== void 0) this.el.style.zIndex = String(opts.zIndex);
1041
+ this.el.addEventListener("pointerdown", (e) => e.stopPropagation());
1042
+ if (opts.content instanceof Node) this.el.appendChild(opts.content);
1043
+ else if (typeof opts.content === "function") opts.content(this.el);
1044
+ }
1045
+ get open() {
1046
+ return this.shown;
1047
+ }
1048
+ get position() {
1049
+ return this.ctrl.position;
1050
+ }
1051
+ get align() {
1052
+ return this.ctrl.align;
1053
+ }
1054
+ show() {
1055
+ if (this.shown) {
1056
+ this.place();
1057
+ return;
1058
+ }
1059
+ if (open && open !== this) open.hide();
1060
+ ensureUIHost(this.el, this.theme);
1061
+ this.host.appendChild(this.el);
1062
+ this.shown = true;
1063
+ open = this;
1064
+ this.place();
1065
+ const onOutside = (ev) => {
1066
+ const t = ev.target;
1067
+ if (this.el.contains(t) || this.trigger.contains(t)) return;
1068
+ markPopoverDismiss(ev);
1069
+ this.hide();
1070
+ };
1071
+ const onKey = (ev) => {
1072
+ if (ev.key !== "Escape") return;
1073
+ ev.preventDefault();
1074
+ ev.stopPropagation();
1075
+ this.hide();
1076
+ };
1077
+ const onReflow = () => this.place();
1078
+ setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
1079
+ document.addEventListener("keydown", onKey, true);
1080
+ window.addEventListener("resize", onReflow, true);
1081
+ document.addEventListener("scroll", onReflow, true);
1082
+ this.onOutside = onOutside;
1083
+ this.onKey = onKey;
1084
+ this.onReflow = onReflow;
1085
+ }
1086
+ hide() {
1087
+ if (!this.shown) return;
1088
+ if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
1089
+ if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
1090
+ if (this.onReflow) {
1091
+ window.removeEventListener("resize", this.onReflow, true);
1092
+ document.removeEventListener("scroll", this.onReflow, true);
1093
+ }
1094
+ this.onOutside = null;
1095
+ this.onKey = null;
1096
+ this.onReflow = null;
1097
+ this.el.remove();
1098
+ this.shown = false;
1099
+ if (open === this) open = null;
1100
+ this.ctrl.onClose?.();
1101
+ }
1102
+ /** Show if closed, hide if this instance is the open popover. */
1103
+ toggle() {
1104
+ if (this.shown) this.hide();
1105
+ else this.show();
1106
+ }
1107
+ destroy() {
1108
+ this.hide();
1109
+ }
1110
+ reposition() {
1111
+ if (this.shown) this.place();
1112
+ }
1113
+ clampRect() {
1114
+ const view = viewportRect(window.innerWidth, window.innerHeight, this.ctrl.viewportInset);
1115
+ const bound = this.readBoundary();
1116
+ if (!bound) return view;
1117
+ const inset = insetRect(bound, this.ctrl.boundaryInset);
1118
+ return intersectRects(view, inset);
1119
+ }
1120
+ readBoundary() {
1121
+ const b = this.boundary;
1122
+ if (b === "viewport") return null;
1123
+ const raw = typeof b === "function" ? b() : b.getBoundingClientRect();
1124
+ return raw ? toRect(raw) : null;
1125
+ }
1126
+ place() {
1127
+ const ar = this.trigger.getBoundingClientRect();
1128
+ if (this.ctrl.matchWidth) {
1129
+ this.el.style.minWidth = `${Math.round(Math.max(this.el.offsetWidth, ar.width))}px`;
1130
+ }
1131
+ const origin = this.ctrl.position === "absolute" ? this.host.getBoundingClientRect() : { left: 0, top: 0 };
1132
+ const pos = placePopover({
1133
+ trigger: toRect(ar),
1134
+ pop: { width: this.el.offsetWidth, height: this.el.offsetHeight },
1135
+ gap: this.ctrl.gap,
1136
+ align: this.ctrl.align,
1137
+ clamp: this.clampRect(),
1138
+ originX: origin.left,
1139
+ originY: origin.top
1140
+ });
1141
+ this.el.style.left = `${pos.left}px`;
1142
+ this.el.style.top = `${pos.top}px`;
1143
+ }
1144
+ };
923
1145
  function dialogController(opts = {}) {
924
1146
  return {
925
1147
  machine: dialog__namespace.machine,
@@ -936,7 +1158,7 @@ function dialogController(opts = {}) {
936
1158
  }
937
1159
 
938
1160
  // src/ui/components/dialog/styles.ts
939
- var DIALOG_STYLE_ID = "vela-ui-dialog";
1161
+ var DIALOG_STYLE_ID = "vela-ui-dialog-5";
940
1162
  var DIALOG_CSS = `
941
1163
  .vela-dialog-backdrop {
942
1164
  position: fixed;
@@ -955,6 +1177,12 @@ var DIALOG_CSS = `
955
1177
  padding-top: 10vh;
956
1178
  z-index: var(--vela-z-dialog);
957
1179
  }
1180
+ .vela-dialog-backdrop[data-contained],
1181
+ .vela-dialog-positioner[data-contained] { position: absolute; }
1182
+ .vela-dialog-positioner[data-align='center'] {
1183
+ align-items: center;
1184
+ padding-top: 0;
1185
+ }
958
1186
  .vela-dialog {
959
1187
  background: var(--vela-surface);
960
1188
  color: var(--vela-fg);
@@ -997,14 +1225,56 @@ var DIALOG_CSS = `
997
1225
  font-size: 15px;
998
1226
  }
999
1227
  .vela-dialog-close:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
1000
- .vela-dialog-body { padding: var(--vela-space-4); overflow: auto; }
1001
- .vela-dialog-body::-webkit-scrollbar { width: 8px; }
1002
- .vela-dialog-body::-webkit-scrollbar-thumb {
1003
- background: var(--vela-scroll);
1004
- border-radius: 4px;
1005
- border: 2px solid transparent;
1006
- background-clip: padding-box;
1228
+ .vela-dialog-body { padding: var(--vela-space-4); overflow: auto; min-height: 0; flex: 1 1 auto; }
1229
+ .vela-dialog-body[data-flush] { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
1230
+ .vela-dialog-footer { flex: 0 0 auto; }
1231
+ .vela-dialog--settings {
1232
+ width: fit-content;
1233
+ min-width: min(560px, 94vw);
1234
+ max-width: min(720px, 94vw);
1235
+ max-height: 70vh;
1236
+ font-size: 13px;
1237
+ cursor: default;
1238
+ }
1239
+ .vela-dialog--form {
1240
+ width: fit-content;
1241
+ min-width: min(380px, 90%);
1242
+ max-width: min(640px, 94%);
1243
+ max-height: 82%;
1244
+ font-size: 14px;
1245
+ }
1246
+ /* Form dialogs (indicator inputs, drawing settings): the tab strip / body owns the
1247
+ line under the header, and the footer carries its own top delimiter. */
1248
+ .vela-dialog--form .vela-dialog-header { align-items: flex-start; padding: 16px 20px 20px; border-bottom: none; }
1249
+ .vela-dialog--form .vela-dialog-title { font-size: 20px; line-height: 28px; }
1250
+ .vela-dialog--form .vela-dialog-footer {
1251
+ padding: 16px 20px;
1252
+ display: flex;
1253
+ justify-content: flex-end;
1254
+ gap: 12px;
1255
+ border-top: 1px solid var(--vela-border);
1007
1256
  }
1257
+ .vela-dialog-btn {
1258
+ cursor: pointer;
1259
+ height: 34px;
1260
+ padding: 0 11px;
1261
+ border-radius: 6px;
1262
+ border: 1px solid var(--vela-border-strong);
1263
+ background: transparent;
1264
+ color: var(--vela-fg);
1265
+ font-weight: 400;
1266
+ font-size: 14px;
1267
+ font-family: inherit;
1268
+ transition: background var(--vela-dur-fast) ease, color var(--vela-dur-fast) ease, opacity var(--vela-dur-fast) ease, border-color var(--vela-dur-fast) ease;
1269
+ }
1270
+ .vela-dialog-btn:hover { background: var(--vela-hover); color: var(--vela-fg-bright); border-color: var(--vela-fg-muted); }
1271
+ .vela-dialog-btn-primary { border-color: var(--vela-selected-bg); background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
1272
+ .vela-dialog-btn-primary:hover { background: var(--vela-selected-bg); color: var(--vela-selected-fg); opacity: 0.85; border-color: var(--vela-selected-bg); }
1273
+ ${overlayScrollbarCss(".vela-dialog-body")}
1274
+ /* Scrollers NESTED in a dialog (a flush body's grid, a tab pane) get the same thin
1275
+ overlay bars \u2014 the two-class selectors below stay overridable by more specific
1276
+ per-dialog sheets. */
1277
+ ${overlayScrollbarCss(".vela-dialog *")}
1008
1278
  /* \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
1009
1279
  Inside a shell in the mobile size class ([data-layout='mobile'] on the widget root,
1010
1280
  which is position:relative) every kit dialog presents fullscreen: desktop cards are
@@ -1039,9 +1309,20 @@ var Dialog = class {
1039
1309
  if (opts.dimBackdrop !== true) this.backdrop.classList.add("vela-dialog-backdrop--clear");
1040
1310
  this.positioner = doc.createElement("div");
1041
1311
  this.positioner.className = "vela-dialog-positioner vela-ui-layer";
1312
+ if (opts.contained) {
1313
+ this.backdrop.dataset.contained = "";
1314
+ this.positioner.dataset.contained = "";
1315
+ }
1316
+ this.positioner.dataset.align = opts.align ?? "top";
1042
1317
  this.panel = doc.createElement("div");
1043
1318
  this.panel.className = "vela-dialog";
1319
+ if (opts.className) {
1320
+ for (const cls of opts.className.split(/\s+/)) if (cls) this.panel.classList.add(cls);
1321
+ }
1044
1322
  this.panel.tabIndex = -1;
1323
+ this.panel.addEventListener("keydown", (e) => {
1324
+ if (e.key !== "Escape") e.stopPropagation();
1325
+ });
1045
1326
  const header = doc.createElement("div");
1046
1327
  header.className = "vela-dialog-header";
1047
1328
  if (opts.draggable) {
@@ -1062,20 +1343,45 @@ var Dialog = class {
1062
1343
  });
1063
1344
  header.addEventListener("pointerup", () => dragging = false);
1064
1345
  }
1346
+ if (opts.headerStart) header.appendChild(opts.headerStart);
1065
1347
  const title = doc.createElement("div");
1066
1348
  title.className = "vela-dialog-title";
1067
1349
  title.textContent = opts.title ?? "";
1350
+ this.titleEl = title;
1068
1351
  const close = doc.createElement("button");
1069
1352
  close.className = "vela-dialog-close";
1070
1353
  close.appendChild(iconEl("close", doc));
1071
1354
  header.append(title, close);
1072
1355
  this.body = doc.createElement("div");
1073
1356
  this.body.className = "vela-dialog-body";
1357
+ if (opts.flush) this.body.dataset.flush = "";
1074
1358
  if (opts.content instanceof Node) this.body.appendChild(opts.content);
1075
1359
  else if (typeof opts.content === "function") opts.content(this.body);
1076
1360
  this.panel.append(header, this.body);
1361
+ if (opts.footer) {
1362
+ const foot = doc.createElement("div");
1363
+ foot.className = "vela-dialog-footer";
1364
+ if (opts.footer instanceof Node) foot.appendChild(opts.footer);
1365
+ else opts.footer(foot);
1366
+ this.panel.appendChild(foot);
1367
+ this.footer = foot;
1368
+ } else {
1369
+ this.footer = null;
1370
+ }
1077
1371
  this.positioner.appendChild(this.panel);
1078
1372
  host.append(this.backdrop, this.positioner);
1373
+ if (opts.closeOnBackdrop) {
1374
+ const closeOn = (e) => {
1375
+ if (e.target !== this.backdrop && e.target !== this.positioner) return;
1376
+ if (isPopoverOpen() || eventDismissedPopover(e)) {
1377
+ closeOpenPopovers();
1378
+ return;
1379
+ }
1380
+ this.hide();
1381
+ };
1382
+ this.backdrop.addEventListener("pointerdown", closeOn);
1383
+ this.positioner.addEventListener("pointerdown", closeOn);
1384
+ }
1079
1385
  this.ctrl = dialogController({
1080
1386
  ...opts,
1081
1387
  // Mobile chrome (fullscreen presentation): opening must never land focus on
@@ -1109,7 +1415,14 @@ var Dialog = class {
1109
1415
  hide() {
1110
1416
  this.ctrl.connect(this.handle.service).setOpen(false);
1111
1417
  }
1418
+ contains(node) {
1419
+ if (node == null) return false;
1420
+ if (this.panel.contains(node) || this.backdrop.contains(node) || this.positioner.contains(node)) return true;
1421
+ const el = node instanceof Element ? node : node.parentElement;
1422
+ return el?.closest(".vela-popover, .vela-menu") != null;
1423
+ }
1112
1424
  destroy() {
1425
+ if (this.open) this.hide();
1113
1426
  this.handle.stop();
1114
1427
  this.backdrop.remove();
1115
1428
  this.positioner.remove();
@@ -1396,219 +1709,10 @@ var Drawer = class {
1396
1709
  }
1397
1710
  };
1398
1711
 
1399
- // src/ui/components/popover/controller.ts
1400
- function insetRect(r, inset) {
1401
- return {
1402
- left: r.left + inset,
1403
- top: r.top + inset,
1404
- right: r.right - inset,
1405
- bottom: r.bottom - inset,
1406
- width: r.width - inset * 2,
1407
- height: r.height - inset * 2
1408
- };
1409
- }
1410
- function viewportRect(width, height, inset) {
1411
- return {
1412
- left: inset,
1413
- top: inset,
1414
- right: width - inset,
1415
- bottom: height - inset,
1416
- width: width - inset * 2,
1417
- height: height - inset * 2
1418
- };
1419
- }
1420
- function intersectRects(a, b) {
1421
- const left = Math.max(a.left, b.left);
1422
- const top = Math.max(a.top, b.top);
1423
- const right = Math.min(a.right, b.right);
1424
- const bottom = Math.min(a.bottom, b.bottom);
1425
- return { left, top, right, bottom, width: right - left, height: bottom - top };
1426
- }
1427
- function placePopover(a) {
1428
- let left = a.align === "end" ? a.trigger.right - a.pop.width : a.trigger.left;
1429
- const below = a.trigger.bottom + a.gap;
1430
- const above = a.trigger.top - a.pop.height - a.gap;
1431
- const fitsBelow = below + a.pop.height <= a.clamp.bottom;
1432
- const fitsAbove = above >= a.clamp.top;
1433
- let top = below;
1434
- if (!fitsBelow && (fitsAbove || a.trigger.top - a.clamp.top > a.clamp.bottom - a.trigger.bottom)) {
1435
- top = Math.max(a.clamp.top, above);
1436
- }
1437
- if (left + a.pop.width > a.clamp.right) left = a.clamp.right - a.pop.width;
1438
- if (left < a.clamp.left) left = a.clamp.left;
1439
- if (top + a.pop.height > a.clamp.bottom) top = a.clamp.bottom - a.pop.height;
1440
- if (top < a.clamp.top) top = a.clamp.top;
1441
- return {
1442
- left: Math.round(left - a.originX),
1443
- top: Math.round(top - a.originY)
1444
- };
1445
- }
1446
- function popoverController(opts = {}) {
1447
- return {
1448
- gap: opts.gap ?? 4,
1449
- align: opts.align ?? "start",
1450
- matchWidth: opts.matchWidth ?? false,
1451
- position: opts.position ?? "fixed",
1452
- boundaryInset: opts.boundaryInset ?? 0,
1453
- viewportInset: opts.viewportInset ?? 6,
1454
- onClose: opts.onClose
1455
- };
1456
- }
1457
-
1458
- // src/ui/components/popover/styles.ts
1459
- var POPOVER_STYLE_ID = "vela-ui-popover";
1460
- var POPOVER_CSS = `
1461
- .vela-popover {
1462
- position: fixed;
1463
- z-index: var(--vela-z-popover);
1464
- box-sizing: border-box;
1465
- /* Never serve as a scroll anchor: the shell is portaled and re-placed between
1466
- gestures, and anchoring against it jumps the scroller underneath. */
1467
- overflow-anchor: none;
1468
- }
1469
- .vela-popover[data-position='absolute'] { position: absolute; }
1470
- .vela-popover[hidden] { display: none !important; }
1471
- `;
1472
-
1473
- // src/ui/components/popover/view.ts
1474
- var open = null;
1475
- function closeOpenPopovers() {
1476
- open?.hide();
1477
- }
1478
- function isPopoverOpen() {
1479
- return open !== null;
1480
- }
1481
- function openPopoverTrigger() {
1482
- return open?.trigger ?? null;
1483
- }
1484
- function toRect(r) {
1485
- return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
1486
- }
1487
- var Popover = class {
1488
- constructor(opts) {
1489
- this.onOutside = null;
1490
- this.onKey = null;
1491
- this.onReflow = null;
1492
- this.shown = false;
1493
- const doc = opts.trigger.ownerDocument;
1494
- injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
1495
- this.trigger = opts.trigger;
1496
- this.host = opts.host ?? doc.body;
1497
- this.ctrl = popoverController(opts);
1498
- this.boundary = opts.boundary ?? "viewport";
1499
- this.theme = opts.theme;
1500
- this.el = doc.createElement("div");
1501
- this.el.className = "vela-popover vela-ui-layer" + (opts.className ? ` ${opts.className}` : "");
1502
- this.el.dataset.position = this.ctrl.position;
1503
- if (opts.zIndex !== void 0) this.el.style.zIndex = String(opts.zIndex);
1504
- this.el.addEventListener("pointerdown", (e) => e.stopPropagation());
1505
- if (opts.content instanceof Node) this.el.appendChild(opts.content);
1506
- else if (typeof opts.content === "function") opts.content(this.el);
1507
- }
1508
- get open() {
1509
- return this.shown;
1510
- }
1511
- get position() {
1512
- return this.ctrl.position;
1513
- }
1514
- get align() {
1515
- return this.ctrl.align;
1516
- }
1517
- show() {
1518
- if (this.shown) {
1519
- this.place();
1520
- return;
1521
- }
1522
- if (open && open !== this) open.hide();
1523
- ensureUIHost(this.el, this.theme);
1524
- this.host.appendChild(this.el);
1525
- this.shown = true;
1526
- open = this;
1527
- this.place();
1528
- const onOutside = (ev) => {
1529
- const t = ev.target;
1530
- if (this.el.contains(t) || this.trigger.contains(t)) return;
1531
- this.hide();
1532
- };
1533
- const onKey = (ev) => {
1534
- if (ev.key !== "Escape") return;
1535
- ev.preventDefault();
1536
- ev.stopPropagation();
1537
- this.hide();
1538
- };
1539
- const onReflow = () => this.place();
1540
- setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
1541
- document.addEventListener("keydown", onKey, true);
1542
- window.addEventListener("resize", onReflow, true);
1543
- document.addEventListener("scroll", onReflow, true);
1544
- this.onOutside = onOutside;
1545
- this.onKey = onKey;
1546
- this.onReflow = onReflow;
1547
- }
1548
- hide() {
1549
- if (!this.shown) return;
1550
- if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
1551
- if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
1552
- if (this.onReflow) {
1553
- window.removeEventListener("resize", this.onReflow, true);
1554
- document.removeEventListener("scroll", this.onReflow, true);
1555
- }
1556
- this.onOutside = null;
1557
- this.onKey = null;
1558
- this.onReflow = null;
1559
- this.el.remove();
1560
- this.shown = false;
1561
- if (open === this) open = null;
1562
- this.ctrl.onClose?.();
1563
- }
1564
- /** Show if closed, hide if this instance is the open popover. */
1565
- toggle() {
1566
- if (this.shown) this.hide();
1567
- else this.show();
1568
- }
1569
- destroy() {
1570
- this.hide();
1571
- }
1572
- reposition() {
1573
- if (this.shown) this.place();
1574
- }
1575
- clampRect() {
1576
- const view = viewportRect(window.innerWidth, window.innerHeight, this.ctrl.viewportInset);
1577
- const bound = this.readBoundary();
1578
- if (!bound) return view;
1579
- const inset = insetRect(bound, this.ctrl.boundaryInset);
1580
- return intersectRects(view, inset);
1581
- }
1582
- readBoundary() {
1583
- const b = this.boundary;
1584
- if (b === "viewport") return null;
1585
- const raw = typeof b === "function" ? b() : b.getBoundingClientRect();
1586
- return raw ? toRect(raw) : null;
1587
- }
1588
- place() {
1589
- const ar = this.trigger.getBoundingClientRect();
1590
- if (this.ctrl.matchWidth) {
1591
- this.el.style.minWidth = `${Math.round(Math.max(this.el.offsetWidth, ar.width))}px`;
1592
- }
1593
- const origin = this.ctrl.position === "absolute" ? this.host.getBoundingClientRect() : { left: 0, top: 0 };
1594
- const pos = placePopover({
1595
- trigger: toRect(ar),
1596
- pop: { width: this.el.offsetWidth, height: this.el.offsetHeight },
1597
- gap: this.ctrl.gap,
1598
- align: this.ctrl.align,
1599
- clamp: this.clampRect(),
1600
- originX: origin.left,
1601
- originY: origin.top
1602
- });
1603
- this.el.style.left = `${pos.left}px`;
1604
- this.el.style.top = `${pos.top}px`;
1605
- }
1606
- };
1607
-
1608
- // src/ui/components/switch/controller.ts
1609
- function switchController(opts = {}) {
1610
- let checked = opts.checked ?? false;
1611
- const disabled = opts.disabled ?? false;
1712
+ // src/ui/components/switch/controller.ts
1713
+ function switchController(opts = {}) {
1714
+ let checked = opts.checked ?? false;
1715
+ const disabled = opts.disabled ?? false;
1612
1716
  return {
1613
1717
  get checked() {
1614
1718
  return checked;
@@ -1739,14 +1843,18 @@ function selectController(opts) {
1739
1843
  }
1740
1844
 
1741
1845
  // src/ui/components/select/styles.ts
1742
- var SELECT_STYLE_ID = "vela-ui-select";
1846
+ var SELECT_STYLE_ID = "vela-ui-select-3";
1743
1847
  var SELECT_CSS = `
1744
1848
  .vela-select { position: relative; display: inline-block; min-width: 0; }
1745
1849
  .vela-select[data-fill] { width: 100%; }
1850
+ /* Closed trigger matches NumberInput / TextField (100px). Long labels ellipsis;
1851
+ the open list still sizes to the longest item. */
1852
+ .vela-select:not([data-fill]) { width: 100px; flex: none; justify-self: start; }
1746
1853
  .vela-select-trigger {
1747
1854
  display: flex;
1748
1855
  align-items: center;
1749
1856
  width: 100%;
1857
+ min-width: 0;
1750
1858
  box-sizing: border-box;
1751
1859
  height: 34px;
1752
1860
  padding: 0 26px 0 8px;
@@ -1763,10 +1871,6 @@ var SELECT_CSS = `
1763
1871
  }
1764
1872
  .vela-select-trigger:hover { border-color: var(--vela-fg-muted); }
1765
1873
  .vela-select-trigger:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
1766
- /* As a grid item (chart-settings rows dissolve via display:contents into a shared
1767
- max-content column) the wrap must NOT stretch to the column: it sizes to its own
1768
- widest option, like the native select it replaces. */
1769
- .vela-select[data-size='sm'] { max-width: 200px; flex: 0 0 auto; justify-self: start; }
1770
1874
  .vela-select[data-size='sm'] .vela-select-trigger {
1771
1875
  height: 28px;
1772
1876
  background: var(--vela-surface-elev);
@@ -1776,12 +1880,10 @@ var SELECT_CSS = `
1776
1880
  box-shadow: none;
1777
1881
  }
1778
1882
  .vela-select[data-size='sm'] .vela-select-trigger:focus { box-shadow: none; border-color: var(--vela-fg-muted); }
1779
- .vela-select-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1780
- /* Invisible option-label stack: gives the shrink-to-fit wrap the WIDEST option's
1781
- intrinsic width (like a native select), so picking a shorter/longer label never
1782
- resizes the trigger and shifts the row around it. */
1783
- .vela-select-sizer { visibility: hidden; height: 0; overflow: hidden; font-size: 14px; }
1784
- .vela-select[data-size='sm'] .vela-select-sizer { font-size: 13px; }
1883
+ .vela-select-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1884
+ /* Compact toolbar selects still hug the widest option (no 100px kit column). */
1885
+ .vela-select[data-size='sm']:not([data-fill]) { width: auto; max-width: 200px; }
1886
+ .vela-select-sizer { visibility: hidden; height: 0; overflow: hidden; font-size: 13px; }
1785
1887
  .vela-select-sizer span { display: block; height: 0; white-space: nowrap; padding: 0 26px 0 8px; border-inline: 1px solid transparent; }
1786
1888
  .vela-select-chevron {
1787
1889
  position: absolute;
@@ -1796,6 +1898,7 @@ var SELECT_CSS = `
1796
1898
  }
1797
1899
  .vela-select-chevron .vela-icon, .vela-select-chevron svg { width: 12px; height: 12px; display: block; }
1798
1900
  .vela-select-list {
1901
+ width: max-content;
1799
1902
  background: var(--vela-bg);
1800
1903
  color: var(--vela-fg);
1801
1904
  border: none;
@@ -1806,11 +1909,11 @@ var SELECT_CSS = `
1806
1909
  overflow: hidden;
1807
1910
  }
1808
1911
  .vela-select-list[data-size='sm'] { font-size: 13px; background: var(--vela-surface-overlay); }
1809
- .vela-select-items { max-height: none; overflow: hidden; }
1912
+ .vela-select-items { width: max-content; min-width: 100%; max-height: none; overflow: hidden; }
1810
1913
  .vela-select-list.is-scroll { display: flex; align-items: stretch; gap: 2px; }
1811
1914
  .vela-select-list.is-scroll .vela-select-items {
1812
1915
  flex: 1 1 auto;
1813
- min-width: 0;
1916
+ min-width: min-content;
1814
1917
  max-height: 240px;
1815
1918
  overflow-y: auto;
1816
1919
  scrollbar-width: none;
@@ -1820,7 +1923,9 @@ var SELECT_CSS = `
1820
1923
  .vela-select-thumb { width: 3px; border-radius: 2px; background: var(--vela-scroll); }
1821
1924
  .vela-select-item {
1822
1925
  display: block;
1823
- width: 100%;
1926
+ width: auto;
1927
+ min-width: 100%;
1928
+ white-space: nowrap;
1824
1929
  text-align: left;
1825
1930
  padding: 7px 10px;
1826
1931
  border: none;
@@ -1947,15 +2052,17 @@ var Select = class {
1947
2052
  chevron.appendChild(iconEl("chevron-down", doc));
1948
2053
  btn.append(label, chevron);
1949
2054
  wrap.appendChild(btn);
1950
- const sizer = doc.createElement("div");
1951
- sizer.className = "vela-select-sizer";
1952
- sizer.setAttribute("aria-hidden", "true");
1953
- for (const o of this.ctrl.options) {
1954
- const s = doc.createElement("span");
1955
- s.textContent = o.label;
1956
- sizer.appendChild(s);
2055
+ if (this.ctrl.size === "sm") {
2056
+ const sizer = doc.createElement("div");
2057
+ sizer.className = "vela-select-sizer";
2058
+ sizer.setAttribute("aria-hidden", "true");
2059
+ for (const o of this.ctrl.options) {
2060
+ const s = doc.createElement("span");
2061
+ s.textContent = o.label;
2062
+ sizer.appendChild(s);
2063
+ }
2064
+ wrap.appendChild(sizer);
1957
2065
  }
1958
- wrap.appendChild(sizer);
1959
2066
  btn.addEventListener("pointerdown", (e) => {
1960
2067
  e.preventDefault();
1961
2068
  btn.focus({ preventScroll: true });
@@ -2061,10 +2168,12 @@ function numberInputController(opts) {
2061
2168
  }
2062
2169
 
2063
2170
  // src/ui/components/number-input/styles.ts
2064
- var NUMBER_STYLE_ID = "vela-ui-number";
2171
+ var NUMBER_STYLE_ID = "vela-ui-number-3";
2065
2172
  var NUMBER_CSS = `
2066
2173
  .vela-num { position: relative; display: inline-block; min-width: 0; }
2067
2174
  .vela-num[data-fill] { width: 100%; }
2175
+ .vela-num:not([data-fill]) { width: 100px; flex: none; justify-self: start; }
2176
+ .vela-num:not([data-fill])[data-compact] { width: 80px; }
2068
2177
  .vela-num input {
2069
2178
  width: 100%;
2070
2179
  box-sizing: border-box;
@@ -2084,7 +2193,9 @@ var NUMBER_CSS = `
2084
2193
  .vela-num input::-webkit-inner-spin-button, .vela-num input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
2085
2194
  .vela-num input:hover { border-color: var(--vela-fg-muted); }
2086
2195
  .vela-num input:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
2087
- .vela-num[data-steppers] input { padding-right: 26px; }
2196
+ /* The stepper gutter exists only while the steppers do (hover) \u2014 an idle field keeps
2197
+ its full width so long values aren't cut under an invisible arrow column. */
2198
+ .vela-num[data-steppers]:hover input { padding-right: 26px; }
2088
2199
  .vela-num[data-size='sm'] { width: 64px; flex: none; }
2089
2200
  .vela-num[data-size='sm'][data-compact] { width: 56px; }
2090
2201
  .vela-num[data-size='sm'] input {
@@ -2140,7 +2251,7 @@ var NumberInput = class {
2140
2251
  const wrap = doc.createElement("div");
2141
2252
  wrap.className = "vela-num";
2142
2253
  wrap.dataset.size = this.ctrl.size;
2143
- if (this.ctrl.size !== "sm") wrap.dataset.fill = "";
2254
+ if (opts.fill ?? this.ctrl.size !== "sm") wrap.dataset.fill = "";
2144
2255
  if (opts.compact) wrap.dataset.compact = "";
2145
2256
  if (this.ctrl.steppers) wrap.dataset.steppers = "";
2146
2257
  const ni = doc.createElement("input");
@@ -2266,15 +2377,17 @@ function textFieldController(opts = {}) {
2266
2377
  }
2267
2378
 
2268
2379
  // src/ui/components/text-field/styles.ts
2269
- var TEXT_STYLE_ID = "vela-ui-text";
2380
+ var TEXT_STYLE_ID = "vela-ui-text-2";
2270
2381
  var TEXT_CSS = `
2271
2382
  .vela-text {
2272
2383
  display: inline-block;
2273
2384
  min-width: 0;
2274
2385
  }
2275
2386
  .vela-text[data-fill] { width: 100%; }
2387
+ .vela-text:not([data-fill]) { width: 100px; flex: none; justify-self: start; }
2276
2388
  .vela-text-field {
2277
2389
  width: 100%;
2390
+ min-width: 0;
2278
2391
  box-sizing: border-box;
2279
2392
  height: 34px;
2280
2393
  background: transparent;
@@ -2285,6 +2398,8 @@ var TEXT_CSS = `
2285
2398
  font-size: 14px;
2286
2399
  font-family: inherit;
2287
2400
  outline: none;
2401
+ overflow: hidden;
2402
+ text-overflow: ellipsis;
2288
2403
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
2289
2404
  }
2290
2405
  .vela-text-field:hover { border-color: var(--vela-fg-muted); }
@@ -2310,6 +2425,7 @@ var TextField = class {
2310
2425
  if (this.ctrl.fill) wrap.dataset.fill = "";
2311
2426
  const ti = doc.createElement("input");
2312
2427
  ti.type = "text";
2428
+ ti.size = 1;
2313
2429
  if (opts.id) ti.id = opts.id;
2314
2430
  ti.value = this.ctrl.value;
2315
2431
  ti.className = "vela-text-field";
@@ -2612,6 +2728,624 @@ function closeColorPopover() {
2612
2728
  closeOpenPopovers();
2613
2729
  }
2614
2730
 
2731
+ // src/ui/components/text-area/controller.ts
2732
+ function textAreaController(opts = {}) {
2733
+ let value = opts.value ?? "";
2734
+ return {
2735
+ get value() {
2736
+ return value;
2737
+ },
2738
+ size: opts.size ?? "md",
2739
+ rows: opts.rows ?? 3,
2740
+ disabled: opts.disabled ?? false,
2741
+ commit(next) {
2742
+ if (next === value) return null;
2743
+ value = next;
2744
+ opts.onChange?.(next);
2745
+ return next;
2746
+ },
2747
+ sync(next) {
2748
+ value = next;
2749
+ }
2750
+ };
2751
+ }
2752
+
2753
+ // src/ui/components/text-area/styles.ts
2754
+ var TEXTAREA_STYLE_ID = "vela-ui-textarea";
2755
+ var TEXTAREA_CSS = `
2756
+ .vela-textarea { display: block; width: 100%; min-width: 0; }
2757
+ .vela-textarea-field {
2758
+ display: block;
2759
+ width: 100%;
2760
+ box-sizing: border-box;
2761
+ min-height: 64px;
2762
+ background: transparent;
2763
+ border: 1px solid var(--vela-border-strong);
2764
+ border-radius: 6px;
2765
+ color: var(--vela-fg-bright);
2766
+ padding: 8px;
2767
+ font-size: 14px;
2768
+ font-family: inherit;
2769
+ line-height: 1.4;
2770
+ resize: vertical;
2771
+ outline: none;
2772
+ transition: border-color 0.12s ease, box-shadow 0.12s ease;
2773
+ }
2774
+ .vela-textarea-field:hover { border-color: var(--vela-fg-muted); }
2775
+ .vela-textarea-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
2776
+ .vela-textarea-field::placeholder { color: currentColor; opacity: 0.4; }
2777
+ .vela-textarea[data-autogrow] .vela-textarea-field { resize: none; overflow-y: hidden; min-height: 46px; }
2778
+ .vela-textarea[data-size='sm'] .vela-textarea-field {
2779
+ font-size: 13px;
2780
+ min-height: 46px;
2781
+ padding: 6px 9px;
2782
+ line-height: 18px;
2783
+ }
2784
+ `;
2785
+
2786
+ // src/ui/components/text-area/view.ts
2787
+ var TextArea = class {
2788
+ constructor(opts = {}) {
2789
+ const doc = document;
2790
+ injectStyles(TEXTAREA_STYLE_ID, TEXTAREA_CSS, doc);
2791
+ this.ctrl = textAreaController(opts);
2792
+ this.autoGrow = opts.autoGrow === true;
2793
+ this.maxLines = opts.maxLines ?? 4;
2794
+ const wrap = doc.createElement("div");
2795
+ wrap.className = "vela-textarea";
2796
+ wrap.dataset.size = this.ctrl.size;
2797
+ if (this.autoGrow) wrap.dataset.autogrow = "";
2798
+ const ta = doc.createElement("textarea");
2799
+ ta.className = "vela-textarea-field";
2800
+ if (opts.id) ta.id = opts.id;
2801
+ if (opts.placeholder) ta.placeholder = opts.placeholder;
2802
+ ta.rows = this.ctrl.rows;
2803
+ ta.value = this.ctrl.value;
2804
+ if (this.ctrl.disabled) ta.disabled = true;
2805
+ ta.addEventListener("blur", () => {
2806
+ this.ctrl.commit(ta.value);
2807
+ });
2808
+ if (this.autoGrow) {
2809
+ ta.addEventListener("input", () => this.grow());
2810
+ }
2811
+ wrap.appendChild(ta);
2812
+ this.el = wrap;
2813
+ this.input = ta;
2814
+ if (this.autoGrow) this.grow();
2815
+ }
2816
+ get value() {
2817
+ return this.ctrl.value;
2818
+ }
2819
+ setValue(v) {
2820
+ this.ctrl.sync(v);
2821
+ this.input.value = v;
2822
+ if (this.autoGrow) this.grow();
2823
+ }
2824
+ grow() {
2825
+ const ta = this.input;
2826
+ const lh = parseFloat(getComputedStyle(ta).lineHeight) || 18;
2827
+ const pad = ta.clientHeight - (ta.offsetHeight ? lh * (ta.rows || 1) : 0);
2828
+ const max = lh * this.maxLines + (Number.isFinite(pad) && pad > 0 ? pad : 12);
2829
+ ta.style.height = "auto";
2830
+ ta.style.height = `${Math.min(ta.scrollHeight, max)}px`;
2831
+ ta.style.overflowY = ta.scrollHeight > max ? "auto" : "hidden";
2832
+ }
2833
+ };
2834
+
2835
+ // src/ui/components/glyph-select/controller.ts
2836
+ function glyphSelectController(opts) {
2837
+ let value = opts.value;
2838
+ return {
2839
+ options: opts.options,
2840
+ get value() {
2841
+ return value;
2842
+ },
2843
+ setValue(v) {
2844
+ value = v;
2845
+ },
2846
+ optionOf(v) {
2847
+ return opts.options.find((o) => o.value === v);
2848
+ },
2849
+ pick(v) {
2850
+ value = v;
2851
+ opts.onChange?.(v);
2852
+ return v;
2853
+ }
2854
+ };
2855
+ }
2856
+ var WIDTH_FIELD_OPTIONS = [1, 2, 3, 4, 5];
2857
+ function lineWidthGlyph(width) {
2858
+ return `<svg width="22" height="14" viewBox="0 0 22 14" fill="none"><line x1="2" y1="7" x2="20" y2="7" stroke="currentColor" stroke-width="${width}" stroke-linecap="round"/></svg>`;
2859
+ }
2860
+ function widthFieldOptions() {
2861
+ return WIDTH_FIELD_OPTIONS.map((w) => ({ value: w, label: `${w}px`, glyph: lineWidthGlyph(w) }));
2862
+ }
2863
+
2864
+ // src/ui/components/glyph-select/styles.ts
2865
+ var GLYPH_SELECT_STYLE_ID = "vela-ui-glyph-select";
2866
+ var GLYPH_SELECT_CSS = `
2867
+ .vela-glyph-select {
2868
+ height: 34px;
2869
+ padding: 0 26px 0 8px;
2870
+ border: 1px solid var(--vela-border-strong);
2871
+ border-radius: 6px;
2872
+ background: transparent;
2873
+ cursor: pointer;
2874
+ display: inline-flex;
2875
+ align-items: center;
2876
+ gap: 8px;
2877
+ flex: none;
2878
+ justify-self: start;
2879
+ color: var(--vela-fg-bright);
2880
+ font-size: 14px;
2881
+ font-family: inherit;
2882
+ outline: none;
2883
+ position: relative;
2884
+ transition: border-color 0.12s ease, box-shadow 0.12s ease;
2885
+ }
2886
+ .vela-glyph-select:hover { border-color: var(--vela-fg-muted); }
2887
+ .vela-glyph-select:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
2888
+ .vela-glyph-select-caret {
2889
+ position: absolute;
2890
+ right: 8px;
2891
+ top: 50%;
2892
+ transform: translateY(-50%);
2893
+ display: flex;
2894
+ opacity: 0.55;
2895
+ color: inherit;
2896
+ line-height: 0;
2897
+ pointer-events: none;
2898
+ }
2899
+ .vela-glyph-select-pop {
2900
+ background: var(--vela-bg);
2901
+ border: none;
2902
+ border-radius: 6px;
2903
+ box-shadow: var(--vela-shadow);
2904
+ padding: 4px;
2905
+ display: flex;
2906
+ flex-direction: column;
2907
+ gap: 1px;
2908
+ color: var(--vela-fg);
2909
+ font: 14px var(--vela-font);
2910
+ }
2911
+ .vela-glyph-select-item {
2912
+ display: flex;
2913
+ align-items: center;
2914
+ gap: 8px;
2915
+ min-width: 96px;
2916
+ padding: 7px 10px;
2917
+ border: none;
2918
+ border-radius: 4px;
2919
+ background: transparent;
2920
+ color: inherit;
2921
+ cursor: pointer;
2922
+ text-align: left;
2923
+ font: inherit;
2924
+ }
2925
+ .vela-glyph-select-item:hover { background: var(--vela-hover); }
2926
+ .vela-glyph-select-item[data-active='1'] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
2927
+ `;
2928
+
2929
+ // src/ui/components/glyph-select/view.ts
2930
+ var GlyphSelect = class {
2931
+ constructor(opts) {
2932
+ injectStyles(GLYPH_SELECT_STYLE_ID, GLYPH_SELECT_CSS, document);
2933
+ this.ctrl = glyphSelectController(opts);
2934
+ this.theme = opts.theme;
2935
+ this.get = opts.get ?? (() => this.ctrl.value);
2936
+ const trigger = document.createElement("button");
2937
+ trigger.type = "button";
2938
+ trigger.className = "vela-glyph-select";
2939
+ if (opts.title) trigger.title = opts.title;
2940
+ this.el = trigger;
2941
+ this.paint();
2942
+ trigger.addEventListener("pointerdown", (e) => {
2943
+ e.preventDefault();
2944
+ trigger.focus({ preventScroll: true });
2945
+ });
2946
+ trigger.addEventListener("click", (e) => {
2947
+ e.stopPropagation();
2948
+ this.toggle();
2949
+ });
2950
+ trigger.addEventListener("vela-sync", () => {
2951
+ this.ctrl.setValue(this.get());
2952
+ this.paint();
2953
+ });
2954
+ }
2955
+ setValue(v) {
2956
+ this.ctrl.setValue(v);
2957
+ this.paint();
2958
+ }
2959
+ paint() {
2960
+ const cur = this.get();
2961
+ const opt = this.ctrl.optionOf(cur);
2962
+ const glyph = document.createElement("span");
2963
+ glyph.style.display = "flex";
2964
+ glyph.innerHTML = opt?.glyph ?? "";
2965
+ const caret = document.createElement("span");
2966
+ caret.className = "vela-glyph-select-caret";
2967
+ caret.appendChild(iconEl("chevron-down", this.el.ownerDocument));
2968
+ this.el.replaceChildren(glyph, caret);
2969
+ }
2970
+ toggle() {
2971
+ if (openPopoverTrigger() === this.el) {
2972
+ closeOpenPopovers();
2973
+ return;
2974
+ }
2975
+ const current = this.get();
2976
+ const pop = new Popover({
2977
+ trigger: this.el,
2978
+ theme: this.theme,
2979
+ align: "end",
2980
+ gap: 6,
2981
+ className: "vela-glyph-select-pop",
2982
+ content: (el) => {
2983
+ el.style.font = `var(--vela-font-size-md) ${this.theme.fontFamily}`;
2984
+ for (const o of this.ctrl.options) {
2985
+ const item = document.createElement("button");
2986
+ item.type = "button";
2987
+ item.className = "vela-glyph-select-item";
2988
+ item.dataset.active = o.value === current ? "1" : "0";
2989
+ item.innerHTML = `<span style="display:flex;flex:none;">${o.glyph}</span><span style="flex:1;font-variant-numeric:tabular-nums;">${o.label}</span>`;
2990
+ item.addEventListener("click", (ev) => {
2991
+ ev.stopPropagation();
2992
+ pop.hide();
2993
+ this.ctrl.pick(o.value);
2994
+ this.paint();
2995
+ });
2996
+ el.appendChild(item);
2997
+ }
2998
+ }
2999
+ });
3000
+ pop.show();
3001
+ }
3002
+ };
3003
+ function widthField(theme, getVal, onVal) {
3004
+ return new GlyphSelect({
3005
+ theme,
3006
+ options: widthFieldOptions(),
3007
+ value: getVal(),
3008
+ get: getVal,
3009
+ onChange: onVal
3010
+ }).el;
3011
+ }
3012
+ function closeWidthPopover() {
3013
+ closeOpenPopovers();
3014
+ }
3015
+
3016
+ // src/ui/components/field/controller.ts
3017
+ var FIELD_GAP_PX = 16;
3018
+ function fieldGridColumns(variant, mobile) {
3019
+ if (variant === "inputs") return mobile ? "minmax(0,1fr) auto" : "max-content 1fr";
3020
+ return mobile ? "minmax(0,1fr) max-content" : "max-content max-content";
3021
+ }
3022
+
3023
+ // src/ui/components/field/styles.ts
3024
+ var FIELD_STYLE_ID = "vela-ui-field";
3025
+ var FIELD_CSS = `
3026
+ .vela-field-grid {
3027
+ display: grid;
3028
+ align-items: center;
3029
+ align-content: start;
3030
+ column-gap: ${FIELD_GAP_PX}px;
3031
+ row-gap: ${FIELD_GAP_PX}px;
3032
+ }
3033
+ .vela-field-grid[data-variant='inputs'] { grid-template-columns: max-content 1fr; }
3034
+ .vela-field-grid[data-variant='inputs'][data-mobile] { grid-template-columns: minmax(0,1fr) auto; }
3035
+ .vela-field-grid[data-variant='settings'] {
3036
+ grid-template-columns: max-content max-content;
3037
+ column-gap: 12px;
3038
+ }
3039
+ .vela-field-grid[data-variant='settings'][data-mobile] { grid-template-columns: minmax(0,1fr) max-content; }
3040
+ .vela-field-row { display: contents; }
3041
+ .vela-field-span { grid-column: 1 / -1; }
3042
+ .vela-field-label {
3043
+ opacity: 0.9;
3044
+ white-space: nowrap;
3045
+ overflow: hidden;
3046
+ text-overflow: ellipsis;
3047
+ font-size: 14px;
3048
+ }
3049
+ .vela-field-label[data-size='sm'] { font-size: inherit; opacity: 0.85; }
3050
+ .vela-field-cell { display: flex; align-items: center; gap: 8px; justify-self: start; }
3051
+ .vela-field-bool { display: flex; align-items: center; gap: 8px; min-height: 22px; }
3052
+ .vela-field-stacked { display: flex; flex-direction: column; gap: 8px; }
3053
+ .vela-field-stacked-head { display: flex; align-items: center; justify-content: center; gap: 8px; }
3054
+ .vela-field-inline { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
3055
+ .vela-field-inline-item { display: flex; align-items: center; gap: 8px; }
3056
+ .vela-field-toggle-label { display: flex; align-items: center; gap: 8px; }
3057
+ .vela-field-section {
3058
+ grid-column: 1 / -1;
3059
+ margin: 24px 0 0;
3060
+ padding-bottom: 8px;
3061
+ font-size: var(--vela-font-size-sm);
3062
+ font-weight: 700;
3063
+ letter-spacing: 0.05em;
3064
+ text-transform: uppercase;
3065
+ color: var(--vela-fg-muted);
3066
+ }
3067
+ .vela-field-section[data-variant='inputs'] {
3068
+ margin: 0;
3069
+ padding: 20px 0 8px;
3070
+ font-size: 11px;
3071
+ font-weight: 600;
3072
+ }
3073
+ .vela-field-section[data-variant='inputs'][data-first] { padding-top: 4px; }
3074
+ .vela-field-sep { grid-column: 1 / -1; height: 14px; }
3075
+ .vela-field-dim { opacity: 0.4 !important; pointer-events: none !important; }
3076
+ `;
3077
+
3078
+ // src/ui/components/field/view.ts
3079
+ function ensureFieldStyles(doc = document) {
3080
+ injectStyles(FIELD_STYLE_ID, FIELD_CSS, doc);
3081
+ }
3082
+ function fieldGrid(opts = {}) {
3083
+ ensureFieldStyles();
3084
+ const el = document.createElement("div");
3085
+ el.className = "vela-field-grid";
3086
+ const variant = opts.variant ?? "inputs";
3087
+ el.dataset.variant = variant;
3088
+ if (opts.mobile) el.dataset.mobile = "";
3089
+ el.style.gridTemplateColumns = fieldGridColumns(variant, opts.mobile === true);
3090
+ return el;
3091
+ }
3092
+ function fieldSection(title, opts = {}) {
3093
+ ensureFieldStyles();
3094
+ const el = document.createElement("div");
3095
+ el.className = "vela-field-section";
3096
+ if (opts.variant) el.dataset.variant = opts.variant;
3097
+ if (opts.first) el.dataset.first = "";
3098
+ el.textContent = title;
3099
+ return el;
3100
+ }
3101
+ function fieldSeparator() {
3102
+ ensureFieldStyles();
3103
+ const el = document.createElement("div");
3104
+ el.className = "vela-field-sep";
3105
+ return el;
3106
+ }
3107
+ function labelEl(text, opts) {
3108
+ const el = opts.id ? document.createElement("label") : document.createElement("span");
3109
+ if (opts.id && el instanceof HTMLLabelElement) el.htmlFor = opts.id;
3110
+ el.className = "vela-field-label";
3111
+ if (opts.size) el.dataset.size = opts.size;
3112
+ el.textContent = text;
3113
+ return el;
3114
+ }
3115
+ function asList(control) {
3116
+ if (!control) return [];
3117
+ return Array.isArray(control) ? control : [control];
3118
+ }
3119
+ function applyClass(el, extra) {
3120
+ if (extra) {
3121
+ for (const c of extra.split(/\s+/)) if (c) el.classList.add(c);
3122
+ }
3123
+ }
3124
+ function dimControls(controls, on) {
3125
+ for (const c of controls) {
3126
+ if (c.dataset.sdSelfGated === "1") continue;
3127
+ c.classList.toggle("vela-field-dim", !on);
3128
+ }
3129
+ }
3130
+ function fieldRow(opts) {
3131
+ ensureFieldStyles();
3132
+ const size = opts.labelSize;
3133
+ if (opts.inline) {
3134
+ const wrap2 = document.createElement("div");
3135
+ wrap2.className = "vela-field-span vela-field-inline";
3136
+ for (const item of opts.inline) {
3137
+ const it = document.createElement("div");
3138
+ it.className = "vela-field-inline-item";
3139
+ if (item.toggleFirst) it.style.flexDirection = "row-reverse";
3140
+ if (item.label) {
3141
+ const lbl = labelEl(item.label, { id: item.id, size });
3142
+ if (item.toggleFirst) lbl.style.cursor = "pointer";
3143
+ it.appendChild(lbl);
3144
+ }
3145
+ if (item.fit) it.appendChild(item.control);
3146
+ else {
3147
+ const cw = document.createElement("div");
3148
+ cw.style.width = "100px";
3149
+ cw.appendChild(item.control);
3150
+ it.appendChild(cw);
3151
+ }
3152
+ wrap2.appendChild(it);
3153
+ }
3154
+ if (opts.info) wrap2.appendChild(opts.info);
3155
+ applyClass(wrap2, opts.className);
3156
+ return wrap2;
3157
+ }
3158
+ if (opts.bool) {
3159
+ const wrap2 = document.createElement("div");
3160
+ wrap2.className = "vela-field-span vela-field-bool";
3161
+ const sw = new Switch({
3162
+ id: opts.toggle?.id ?? opts.id,
3163
+ checked: opts.toggle?.checked ?? false,
3164
+ size: "md",
3165
+ tone: "bright",
3166
+ onChange: (v) => opts.toggle?.onChange(v)
3167
+ });
3168
+ wrap2.append(sw.el, labelEl(opts.label, { size }));
3169
+ if (opts.info) wrap2.appendChild(opts.info);
3170
+ if (opts.toggle?.get) {
3171
+ const get = opts.toggle.get;
3172
+ wrap2.addEventListener("vela-sync", () => {
3173
+ sw.setChecked(get());
3174
+ });
3175
+ }
3176
+ applyClass(wrap2, opts.className);
3177
+ return wrap2;
3178
+ }
3179
+ if (opts.stacked) {
3180
+ const wrap2 = document.createElement("div");
3181
+ wrap2.className = "vela-field-span vela-field-stacked";
3182
+ const head = document.createElement("div");
3183
+ head.className = "vela-field-stacked-head";
3184
+ head.appendChild(labelEl(opts.label, { id: opts.id, size }));
3185
+ if (opts.info) head.appendChild(opts.info);
3186
+ wrap2.appendChild(head);
3187
+ for (const c of asList(opts.control)) wrap2.appendChild(c);
3188
+ applyClass(wrap2, opts.className);
3189
+ return wrap2;
3190
+ }
3191
+ const wrap = document.createElement("div");
3192
+ wrap.className = "vela-field-row";
3193
+ const controls = asList(opts.control);
3194
+ if (opts.toggle) {
3195
+ const left = document.createElement("div");
3196
+ left.className = "vela-field-toggle-label";
3197
+ const sw = new Switch({
3198
+ id: opts.toggle.id ?? opts.id,
3199
+ checked: opts.toggle.checked,
3200
+ size: "md",
3201
+ tone: "bright",
3202
+ onChange: (v) => {
3203
+ opts.toggle.onChange(v);
3204
+ if (controls.length > 0) dimControls(controls, v);
3205
+ }
3206
+ });
3207
+ left.append(sw.el, labelEl(opts.label, { size }));
3208
+ wrap.appendChild(left);
3209
+ if (controls.length > 0) dimControls(controls, opts.toggle.checked);
3210
+ if (opts.toggle.get) {
3211
+ const get = opts.toggle.get;
3212
+ wrap.addEventListener("vela-sync", () => {
3213
+ const v = get();
3214
+ sw.setChecked(v);
3215
+ if (controls.length > 0) dimControls(controls, v);
3216
+ });
3217
+ }
3218
+ } else {
3219
+ wrap.appendChild(labelEl(opts.label, { id: opts.id, size }));
3220
+ }
3221
+ const cell = document.createElement("div");
3222
+ cell.className = "vela-field-cell";
3223
+ if (opts.controlWidth != null && controls.length === 1 && !opts.fit) {
3224
+ const cw = document.createElement("div");
3225
+ cw.style.width = `${opts.controlWidth}px`;
3226
+ cw.appendChild(controls[0]);
3227
+ cell.appendChild(cw);
3228
+ } else {
3229
+ for (const c of controls) cell.appendChild(c);
3230
+ }
3231
+ if (opts.info) cell.appendChild(opts.info);
3232
+ wrap.appendChild(cell);
3233
+ applyClass(wrap, opts.className);
3234
+ return wrap;
3235
+ }
3236
+ function buildFieldControl(desc) {
3237
+ if (desc.kind === "number") {
3238
+ const ni = new NumberInput({
3239
+ id: desc.id,
3240
+ value: desc.value,
3241
+ min: desc.min,
3242
+ max: desc.max,
3243
+ step: desc.step,
3244
+ integer: desc.integer,
3245
+ size: "md",
3246
+ fill: desc.fill,
3247
+ compact: desc.compact,
3248
+ commit: desc.commit,
3249
+ steppers: desc.steppers,
3250
+ clamp: desc.clamp,
3251
+ placeholder: desc.placeholder,
3252
+ emptyValue: desc.emptyValue,
3253
+ title: desc.title,
3254
+ onChange: desc.onChange
3255
+ });
3256
+ if (desc.sync) {
3257
+ const sync = desc.sync;
3258
+ ni.el.addEventListener("vela-sync", () => {
3259
+ ni.setValue(sync());
3260
+ });
3261
+ }
3262
+ return { el: ni.el, setValue: (v) => ni.setValue(Number(v)) };
3263
+ }
3264
+ if (desc.kind === "select") {
3265
+ const sel = new Select({
3266
+ id: desc.id,
3267
+ options: desc.options,
3268
+ value: desc.value,
3269
+ size: "md",
3270
+ fill: desc.fill,
3271
+ theme: desc.theme,
3272
+ list: desc.list,
3273
+ onChange: desc.onChange
3274
+ });
3275
+ if (desc.title) sel.el.title = desc.title;
3276
+ if (desc.sync) {
3277
+ const sync = desc.sync;
3278
+ sel.el.addEventListener("vela-sync", () => {
3279
+ sel.setValue(sync());
3280
+ });
3281
+ }
3282
+ return { el: sel.el, setValue: (v) => sel.setValue(String(v)) };
3283
+ }
3284
+ if (desc.kind === "switch") {
3285
+ const sw = new Switch({
3286
+ id: desc.id,
3287
+ checked: desc.checked,
3288
+ size: "md",
3289
+ tone: "bright",
3290
+ onChange: desc.onChange
3291
+ });
3292
+ if (desc.sync) {
3293
+ const sync = desc.sync;
3294
+ sw.el.addEventListener("vela-sync", () => {
3295
+ sw.setChecked(sync());
3296
+ });
3297
+ }
3298
+ return { el: sw.el, setValue: (v) => sw.setChecked(Boolean(v)) };
3299
+ }
3300
+ if (desc.kind === "color") {
3301
+ const el2 = colorField(desc.theme, desc.get, desc.onChange, {
3302
+ shape: "circle",
3303
+ id: desc.id,
3304
+ popover: desc.popover
3305
+ });
3306
+ if (desc.title) el2.title = desc.title;
3307
+ return { el: el2 };
3308
+ }
3309
+ if (desc.kind === "text") {
3310
+ const tf = new TextField({
3311
+ id: desc.id,
3312
+ value: desc.value,
3313
+ size: "md",
3314
+ fill: desc.fill,
3315
+ onChange: desc.onChange
3316
+ });
3317
+ if (desc.placeholder) tf.input.placeholder = desc.placeholder;
3318
+ if (desc.sync) {
3319
+ const sync = desc.sync;
3320
+ tf.el.addEventListener("vela-sync", () => {
3321
+ tf.setValue(sync());
3322
+ });
3323
+ }
3324
+ return { el: tf.el, setValue: (v) => tf.setValue(String(v)) };
3325
+ }
3326
+ if (desc.kind === "textarea") {
3327
+ const ta = new TextArea({
3328
+ id: desc.id,
3329
+ value: desc.value,
3330
+ rows: desc.rows,
3331
+ autoGrow: desc.autoGrow,
3332
+ maxLines: desc.maxLines,
3333
+ placeholder: desc.placeholder,
3334
+ onChange: desc.onChange
3335
+ });
3336
+ if (desc.sync) {
3337
+ const sync = desc.sync;
3338
+ ta.el.addEventListener("vela-sync", () => {
3339
+ ta.setValue(sync());
3340
+ });
3341
+ }
3342
+ return { el: ta.el, setValue: (v) => ta.setValue(String(v)) };
3343
+ }
3344
+ const el = widthField(desc.theme, desc.get, desc.onChange);
3345
+ if (desc.title) el.title = desc.title;
3346
+ return { el };
3347
+ }
3348
+
2615
3349
  Object.defineProperty(exports, "normalizeProps", {
2616
3350
  enumerable: true,
2617
3351
  get: function () { return vanilla.normalizeProps; }
@@ -2623,22 +3357,30 @@ Object.defineProperty(exports, "spreadProps", {
2623
3357
  exports.ColorField = ColorField;
2624
3358
  exports.Dialog = Dialog;
2625
3359
  exports.Drawer = Drawer;
3360
+ exports.FIELD_FOCUS_CSS = FIELD_FOCUS_CSS;
3361
+ exports.FIELD_FOCUS_RING = FIELD_FOCUS_RING;
3362
+ exports.FIELD_GAP_PX = FIELD_GAP_PX;
3363
+ exports.GlyphSelect = GlyphSelect;
2626
3364
  exports.KeymapManager = KeymapManager;
2627
3365
  exports.Menu = Menu;
2628
3366
  exports.NumberInput = NumberInput;
2629
3367
  exports.Popover = Popover;
2630
3368
  exports.Select = Select;
2631
3369
  exports.Switch = Switch;
3370
+ exports.TextArea = TextArea;
2632
3371
  exports.TextField = TextField;
2633
3372
  exports.Tooltip = Tooltip;
3373
+ exports.WIDTH_FIELD_OPTIONS = WIDTH_FIELD_OPTIONS;
2634
3374
  exports.applyPlotOverlayTokens = applyPlotOverlayTokens;
2635
3375
  exports.applyThemeTokens = applyThemeTokens;
2636
3376
  exports.blendOver = blendOver;
2637
3377
  exports.buildColorPicker = buildColorPicker;
3378
+ exports.buildFieldControl = buildFieldControl;
2638
3379
  exports.buildPalette = buildPalette;
2639
3380
  exports.clampNumber = clampNumber;
2640
3381
  exports.closeColorPopover = closeColorPopover;
2641
3382
  exports.closeOpenPopovers = closeOpenPopovers;
3383
+ exports.closeWidthPopover = closeWidthPopover;
2642
3384
  exports.colorField = colorField;
2643
3385
  exports.colorPickerController = colorPickerController;
2644
3386
  exports.combineColor = combineColor;
@@ -2646,7 +3388,14 @@ exports.decorateSelectScroll = decorateSelectScroll;
2646
3388
  exports.dialogController = dialogController;
2647
3389
  exports.drawerController = drawerController;
2648
3390
  exports.ensureUIHost = ensureUIHost;
3391
+ exports.eventDismissedPopover = eventDismissedPopover;
3392
+ exports.fieldGrid = fieldGrid;
3393
+ exports.fieldGridColumns = fieldGridColumns;
3394
+ exports.fieldRow = fieldRow;
3395
+ exports.fieldSection = fieldSection;
3396
+ exports.fieldSeparator = fieldSeparator;
2649
3397
  exports.fillSelectList = fillSelectList;
3398
+ exports.glyphSelectController = glyphSelectController;
2650
3399
  exports.hslHex = hslHex;
2651
3400
  exports.iconEl = iconEl;
2652
3401
  exports.iconMarkup = iconMarkup;
@@ -2654,11 +3403,13 @@ exports.injectStyles = injectStyles;
2654
3403
  exports.insetRect = insetRect;
2655
3404
  exports.intersectRects = intersectRects;
2656
3405
  exports.isPopoverOpen = isPopoverOpen;
3406
+ exports.lineWidthGlyph = lineWidthGlyph;
2657
3407
  exports.menuController = menuController;
2658
3408
  exports.nextUid = nextUid;
2659
3409
  exports.numberInputController = numberInputController;
2660
3410
  exports.openPopoverTrigger = openPopoverTrigger;
2661
3411
  exports.openSelectList = openSelectList;
3412
+ exports.overlayScrollbarCss = overlayScrollbarCss;
2662
3413
  exports.placePopover = placePopover;
2663
3414
  exports.popoverController = popoverController;
2664
3415
  exports.registerIcon = registerIcon;
@@ -2669,9 +3420,12 @@ exports.splitColor = splitColor;
2669
3420
  exports.svg16 = svg16;
2670
3421
  exports.svg24 = svg24;
2671
3422
  exports.switchController = switchController;
3423
+ exports.textAreaController = textAreaController;
2672
3424
  exports.textFieldController = textFieldController;
2673
3425
  exports.toggleSelectList = toggleSelectList;
2674
3426
  exports.tooltipController = tooltipController;
2675
3427
  exports.transparencyChecker = transparencyChecker;
2676
3428
  exports.viewportRect = viewportRect;
3429
+ exports.widthField = widthField;
3430
+ exports.widthFieldOptions = widthFieldOptions;
2677
3431
  exports.withAlpha = withAlpha;