@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-solid.js CHANGED
@@ -1155,7 +1155,7 @@ var open = springEasing(2 * Math.PI / 0.22, 1);
1155
1155
  var activityCss = `
1156
1156
  :where(.knobs-activity,.knobs-activity *){box-sizing:border-box}
1157
1157
  :where(.knobs-activity){color-scheme:dark}
1158
- .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;
1158
+ .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;
1159
1159
  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;
1160
1160
  --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));
1161
1161
  --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));
@@ -1506,6 +1506,121 @@ var readoutCss = `
1506
1506
  @media(prefers-reduced-motion:reduce){.knobs-rd :where(*){transition:none!important}}
1507
1507
  `;
1508
1508
 
1509
+ // src/core/keys.ts
1510
+ var MODS = ["Ctrl", "Alt", "Shift", "Meta"];
1511
+ var ALIASES = {
1512
+ ctrl: "Ctrl",
1513
+ control: "Ctrl",
1514
+ "⌃": "Ctrl",
1515
+ alt: "Alt",
1516
+ option: "Alt",
1517
+ opt: "Alt",
1518
+ "⌥": "Alt",
1519
+ shift: "Shift",
1520
+ "⇧": "Shift",
1521
+ meta: "Meta",
1522
+ cmd: "Meta",
1523
+ command: "Meta",
1524
+ "⌘": "Meta",
1525
+ mod: "Mod"
1526
+ };
1527
+ var NAMED = {
1528
+ " ": "Space",
1529
+ space: "Space",
1530
+ spacebar: "Space",
1531
+ esc: "Escape",
1532
+ escape: "Escape",
1533
+ enter: "Enter",
1534
+ return: "Enter",
1535
+ tab: "Tab",
1536
+ backspace: "Backspace",
1537
+ delete: "Delete",
1538
+ del: "Delete",
1539
+ up: "ArrowUp",
1540
+ down: "ArrowDown",
1541
+ left: "ArrowLeft",
1542
+ right: "ArrowRight",
1543
+ arrowup: "ArrowUp",
1544
+ arrowdown: "ArrowDown",
1545
+ arrowleft: "ArrowLeft",
1546
+ arrowright: "ArrowRight",
1547
+ home: "Home",
1548
+ end: "End",
1549
+ pageup: "PageUp",
1550
+ pagedown: "PageDown"
1551
+ };
1552
+ var isApple = () => typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.userAgentData?.platform ?? navigator.platform ?? "");
1553
+ var keyName = (raw) => {
1554
+ if (!raw)
1555
+ return "";
1556
+ const named = NAMED[raw.toLowerCase()];
1557
+ if (named)
1558
+ return named;
1559
+ if (raw.length === 1)
1560
+ return raw.toUpperCase();
1561
+ if (/^f\d{1,2}$/i.test(raw))
1562
+ return raw.toUpperCase();
1563
+ return raw.charAt(0).toUpperCase() + raw.slice(1);
1564
+ };
1565
+ function normalizeCombo(combo) {
1566
+ const trimmed = combo.trim();
1567
+ if (!trimmed)
1568
+ return "";
1569
+ const parts = trimmed === "+" ? ["+"] : trimmed.replace(/\+\+$/, "+plus").split("+");
1570
+ const mods = new Set;
1571
+ let key = "";
1572
+ for (const part of parts) {
1573
+ const alias = ALIASES[part.trim().toLowerCase()] ?? ALIASES[part.trim()];
1574
+ if (alias === "Mod")
1575
+ mods.add(isApple() ? "Meta" : "Ctrl");
1576
+ else if (alias)
1577
+ mods.add(alias);
1578
+ else
1579
+ key = part === "plus" ? "+" : keyName(part.trim() || part);
1580
+ }
1581
+ if (!key)
1582
+ return "";
1583
+ return [...MODS.filter((m) => mods.has(m)), key].join("+");
1584
+ }
1585
+ var GLYPHS = { Ctrl: "⌃", Alt: "⌥", Shift: "⇧", Meta: "⌘" };
1586
+ var WORDS = { Ctrl: "Ctrl", Alt: "Alt", Shift: "Shift", Meta: "Meta" };
1587
+ var KEY_GLYPHS = {
1588
+ ArrowUp: "↑",
1589
+ ArrowDown: "↓",
1590
+ ArrowLeft: "←",
1591
+ ArrowRight: "→",
1592
+ Backspace: "⌫",
1593
+ Delete: "⌦",
1594
+ Enter: "↩",
1595
+ Escape: "Esc",
1596
+ Tab: "⇥",
1597
+ PageUp: "PgUp",
1598
+ PageDown: "PgDn"
1599
+ };
1600
+ function comboParts(combo, apple = isApple()) {
1601
+ const norm = normalizeCombo(combo);
1602
+ if (!norm)
1603
+ return [];
1604
+ const parts = norm === "+" ? ["+"] : norm.split("+");
1605
+ if (norm.endsWith("++"))
1606
+ parts.splice(-2, 2, "+");
1607
+ const key = parts.pop();
1608
+ const mods = parts;
1609
+ const shown = KEY_GLYPHS[key] ?? key;
1610
+ return apple ? [...mods.map((m) => GLYPHS[m]), shown] : [...mods.map((m) => WORDS[m]), shown];
1611
+ }
1612
+ function isTypingTarget(target) {
1613
+ const el = target;
1614
+ if (!el || typeof el.tagName !== "string")
1615
+ return false;
1616
+ const tag = el.tagName;
1617
+ if (tag === "SELECT" || el.isContentEditable)
1618
+ return true;
1619
+ if (tag === "TEXTAREA")
1620
+ return !el.readOnly;
1621
+ return tag === "INPUT" && !el.readOnly && !["range", "checkbox", "radio", "button", "submit", "reset", "color"].includes(el.type);
1622
+ }
1623
+
1509
1624
  // src/ui/look.ts
1510
1625
  var ease = springEasing(26, 1);
1511
1626
  var TOKENS = {
@@ -1623,6 +1738,17 @@ var css = `
1623
1738
  }
1624
1739
  @supports not (animation-timeline:scroll()){.knobs-scroll-fade-x{text-overflow:ellipsis}}
1625
1740
 
1741
+ /* Overlay scrollbars (trees, activity rails): a thin thumb over the content, so the rows never
1742
+ give up width to a scrollbar as they start or stop overflowing. It shows while the content
1743
+ scrolls or the pointer is over it. */
1744
+ :is(.knobs-tree,.knobs-activity)::-webkit-scrollbar{display:none}
1745
+ .knobs-scrollbar{position:sticky;top:0;z-index:3;height:0;pointer-events:none}
1746
+ .knobs-scrollbar[hidden]{display:none}
1747
+ .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;
1748
+ transition:opacity .3s ease-out,background .15s}
1749
+ :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}
1750
+ .knobs-scrollbar-thumb:hover,.knobs-scrollbar[data-dragging]>.knobs-scrollbar-thumb{background:color-mix(in srgb,var(--ku-text) 48%,transparent)}
1751
+
1626
1752
  /* Split: panes along a row or column, hairline dividers with a wider grip. */
1627
1753
  .knobs-split{position:relative;display:flex;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden}
1628
1754
  .knobs-split[data-direction="column"]{flex-direction:column}
@@ -1659,7 +1785,11 @@ var css = `
1659
1785
  .knobs-split[data-direction="column"]>.knobs-pane[data-edge="start"]>.knobs-pane-rail{bottom:0}
1660
1786
  .knobs-split[data-direction="column"]>.knobs-pane[data-edge="end"]>.knobs-pane-rail{top:0}
1661
1787
  .knobs-pane[data-collapsed]>.knobs-pane-rail{pointer-events:auto}
1662
- .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)}
1788
+ /* Its hover wash fades, rather than going in one frame as a click opens the pane. */
1789
+ .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}
1790
+ .knobs-pane-rail>*{position:relative}
1791
+ .knobs-ui .knobs-pane[data-collapsed]>.knobs-pane-rail:is(:hover,:focus-visible){color:var(--ku-text)}
1792
+ .knobs-ui .knobs-pane[data-collapsed]>.knobs-pane-rail:is(:hover,:focus-visible)::before{opacity:1}
1663
1793
  .knobs-pane-rail-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
1664
1794
  .knobs-pane-chevron{display:flex;flex:none;width:11px;height:11px;writing-mode:horizontal-tb;color:var(--ku-dim)}
1665
1795
  .knobs-pane-turn{display:flex;width:100%;height:100%;transform-origin:50% 50%}
@@ -1696,7 +1826,7 @@ var css = `
1696
1826
  :root[data-knobs-resizing] :is(iframe,canvas:not(.knobs-gpu-canvas)){pointer-events:none}
1697
1827
 
1698
1828
  /* Tree: one scroller, rows placed by transform, only those near the view in the DOM. */
1699
- .knobs-tree{position:relative;overflow:auto;height:100%;outline:none;scrollbar-width:thin;overscroll-behavior:contain;user-select:none;-webkit-user-select:none}
1829
+ .knobs-tree{position:relative;overflow:auto;height:100%;outline:none;scrollbar-width:none;overscroll-behavior:contain;user-select:none;-webkit-user-select:none}
1700
1830
  .knobs-tree-space{position:relative;width:100%;pointer-events:none}
1701
1831
  .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);
1702
1832
  color:var(--ku-text);white-space:nowrap;cursor:default;will-change:transform;contain:layout style}
@@ -1848,7 +1978,6 @@ var tune = (s, m) => {
1848
1978
  s.zeta = m.zeta;
1849
1979
  return s;
1850
1980
  };
1851
- 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));
1852
1981
  function notifyLayout(doc) {
1853
1982
  doc.dispatchEvent(new Event("knobs-layout"));
1854
1983
  }
@@ -1888,6 +2017,79 @@ var ICONS = {
1888
2017
  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"/>')
1889
2018
  };
1890
2019
 
2020
+ // src/ui/scrollbar.ts
2021
+ function overlayScrollbar(scroller, content) {
2022
+ const doc = scroller.ownerDocument;
2023
+ const win = doc.defaultView;
2024
+ const track = doc.createElement("div");
2025
+ track.className = "knobs-scrollbar";
2026
+ track.setAttribute("aria-hidden", "true");
2027
+ const thumb = doc.createElement("div");
2028
+ thumb.className = "knobs-scrollbar-thumb";
2029
+ track.append(thumb);
2030
+ scroller.prepend(track);
2031
+ let size = 0;
2032
+ const update = () => {
2033
+ const view = scroller.clientHeight;
2034
+ const over = scroller.scrollHeight - view;
2035
+ const shown = over > 1 && view > 0;
2036
+ track.hidden = !shown;
2037
+ if (!shown)
2038
+ return;
2039
+ size = Math.max(20, view * view / (view + over));
2040
+ const y = Math.min(over, Math.max(0, scroller.scrollTop)) / over * (view - size);
2041
+ thumb.style.height = `${size.toFixed(1)}px`;
2042
+ thumb.style.transform = `translate3d(0,${y.toFixed(1)}px,0)`;
2043
+ };
2044
+ let idle = 0;
2045
+ const onScroll = () => {
2046
+ update();
2047
+ track.dataset.active = "";
2048
+ win.clearTimeout(idle);
2049
+ idle = win.setTimeout(() => delete track.dataset.active, 800);
2050
+ };
2051
+ scroller.addEventListener("scroll", onScroll, { passive: true });
2052
+ let drag;
2053
+ thumb.addEventListener("pointerdown", (e) => {
2054
+ if (e.button !== 0)
2055
+ return;
2056
+ e.preventDefault();
2057
+ e.stopPropagation();
2058
+ drag = { y: e.clientY, top: scroller.scrollTop, pointer: e.pointerId };
2059
+ thumb.setPointerCapture(e.pointerId);
2060
+ track.dataset.dragging = "";
2061
+ });
2062
+ thumb.addEventListener("pointermove", (e) => {
2063
+ if (!drag || e.pointerId !== drag.pointer)
2064
+ return;
2065
+ const view = scroller.clientHeight;
2066
+ const over = scroller.scrollHeight - view;
2067
+ scroller.scrollTop = drag.top + (e.clientY - drag.y) * over / Math.max(1, view - size);
2068
+ });
2069
+ const end = (e) => {
2070
+ if (!drag || e.pointerId !== drag.pointer)
2071
+ return;
2072
+ drag = undefined;
2073
+ delete track.dataset.dragging;
2074
+ };
2075
+ thumb.addEventListener("pointerup", end);
2076
+ thumb.addEventListener("pointercancel", end);
2077
+ thumb.addEventListener("lostpointercapture", end);
2078
+ const ro = new ResizeObserver(update);
2079
+ ro.observe(scroller);
2080
+ ro.observe(content);
2081
+ update();
2082
+ return {
2083
+ update,
2084
+ dispose() {
2085
+ ro.disconnect();
2086
+ win.clearTimeout(idle);
2087
+ scroller.removeEventListener("scroll", onScroll);
2088
+ track.remove();
2089
+ }
2090
+ };
2091
+ }
2092
+
1891
2093
  // src/ui/activity.ts
1892
2094
  var ACTIVITY_ICONS = {
1893
2095
  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"/>'),
@@ -1900,7 +2102,7 @@ var ACTIVITY_ICONS = {
1900
2102
  shipped: svg('<circle cx="8" cy="8" r="5.8"/><path d="m5.6 8.2 1.7 1.7 3.2-3.6"/>')
1901
2103
  };
1902
2104
  var PLAY = '<svg viewBox="0 0 8 8" aria-hidden="true"><path d="M1.5 1v6l5.2-3Z" fill="currentColor"/></svg>';
1903
- var REFLOW = springMotion({ duration: 0.4, bounce: 0 });
2105
+ var REFLOW = springMotion({ duration: 0.3, bounce: 0 });
1904
2106
  var OPEN = springMotion({ duration: 0.22, bounce: 0 });
1905
2107
  var CLOSE = springMotion({ duration: 0.18, bounce: 0 });
1906
2108
  var BLOOM = springMotion({ duration: 0.3, bounce: 0 });
@@ -1943,6 +2145,7 @@ function activity(container, options) {
1943
2145
  space.append(hl, line);
1944
2146
  root.append(empty, space);
1945
2147
  container.append(root);
2148
+ const scrollbar = overlayScrollbar(root, space);
1946
2149
  let heights = [32, 26];
1947
2150
  const measure = () => {
1948
2151
  syncFrame(root);
@@ -2566,6 +2769,7 @@ function activity(container, options) {
2566
2769
  destroy() {
2567
2770
  loop.stop();
2568
2771
  ro.disconnect();
2772
+ scrollbar.dispose();
2569
2773
  if (keysFrom === "page") {
2570
2774
  win.removeEventListener("keydown", onKey);
2571
2775
  win.removeEventListener("pointerdown", onPointer, true);
@@ -2912,11 +3116,7 @@ var SLIDE_BY = 0.12;
2912
3116
  var EXIT_BLUR = 3;
2913
3117
  var px = (x) => new Spring(x, LAYOUT.omega, LAYOUT.zeta, 0.05);
2914
3118
  var unit = (x) => new Spring(x, ENTER.omega, ENTER.zeta, 0.0005);
2915
- var drive = (s, m, target) => {
2916
- s.omega = m.omega;
2917
- s.zeta = m.zeta;
2918
- return s.set(target);
2919
- };
3119
+ var drive = (s, m, target) => tune(s, m).set(target);
2920
3120
  var probed = new Map;
2921
3121
  var smallest = (srcset) => {
2922
3122
  let best;
@@ -3677,7 +3877,7 @@ function bento(container, options) {
3677
3877
  }
3678
3878
  zoomIn(t.id);
3679
3879
  };
3680
- const typing = (target) => target instanceof HTMLElement && target !== root && (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName));
3880
+ const typing = (target) => target !== root && isTypingTarget(target);
3681
3881
  const reveal = (id) => {
3682
3882
  const r = rectOf(id);
3683
3883
  if (!r)
@@ -4636,15 +4836,7 @@ function claimsHintKey(doc, key) {
4636
4836
  const holder = doc;
4637
4837
  return key === PAGE_HINT_KEY && Boolean(holder[KEY]?.listening);
4638
4838
  }
4639
- var isTyping = (target) => {
4640
- const el = target;
4641
- if (!el || typeof el.tagName !== "string")
4642
- return false;
4643
- const tag = el.tagName;
4644
- if (tag === "TEXTAREA" || tag === "SELECT" || el.isContentEditable)
4645
- return true;
4646
- return tag === "INPUT" && !["range", "checkbox", "radio", "button", "submit", "reset", "color"].includes(el.type);
4647
- };
4839
+ var isTyping = isTypingTarget;
4648
4840
  var PRESSABLE = "button,a[href],summary,[role=button],[role=tab],[role=radio],[role=switch],[role=checkbox],[role=menuitem],[role=option]";
4649
4841
  function activateElement(el) {
4650
4842
  const pressable = el.matches(PRESSABLE) || el.tabIndex < 0 && !el.matches("input,select,textarea");
@@ -5024,110 +5216,6 @@ function layerPainter(doc, groups) {
5024
5216
  };
5025
5217
  }
5026
5218
 
5027
- // src/core/keys.ts
5028
- var MODS = ["Ctrl", "Alt", "Shift", "Meta"];
5029
- var ALIASES = {
5030
- ctrl: "Ctrl",
5031
- control: "Ctrl",
5032
- "⌃": "Ctrl",
5033
- alt: "Alt",
5034
- option: "Alt",
5035
- opt: "Alt",
5036
- "⌥": "Alt",
5037
- shift: "Shift",
5038
- "⇧": "Shift",
5039
- meta: "Meta",
5040
- cmd: "Meta",
5041
- command: "Meta",
5042
- "⌘": "Meta",
5043
- mod: "Mod"
5044
- };
5045
- var NAMED = {
5046
- " ": "Space",
5047
- space: "Space",
5048
- spacebar: "Space",
5049
- esc: "Escape",
5050
- escape: "Escape",
5051
- enter: "Enter",
5052
- return: "Enter",
5053
- tab: "Tab",
5054
- backspace: "Backspace",
5055
- delete: "Delete",
5056
- del: "Delete",
5057
- up: "ArrowUp",
5058
- down: "ArrowDown",
5059
- left: "ArrowLeft",
5060
- right: "ArrowRight",
5061
- arrowup: "ArrowUp",
5062
- arrowdown: "ArrowDown",
5063
- arrowleft: "ArrowLeft",
5064
- arrowright: "ArrowRight",
5065
- home: "Home",
5066
- end: "End",
5067
- pageup: "PageUp",
5068
- pagedown: "PageDown"
5069
- };
5070
- var isApple = () => typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.userAgentData?.platform ?? navigator.platform ?? "");
5071
- var keyName = (raw) => {
5072
- if (!raw)
5073
- return "";
5074
- const named = NAMED[raw.toLowerCase()];
5075
- if (named)
5076
- return named;
5077
- if (raw.length === 1)
5078
- return raw.toUpperCase();
5079
- if (/^f\d{1,2}$/i.test(raw))
5080
- return raw.toUpperCase();
5081
- return raw.charAt(0).toUpperCase() + raw.slice(1);
5082
- };
5083
- function normalizeCombo(combo) {
5084
- const trimmed = combo.trim();
5085
- if (!trimmed)
5086
- return "";
5087
- const parts = trimmed === "+" ? ["+"] : trimmed.replace(/\+\+$/, "+plus").split("+");
5088
- const mods = new Set;
5089
- let key = "";
5090
- for (const part of parts) {
5091
- const alias = ALIASES[part.trim().toLowerCase()] ?? ALIASES[part.trim()];
5092
- if (alias === "Mod")
5093
- mods.add(isApple() ? "Meta" : "Ctrl");
5094
- else if (alias)
5095
- mods.add(alias);
5096
- else
5097
- key = part === "plus" ? "+" : keyName(part.trim() || part);
5098
- }
5099
- if (!key)
5100
- return "";
5101
- return [...MODS.filter((m) => mods.has(m)), key].join("+");
5102
- }
5103
- var GLYPHS = { Ctrl: "⌃", Alt: "⌥", Shift: "⇧", Meta: "⌘" };
5104
- var WORDS = { Ctrl: "Ctrl", Alt: "Alt", Shift: "Shift", Meta: "Meta" };
5105
- var KEY_GLYPHS = {
5106
- ArrowUp: "↑",
5107
- ArrowDown: "↓",
5108
- ArrowLeft: "←",
5109
- ArrowRight: "→",
5110
- Backspace: "⌫",
5111
- Delete: "⌦",
5112
- Enter: "↩",
5113
- Escape: "Esc",
5114
- Tab: "⇥",
5115
- PageUp: "PgUp",
5116
- PageDown: "PgDn"
5117
- };
5118
- function comboParts(combo, apple = isApple()) {
5119
- const norm = normalizeCombo(combo);
5120
- if (!norm)
5121
- return [];
5122
- const parts = norm === "+" ? ["+"] : norm.split("+");
5123
- if (norm.endsWith("++"))
5124
- parts.splice(-2, 2, "+");
5125
- const key = parts.pop();
5126
- const mods = parts;
5127
- const shown = KEY_GLYPHS[key] ?? key;
5128
- return apple ? [...mods.map((m) => GLYPHS[m]), shown] : [...mods.map((m) => WORDS[m]), shown];
5129
- }
5130
-
5131
5219
  // src/ui/layer.ts
5132
5220
  var lookOf = (look) => ({ ...lookDefaults.current, ...look });
5133
5221
  var roots = new WeakMap;
@@ -5700,10 +5788,10 @@ function foldFaces(prev, p) {
5700
5788
  function chevronTurn(p, edge) {
5701
5789
  return (edge === "start" ? 180 : -180) * (1 - clamp01(p));
5702
5790
  }
5703
- function chevronFlip(f) {
5704
- return 2 * clamp01(f) - 1;
5791
+ function chevronFlip(f, edge) {
5792
+ return chevronTurn(f, edge);
5705
5793
  }
5706
- var FOLD_STYLE = { chevron: "flip", title: "travel", duration: 0.3 };
5794
+ var FOLD_STYLE = { chevron: "flip", title: "travel", duration: 0.25 };
5707
5795
  function foldStyle(options = {}) {
5708
5796
  const duration = options.duration;
5709
5797
  return {
@@ -5744,10 +5832,12 @@ function serializeLayout(layout) {
5744
5832
  }
5745
5833
 
5746
5834
  // src/ui/split.ts
5747
- var FLIP = springMotion({ duration: 0.18, bounce: 0 });
5835
+ var FLIP = springMotion({ duration: 0.25, bounce: 0 });
5836
+ var FLIP_DEPTH = 28;
5748
5837
  var SETTLE = springMotion({ duration: 0.3, bounce: 0 });
5749
5838
  var CONTENT_BLUR = 3;
5750
5839
  var LABEL_BLUR = 2;
5840
+ var COUNT_CLEAR = 0.12;
5751
5841
  var put = (el, prop, value) => {
5752
5842
  if (el.style.getPropertyValue(prop) !== value)
5753
5843
  el.style.setProperty(prop, value);
@@ -5884,7 +5974,12 @@ function split(container, options) {
5884
5974
  if (!row) {
5885
5975
  head.dataset.toggles = "";
5886
5976
  head.addEventListener("click", () => setCollapsed(spec.id, !collapsed[spec.id]));
5887
- }
5977
+ } else
5978
+ head.addEventListener("dblclick", (e) => {
5979
+ if (e.target.closest(".knobs-pane-btn"))
5980
+ return;
5981
+ setCollapsed(spec.id, true);
5982
+ });
5888
5983
  }
5889
5984
  inner.append(head);
5890
5985
  view.head = head;
@@ -6053,6 +6148,8 @@ function split(container, options) {
6053
6148
  const label = v.railLabel;
6054
6149
  const travel = morph && style.title === "travel" && Boolean(v.titleEl) && label.textContent === v.titleEl.textContent;
6055
6150
  v.el.toggleAttribute("data-travel", travel && folding);
6151
+ if (v.countEl)
6152
+ put(v.countEl, "opacity", travel && folding && p > 0 ? Math.max(0, 1 - p / COUNT_CLEAR).toFixed(3) : "");
6056
6153
  const k = 1 - p;
6057
6154
  if (travel && !folding) {
6058
6155
  put(label, "opacity", "0");
@@ -6080,8 +6177,9 @@ function split(container, options) {
6080
6177
  if (style.chevron === "turn")
6081
6178
  put(v.turn, "transform", k === 0 ? "" : `${move} rotate(${chevronTurn(p, v.edge).toFixed(2)}deg) ${size}`);
6082
6179
  else {
6083
- const w = chevronFlip(v.flip.x);
6084
- put(v.turn, "transform", k === 0 && w === 1 ? "" : `${move} ${size} scaleX(${w.toFixed(4)})`);
6180
+ const turn = chevronFlip(v.flip.x, v.edge);
6181
+ const over = v.flip.resting ? turn === 0 ? "" : "scaleX(-1)" : `perspective(${FLIP_DEPTH}px) rotateY(${turn.toFixed(2)}deg)`;
6182
+ put(v.turn, "transform", k === 0 && !over ? "" : `${move} ${size} ${over}`);
6085
6183
  }
6086
6184
  } else {
6087
6185
  const a = v.faces.rail;
@@ -8085,7 +8183,7 @@ function virtualWindow(scrollTop, height, rowHeight, count, overscan = 6) {
8085
8183
  }
8086
8184
 
8087
8185
  // src/ui/tree.ts
8088
- var MOVE = springMotion({ duration: 0.34, bounce: 0.02 });
8186
+ var MOVE = springMotion({ duration: 0.3, bounce: 0 });
8089
8187
  var EDGE = 28;
8090
8188
  function tree(container, options) {
8091
8189
  const doc = container.ownerDocument;
@@ -8124,6 +8222,7 @@ function tree(container, options) {
8124
8222
  empty.hidden = true;
8125
8223
  root.append(space, line, empty);
8126
8224
  container.append(root);
8225
+ const scrollbar = overlayScrollbar(root, space);
8127
8226
  let h = 26;
8128
8227
  let indent = 14;
8129
8228
  let inset = 8;
@@ -8973,6 +9072,7 @@ function tree(container, options) {
8973
9072
  loop.stop();
8974
9073
  autoScroll.stop();
8975
9074
  ro.disconnect();
9075
+ scrollbar.dispose();
8976
9076
  dragging?.ghost.remove();
8977
9077
  root.remove();
8978
9078
  }