@kitlangton/knobs 0.2.0 → 0.3.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 (42) hide show
  1. package/README.md +135 -17
  2. package/dist/dev/{chunk-gezhc24g.js → chunk-7xgkerd2.js} +14 -5
  3. package/dist/dev/{chunk-5cr85vv6.js → chunk-jx1ktap3.js} +7843 -7933
  4. package/dist/dev/{chunk-693jd1gj.js → chunk-pmkvb9ep.js} +1544 -160
  5. package/dist/dev/{chunk-4km87zfx.js → chunk-v8ssbcbk.js} +18 -7
  6. package/dist/dev/index.js +187 -31
  7. package/dist/index.js +197 -33
  8. package/dist/panel.js +20260 -18462
  9. package/dist/types/core/keys.d.ts +2 -0
  10. package/dist/types/core/remote.d.ts +2 -0
  11. package/dist/types/core/schema.d.ts +5 -0
  12. package/dist/types/core/store.d.ts +29 -2
  13. package/dist/types/core/unlock.d.ts +77 -0
  14. package/dist/types/index.d.ts +1 -0
  15. package/dist/types/panel/attach.d.ts +2 -0
  16. package/dist/types/panel/crumbs.d.ts +19 -0
  17. package/dist/types/panel/editors.d.ts +9 -0
  18. package/dist/types/panel/feel.d.ts +103 -0
  19. package/dist/types/panel/give.d.ts +34 -1
  20. package/dist/types/panel/gpu/color.d.ts +4 -4
  21. package/dist/types/panel/gpu/renderer.d.ts +32 -0
  22. package/dist/types/panel/gpu/shader.wgsl.d.ts +1 -1
  23. package/dist/types/panel/index.d.ts +2 -1
  24. package/dist/types/panel/inspector.d.ts +1 -1
  25. package/dist/types/panel/lens.d.ts +113 -0
  26. package/dist/types/panel/motion.d.ts +2 -0
  27. package/dist/types/panel/page-hints.d.ts +2 -1
  28. package/dist/types/panel/prefs.d.ts +2 -2
  29. package/dist/types/panel/presence.d.ts +20 -0
  30. package/dist/types/panel/scope.d.ts +29 -0
  31. package/dist/types/panel/slider.d.ts +21 -4
  32. package/dist/types/panel/tab-transition.d.ts +15 -0
  33. package/dist/types/panel/tabs.d.ts +1 -1
  34. package/dist/types/panel/toggle.d.ts +27 -0
  35. package/dist/types/ui/look.d.ts +1 -1
  36. package/dist/types/ui/scrollbar.d.ts +12 -0
  37. package/dist/types/ui/split-model.d.ts +4 -3
  38. package/dist/types/ui/split.d.ts +8 -6
  39. package/dist/ui-react.js +232 -132
  40. package/dist/ui-solid.js +232 -132
  41. package/dist/ui.js +285 -174
  42. package/package.json +1 -1
package/dist/ui-react.js CHANGED
@@ -1126,7 +1126,7 @@ var open = springEasing(2 * Math.PI / 0.22, 1);
1126
1126
  var activityCss = `
1127
1127
  :where(.knobs-activity,.knobs-activity *){box-sizing:border-box}
1128
1128
  :where(.knobs-activity){color-scheme:dark}
1129
- .knobs-ui.knobs-activity{position:relative;display:block;height:100%;overflow:auto;overflow-anchor:none;outline:none;scrollbar-width:thin;overscroll-behavior:contain;user-select:none;-webkit-user-select:none;
1129
+ .knobs-ui.knobs-activity{position:relative;display:block;height:100%;overflow:auto;overflow-anchor:none;outline:none;scrollbar-width:none;overscroll-behavior:contain;user-select:none;-webkit-user-select:none;
1130
1130
  font:var(--ku-font-weight) var(--ku-font-size)/1.35 var(--ku-font);color:var(--ku-text);text-align:left;letter-spacing:normal;-webkit-font-smoothing:antialiased;
1131
1131
  --ka-gutter:var(--k-activity-gutter,28px);--ka-row:var(--k-activity-row,var(--ku-row));--ka-row-child:var(--k-activity-row-child,calc(var(--ka-row) - 6px));
1132
1132
  --ka-line:var(--k-activity-line,var(--ku-line-hi));--ka-select:var(--k-activity-select,var(--ku-wash-hi));--ka-hover:var(--k-activity-hover,var(--ku-wash));
@@ -1477,6 +1477,121 @@ var readoutCss = `
1477
1477
  @media(prefers-reduced-motion:reduce){.knobs-rd :where(*){transition:none!important}}
1478
1478
  `;
1479
1479
 
1480
+ // src/core/keys.ts
1481
+ var MODS = ["Ctrl", "Alt", "Shift", "Meta"];
1482
+ var ALIASES = {
1483
+ ctrl: "Ctrl",
1484
+ control: "Ctrl",
1485
+ "⌃": "Ctrl",
1486
+ alt: "Alt",
1487
+ option: "Alt",
1488
+ opt: "Alt",
1489
+ "⌥": "Alt",
1490
+ shift: "Shift",
1491
+ "⇧": "Shift",
1492
+ meta: "Meta",
1493
+ cmd: "Meta",
1494
+ command: "Meta",
1495
+ "⌘": "Meta",
1496
+ mod: "Mod"
1497
+ };
1498
+ var NAMED = {
1499
+ " ": "Space",
1500
+ space: "Space",
1501
+ spacebar: "Space",
1502
+ esc: "Escape",
1503
+ escape: "Escape",
1504
+ enter: "Enter",
1505
+ return: "Enter",
1506
+ tab: "Tab",
1507
+ backspace: "Backspace",
1508
+ delete: "Delete",
1509
+ del: "Delete",
1510
+ up: "ArrowUp",
1511
+ down: "ArrowDown",
1512
+ left: "ArrowLeft",
1513
+ right: "ArrowRight",
1514
+ arrowup: "ArrowUp",
1515
+ arrowdown: "ArrowDown",
1516
+ arrowleft: "ArrowLeft",
1517
+ arrowright: "ArrowRight",
1518
+ home: "Home",
1519
+ end: "End",
1520
+ pageup: "PageUp",
1521
+ pagedown: "PageDown"
1522
+ };
1523
+ var isApple = () => typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.userAgentData?.platform ?? navigator.platform ?? "");
1524
+ var keyName = (raw) => {
1525
+ if (!raw)
1526
+ return "";
1527
+ const named = NAMED[raw.toLowerCase()];
1528
+ if (named)
1529
+ return named;
1530
+ if (raw.length === 1)
1531
+ return raw.toUpperCase();
1532
+ if (/^f\d{1,2}$/i.test(raw))
1533
+ return raw.toUpperCase();
1534
+ return raw.charAt(0).toUpperCase() + raw.slice(1);
1535
+ };
1536
+ function normalizeCombo(combo) {
1537
+ const trimmed = combo.trim();
1538
+ if (!trimmed)
1539
+ return "";
1540
+ const parts = trimmed === "+" ? ["+"] : trimmed.replace(/\+\+$/, "+plus").split("+");
1541
+ const mods = new Set;
1542
+ let key = "";
1543
+ for (const part of parts) {
1544
+ const alias = ALIASES[part.trim().toLowerCase()] ?? ALIASES[part.trim()];
1545
+ if (alias === "Mod")
1546
+ mods.add(isApple() ? "Meta" : "Ctrl");
1547
+ else if (alias)
1548
+ mods.add(alias);
1549
+ else
1550
+ key = part === "plus" ? "+" : keyName(part.trim() || part);
1551
+ }
1552
+ if (!key)
1553
+ return "";
1554
+ return [...MODS.filter((m) => mods.has(m)), key].join("+");
1555
+ }
1556
+ var GLYPHS = { Ctrl: "⌃", Alt: "⌥", Shift: "⇧", Meta: "⌘" };
1557
+ var WORDS = { Ctrl: "Ctrl", Alt: "Alt", Shift: "Shift", Meta: "Meta" };
1558
+ var KEY_GLYPHS = {
1559
+ ArrowUp: "↑",
1560
+ ArrowDown: "↓",
1561
+ ArrowLeft: "←",
1562
+ ArrowRight: "→",
1563
+ Backspace: "⌫",
1564
+ Delete: "⌦",
1565
+ Enter: "↩",
1566
+ Escape: "Esc",
1567
+ Tab: "⇥",
1568
+ PageUp: "PgUp",
1569
+ PageDown: "PgDn"
1570
+ };
1571
+ function comboParts(combo, apple = isApple()) {
1572
+ const norm = normalizeCombo(combo);
1573
+ if (!norm)
1574
+ return [];
1575
+ const parts = norm === "+" ? ["+"] : norm.split("+");
1576
+ if (norm.endsWith("++"))
1577
+ parts.splice(-2, 2, "+");
1578
+ const key = parts.pop();
1579
+ const mods = parts;
1580
+ const shown = KEY_GLYPHS[key] ?? key;
1581
+ return apple ? [...mods.map((m) => GLYPHS[m]), shown] : [...mods.map((m) => WORDS[m]), shown];
1582
+ }
1583
+ function isTypingTarget(target) {
1584
+ const el = target;
1585
+ if (!el || typeof el.tagName !== "string")
1586
+ return false;
1587
+ const tag = el.tagName;
1588
+ if (tag === "SELECT" || el.isContentEditable)
1589
+ return true;
1590
+ if (tag === "TEXTAREA")
1591
+ return !el.readOnly;
1592
+ return tag === "INPUT" && !el.readOnly && !["range", "checkbox", "radio", "button", "submit", "reset", "color"].includes(el.type);
1593
+ }
1594
+
1480
1595
  // src/ui/look.ts
1481
1596
  var ease = springEasing(26, 1);
1482
1597
  var TOKENS = {
@@ -1594,6 +1709,17 @@ var css = `
1594
1709
  }
1595
1710
  @supports not (animation-timeline:scroll()){.knobs-scroll-fade-x{text-overflow:ellipsis}}
1596
1711
 
1712
+ /* Overlay scrollbars (trees, activity rails): a thin thumb over the content, so the rows never
1713
+ give up width to a scrollbar as they start or stop overflowing. It shows while the content
1714
+ scrolls or the pointer is over it. */
1715
+ :is(.knobs-tree,.knobs-activity)::-webkit-scrollbar{display:none}
1716
+ .knobs-scrollbar{position:sticky;top:0;z-index:3;height:0;pointer-events:none}
1717
+ .knobs-scrollbar[hidden]{display:none}
1718
+ .knobs-scrollbar-thumb{position:absolute;top:0;right:2px;width:5px;border-radius:3px;background:color-mix(in srgb,var(--ku-text) 30%,transparent);opacity:0;
1719
+ transition:opacity .3s ease-out,background .15s}
1720
+ :hover>.knobs-scrollbar>.knobs-scrollbar-thumb,.knobs-scrollbar:is([data-active],[data-dragging])>.knobs-scrollbar-thumb{opacity:1;pointer-events:auto;transition-duration:.12s,.15s}
1721
+ .knobs-scrollbar-thumb:hover,.knobs-scrollbar[data-dragging]>.knobs-scrollbar-thumb{background:color-mix(in srgb,var(--ku-text) 48%,transparent)}
1722
+
1597
1723
  /* Split: panes along a row or column, hairline dividers with a wider grip. */
1598
1724
  .knobs-split{position:relative;display:flex;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden}
1599
1725
  .knobs-split[data-direction="column"]{flex-direction:column}
@@ -1630,7 +1756,11 @@ var css = `
1630
1756
  .knobs-split[data-direction="column"]>.knobs-pane[data-edge="start"]>.knobs-pane-rail{bottom:0}
1631
1757
  .knobs-split[data-direction="column"]>.knobs-pane[data-edge="end"]>.knobs-pane-rail{top:0}
1632
1758
  .knobs-pane[data-collapsed]>.knobs-pane-rail{pointer-events:auto}
1633
- .knobs-ui .knobs-pane[data-collapsed]>.knobs-pane-rail:is(:hover,:focus-visible){background-image:linear-gradient(var(--ku-wash),var(--ku-wash));color:var(--ku-text)}
1759
+ /* Its hover wash fades, rather than going in one frame as a click opens the pane. */
1760
+ .knobs-ui .knobs-pane-rail::before{content:"";position:absolute;inset:0;border-radius:inherit;background:var(--ku-wash);opacity:0;transition:opacity .15s;pointer-events:none}
1761
+ .knobs-pane-rail>*{position:relative}
1762
+ .knobs-ui .knobs-pane[data-collapsed]>.knobs-pane-rail:is(:hover,:focus-visible){color:var(--ku-text)}
1763
+ .knobs-ui .knobs-pane[data-collapsed]>.knobs-pane-rail:is(:hover,:focus-visible)::before{opacity:1}
1634
1764
  .knobs-pane-rail-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
1635
1765
  .knobs-pane-chevron{display:flex;flex:none;width:11px;height:11px;writing-mode:horizontal-tb;color:var(--ku-dim)}
1636
1766
  .knobs-pane-turn{display:flex;width:100%;height:100%;transform-origin:50% 50%}
@@ -1667,7 +1797,7 @@ var css = `
1667
1797
  :root[data-knobs-resizing] :is(iframe,canvas:not(.knobs-gpu-canvas)){pointer-events:none}
1668
1798
 
1669
1799
  /* Tree: one scroller, rows placed by transform, only those near the view in the DOM. */
1670
- .knobs-tree{position:relative;overflow:auto;height:100%;outline:none;scrollbar-width:thin;overscroll-behavior:contain;user-select:none;-webkit-user-select:none}
1800
+ .knobs-tree{position:relative;overflow:auto;height:100%;outline:none;scrollbar-width:none;overscroll-behavior:contain;user-select:none;-webkit-user-select:none}
1671
1801
  .knobs-tree-space{position:relative;width:100%;pointer-events:none}
1672
1802
  .knobs-tree-row{position:absolute;left:var(--ku-row-x);right:var(--ku-row-x);top:0;display:flex;align-items:center;height:var(--ku-row);padding-right:calc(var(--ku-inset) - 4px);
1673
1803
  color:var(--ku-text);white-space:nowrap;cursor:default;will-change:transform;contain:layout style}
@@ -1819,7 +1949,6 @@ var tune = (s, m) => {
1819
1949
  s.zeta = m.zeta;
1820
1950
  return s;
1821
1951
  };
1822
- var isTypingTarget = (t) => t instanceof HTMLElement && (t.isContentEditable || t instanceof HTMLSelectElement || t instanceof HTMLTextAreaElement || t instanceof HTMLInputElement && !t.readOnly && !["range", "checkbox", "radio", "button"].includes(t.type));
1823
1952
  function notifyLayout(doc) {
1824
1953
  doc.dispatchEvent(new Event("knobs-layout"));
1825
1954
  }
@@ -1859,6 +1988,79 @@ var ICONS = {
1859
1988
  unlock: svg('<rect x="3.5" y="7" width="9" height="6.5" rx="1.2"/><path d="M5.5 7V5a2.5 2.5 0 0 1 4.8-1"/>')
1860
1989
  };
1861
1990
 
1991
+ // src/ui/scrollbar.ts
1992
+ function overlayScrollbar(scroller, content) {
1993
+ const doc = scroller.ownerDocument;
1994
+ const win = doc.defaultView;
1995
+ const track = doc.createElement("div");
1996
+ track.className = "knobs-scrollbar";
1997
+ track.setAttribute("aria-hidden", "true");
1998
+ const thumb = doc.createElement("div");
1999
+ thumb.className = "knobs-scrollbar-thumb";
2000
+ track.append(thumb);
2001
+ scroller.prepend(track);
2002
+ let size = 0;
2003
+ const update = () => {
2004
+ const view = scroller.clientHeight;
2005
+ const over = scroller.scrollHeight - view;
2006
+ const shown = over > 1 && view > 0;
2007
+ track.hidden = !shown;
2008
+ if (!shown)
2009
+ return;
2010
+ size = Math.max(20, view * view / (view + over));
2011
+ const y = Math.min(over, Math.max(0, scroller.scrollTop)) / over * (view - size);
2012
+ thumb.style.height = `${size.toFixed(1)}px`;
2013
+ thumb.style.transform = `translate3d(0,${y.toFixed(1)}px,0)`;
2014
+ };
2015
+ let idle = 0;
2016
+ const onScroll = () => {
2017
+ update();
2018
+ track.dataset.active = "";
2019
+ win.clearTimeout(idle);
2020
+ idle = win.setTimeout(() => delete track.dataset.active, 800);
2021
+ };
2022
+ scroller.addEventListener("scroll", onScroll, { passive: true });
2023
+ let drag;
2024
+ thumb.addEventListener("pointerdown", (e) => {
2025
+ if (e.button !== 0)
2026
+ return;
2027
+ e.preventDefault();
2028
+ e.stopPropagation();
2029
+ drag = { y: e.clientY, top: scroller.scrollTop, pointer: e.pointerId };
2030
+ thumb.setPointerCapture(e.pointerId);
2031
+ track.dataset.dragging = "";
2032
+ });
2033
+ thumb.addEventListener("pointermove", (e) => {
2034
+ if (!drag || e.pointerId !== drag.pointer)
2035
+ return;
2036
+ const view = scroller.clientHeight;
2037
+ const over = scroller.scrollHeight - view;
2038
+ scroller.scrollTop = drag.top + (e.clientY - drag.y) * over / Math.max(1, view - size);
2039
+ });
2040
+ const end = (e) => {
2041
+ if (!drag || e.pointerId !== drag.pointer)
2042
+ return;
2043
+ drag = undefined;
2044
+ delete track.dataset.dragging;
2045
+ };
2046
+ thumb.addEventListener("pointerup", end);
2047
+ thumb.addEventListener("pointercancel", end);
2048
+ thumb.addEventListener("lostpointercapture", end);
2049
+ const ro = new ResizeObserver(update);
2050
+ ro.observe(scroller);
2051
+ ro.observe(content);
2052
+ update();
2053
+ return {
2054
+ update,
2055
+ dispose() {
2056
+ ro.disconnect();
2057
+ win.clearTimeout(idle);
2058
+ scroller.removeEventListener("scroll", onScroll);
2059
+ track.remove();
2060
+ }
2061
+ };
2062
+ }
2063
+
1862
2064
  // src/ui/activity.ts
1863
2065
  var ACTIVITY_ICONS = {
1864
2066
  you: svg('<circle cx="8" cy="5.5" r="2.5"/><path d="M3.2 13.5c.8-2.4 2.6-3.8 4.8-3.8s4 1.4 4.8 3.8"/>'),
@@ -1871,7 +2073,7 @@ var ACTIVITY_ICONS = {
1871
2073
  shipped: svg('<circle cx="8" cy="8" r="5.8"/><path d="m5.6 8.2 1.7 1.7 3.2-3.6"/>')
1872
2074
  };
1873
2075
  var PLAY = '<svg viewBox="0 0 8 8" aria-hidden="true"><path d="M1.5 1v6l5.2-3Z" fill="currentColor"/></svg>';
1874
- var REFLOW = springMotion({ duration: 0.4, bounce: 0 });
2076
+ var REFLOW = springMotion({ duration: 0.3, bounce: 0 });
1875
2077
  var OPEN = springMotion({ duration: 0.22, bounce: 0 });
1876
2078
  var CLOSE = springMotion({ duration: 0.18, bounce: 0 });
1877
2079
  var BLOOM = springMotion({ duration: 0.3, bounce: 0 });
@@ -1914,6 +2116,7 @@ function activity(container, options) {
1914
2116
  space.append(hl, line);
1915
2117
  root.append(empty, space);
1916
2118
  container.append(root);
2119
+ const scrollbar = overlayScrollbar(root, space);
1917
2120
  let heights = [32, 26];
1918
2121
  const measure = () => {
1919
2122
  syncFrame(root);
@@ -2537,6 +2740,7 @@ function activity(container, options) {
2537
2740
  destroy() {
2538
2741
  loop.stop();
2539
2742
  ro.disconnect();
2743
+ scrollbar.dispose();
2540
2744
  if (keysFrom === "page") {
2541
2745
  win.removeEventListener("keydown", onKey);
2542
2746
  win.removeEventListener("pointerdown", onPointer, true);
@@ -2883,11 +3087,7 @@ var SLIDE_BY = 0.12;
2883
3087
  var EXIT_BLUR = 3;
2884
3088
  var px = (x) => new Spring(x, LAYOUT.omega, LAYOUT.zeta, 0.05);
2885
3089
  var unit = (x) => new Spring(x, ENTER.omega, ENTER.zeta, 0.0005);
2886
- var drive = (s, m, target) => {
2887
- s.omega = m.omega;
2888
- s.zeta = m.zeta;
2889
- return s.set(target);
2890
- };
3090
+ var drive = (s, m, target) => tune(s, m).set(target);
2891
3091
  var probed = new Map;
2892
3092
  var smallest = (srcset) => {
2893
3093
  let best;
@@ -3648,7 +3848,7 @@ function bento(container, options) {
3648
3848
  }
3649
3849
  zoomIn(t.id);
3650
3850
  };
3651
- const typing = (target) => target instanceof HTMLElement && target !== root && (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName));
3851
+ const typing = (target) => target !== root && isTypingTarget(target);
3652
3852
  const reveal = (id) => {
3653
3853
  const r = rectOf(id);
3654
3854
  if (!r)
@@ -4637,15 +4837,7 @@ function claimsHintKey(doc, key) {
4637
4837
  const holder = doc;
4638
4838
  return key === PAGE_HINT_KEY && Boolean(holder[KEY]?.listening);
4639
4839
  }
4640
- var isTyping = (target) => {
4641
- const el = target;
4642
- if (!el || typeof el.tagName !== "string")
4643
- return false;
4644
- const tag = el.tagName;
4645
- if (tag === "TEXTAREA" || tag === "SELECT" || el.isContentEditable)
4646
- return true;
4647
- return tag === "INPUT" && !["range", "checkbox", "radio", "button", "submit", "reset", "color"].includes(el.type);
4648
- };
4840
+ var isTyping = isTypingTarget;
4649
4841
  var PRESSABLE = "button,a[href],summary,[role=button],[role=tab],[role=radio],[role=switch],[role=checkbox],[role=menuitem],[role=option]";
4650
4842
  function activateElement(el) {
4651
4843
  const pressable = el.matches(PRESSABLE) || el.tabIndex < 0 && !el.matches("input,select,textarea");
@@ -5025,110 +5217,6 @@ function layerPainter(doc, groups) {
5025
5217
  };
5026
5218
  }
5027
5219
 
5028
- // src/core/keys.ts
5029
- var MODS = ["Ctrl", "Alt", "Shift", "Meta"];
5030
- var ALIASES = {
5031
- ctrl: "Ctrl",
5032
- control: "Ctrl",
5033
- "⌃": "Ctrl",
5034
- alt: "Alt",
5035
- option: "Alt",
5036
- opt: "Alt",
5037
- "⌥": "Alt",
5038
- shift: "Shift",
5039
- "⇧": "Shift",
5040
- meta: "Meta",
5041
- cmd: "Meta",
5042
- command: "Meta",
5043
- "⌘": "Meta",
5044
- mod: "Mod"
5045
- };
5046
- var NAMED = {
5047
- " ": "Space",
5048
- space: "Space",
5049
- spacebar: "Space",
5050
- esc: "Escape",
5051
- escape: "Escape",
5052
- enter: "Enter",
5053
- return: "Enter",
5054
- tab: "Tab",
5055
- backspace: "Backspace",
5056
- delete: "Delete",
5057
- del: "Delete",
5058
- up: "ArrowUp",
5059
- down: "ArrowDown",
5060
- left: "ArrowLeft",
5061
- right: "ArrowRight",
5062
- arrowup: "ArrowUp",
5063
- arrowdown: "ArrowDown",
5064
- arrowleft: "ArrowLeft",
5065
- arrowright: "ArrowRight",
5066
- home: "Home",
5067
- end: "End",
5068
- pageup: "PageUp",
5069
- pagedown: "PageDown"
5070
- };
5071
- var isApple = () => typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.userAgentData?.platform ?? navigator.platform ?? "");
5072
- var keyName = (raw) => {
5073
- if (!raw)
5074
- return "";
5075
- const named = NAMED[raw.toLowerCase()];
5076
- if (named)
5077
- return named;
5078
- if (raw.length === 1)
5079
- return raw.toUpperCase();
5080
- if (/^f\d{1,2}$/i.test(raw))
5081
- return raw.toUpperCase();
5082
- return raw.charAt(0).toUpperCase() + raw.slice(1);
5083
- };
5084
- function normalizeCombo(combo) {
5085
- const trimmed = combo.trim();
5086
- if (!trimmed)
5087
- return "";
5088
- const parts = trimmed === "+" ? ["+"] : trimmed.replace(/\+\+$/, "+plus").split("+");
5089
- const mods = new Set;
5090
- let key = "";
5091
- for (const part of parts) {
5092
- const alias = ALIASES[part.trim().toLowerCase()] ?? ALIASES[part.trim()];
5093
- if (alias === "Mod")
5094
- mods.add(isApple() ? "Meta" : "Ctrl");
5095
- else if (alias)
5096
- mods.add(alias);
5097
- else
5098
- key = part === "plus" ? "+" : keyName(part.trim() || part);
5099
- }
5100
- if (!key)
5101
- return "";
5102
- return [...MODS.filter((m) => mods.has(m)), key].join("+");
5103
- }
5104
- var GLYPHS = { Ctrl: "⌃", Alt: "⌥", Shift: "⇧", Meta: "⌘" };
5105
- var WORDS = { Ctrl: "Ctrl", Alt: "Alt", Shift: "Shift", Meta: "Meta" };
5106
- var KEY_GLYPHS = {
5107
- ArrowUp: "↑",
5108
- ArrowDown: "↓",
5109
- ArrowLeft: "←",
5110
- ArrowRight: "→",
5111
- Backspace: "⌫",
5112
- Delete: "⌦",
5113
- Enter: "↩",
5114
- Escape: "Esc",
5115
- Tab: "⇥",
5116
- PageUp: "PgUp",
5117
- PageDown: "PgDn"
5118
- };
5119
- function comboParts(combo, apple = isApple()) {
5120
- const norm = normalizeCombo(combo);
5121
- if (!norm)
5122
- return [];
5123
- const parts = norm === "+" ? ["+"] : norm.split("+");
5124
- if (norm.endsWith("++"))
5125
- parts.splice(-2, 2, "+");
5126
- const key = parts.pop();
5127
- const mods = parts;
5128
- const shown = KEY_GLYPHS[key] ?? key;
5129
- return apple ? [...mods.map((m) => GLYPHS[m]), shown] : [...mods.map((m) => WORDS[m]), shown];
5130
- }
5131
-
5132
5220
  // src/ui/layer.ts
5133
5221
  var lookOf = (look) => ({ ...lookDefaults.current, ...look });
5134
5222
  var roots = new WeakMap;
@@ -5701,10 +5789,10 @@ function foldFaces(prev, p) {
5701
5789
  function chevronTurn(p, edge) {
5702
5790
  return (edge === "start" ? 180 : -180) * (1 - clamp01(p));
5703
5791
  }
5704
- function chevronFlip(f) {
5705
- return 2 * clamp01(f) - 1;
5792
+ function chevronFlip(f, edge) {
5793
+ return chevronTurn(f, edge);
5706
5794
  }
5707
- var FOLD_STYLE = { chevron: "flip", title: "travel", duration: 0.3 };
5795
+ var FOLD_STYLE = { chevron: "flip", title: "travel", duration: 0.25 };
5708
5796
  function foldStyle(options = {}) {
5709
5797
  const duration = options.duration;
5710
5798
  return {
@@ -5745,10 +5833,12 @@ function serializeLayout(layout) {
5745
5833
  }
5746
5834
 
5747
5835
  // src/ui/split.ts
5748
- var FLIP = springMotion({ duration: 0.18, bounce: 0 });
5836
+ var FLIP = springMotion({ duration: 0.25, bounce: 0 });
5837
+ var FLIP_DEPTH = 28;
5749
5838
  var SETTLE = springMotion({ duration: 0.3, bounce: 0 });
5750
5839
  var CONTENT_BLUR = 3;
5751
5840
  var LABEL_BLUR = 2;
5841
+ var COUNT_CLEAR = 0.12;
5752
5842
  var put = (el, prop, value) => {
5753
5843
  if (el.style.getPropertyValue(prop) !== value)
5754
5844
  el.style.setProperty(prop, value);
@@ -5885,7 +5975,12 @@ function split(container, options) {
5885
5975
  if (!row) {
5886
5976
  head.dataset.toggles = "";
5887
5977
  head.addEventListener("click", () => setCollapsed(spec.id, !collapsed[spec.id]));
5888
- }
5978
+ } else
5979
+ head.addEventListener("dblclick", (e) => {
5980
+ if (e.target.closest(".knobs-pane-btn"))
5981
+ return;
5982
+ setCollapsed(spec.id, true);
5983
+ });
5889
5984
  }
5890
5985
  inner.append(head);
5891
5986
  view.head = head;
@@ -6054,6 +6149,8 @@ function split(container, options) {
6054
6149
  const label = v.railLabel;
6055
6150
  const travel = morph && style.title === "travel" && Boolean(v.titleEl) && label.textContent === v.titleEl.textContent;
6056
6151
  v.el.toggleAttribute("data-travel", travel && folding);
6152
+ if (v.countEl)
6153
+ put(v.countEl, "opacity", travel && folding && p > 0 ? Math.max(0, 1 - p / COUNT_CLEAR).toFixed(3) : "");
6057
6154
  const k = 1 - p;
6058
6155
  if (travel && !folding) {
6059
6156
  put(label, "opacity", "0");
@@ -6081,8 +6178,9 @@ function split(container, options) {
6081
6178
  if (style.chevron === "turn")
6082
6179
  put(v.turn, "transform", k === 0 ? "" : `${move} rotate(${chevronTurn(p, v.edge).toFixed(2)}deg) ${size}`);
6083
6180
  else {
6084
- const w = chevronFlip(v.flip.x);
6085
- put(v.turn, "transform", k === 0 && w === 1 ? "" : `${move} ${size} scaleX(${w.toFixed(4)})`);
6181
+ const turn = chevronFlip(v.flip.x, v.edge);
6182
+ const over = v.flip.resting ? turn === 0 ? "" : "scaleX(-1)" : `perspective(${FLIP_DEPTH}px) rotateY(${turn.toFixed(2)}deg)`;
6183
+ put(v.turn, "transform", k === 0 && !over ? "" : `${move} ${size} ${over}`);
6086
6184
  }
6087
6185
  } else {
6088
6186
  const a = v.faces.rail;
@@ -8086,7 +8184,7 @@ function virtualWindow(scrollTop, height, rowHeight, count, overscan = 6) {
8086
8184
  }
8087
8185
 
8088
8186
  // src/ui/tree.ts
8089
- var MOVE = springMotion({ duration: 0.34, bounce: 0.02 });
8187
+ var MOVE = springMotion({ duration: 0.3, bounce: 0 });
8090
8188
  var EDGE = 28;
8091
8189
  function tree(container, options) {
8092
8190
  const doc = container.ownerDocument;
@@ -8125,6 +8223,7 @@ function tree(container, options) {
8125
8223
  empty.hidden = true;
8126
8224
  root.append(space, line, empty);
8127
8225
  container.append(root);
8226
+ const scrollbar = overlayScrollbar(root, space);
8128
8227
  let h = 26;
8129
8228
  let indent = 14;
8130
8229
  let inset = 8;
@@ -8974,6 +9073,7 @@ function tree(container, options) {
8974
9073
  loop.stop();
8975
9074
  autoScroll.stop();
8976
9075
  ro.disconnect();
9076
+ scrollbar.dispose();
8977
9077
  dragging?.ghost.remove();
8978
9078
  root.remove();
8979
9079
  }