@kitlangton/knobs 0.4.0-next.6 → 0.4.0-next.7

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.
package/dist/ui-react.js CHANGED
@@ -576,13 +576,14 @@ var compareCss = `
576
576
  picked: the pill's bright copy is. */
577
577
  .knobs-cmp-modes{position:relative;display:inline-flex;height:var(--_k-control-h);border-radius:var(--_k-radius);background:var(--_k-fill)}
578
578
  .knobs-cmp-modes[hidden]{display:none}
579
- .knobs-ui .knobs-cmp-mode{display:inline-flex;align-items:center;height:100%;padding:0 8px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:pointer;white-space:nowrap;
580
- transition:color calc(var(--_k-hover-out) * 1ms)}
581
- .knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
579
+ /* Hover is a quiet fill under the arrow cursor (no hand, no brightening), there at once. */
580
+ .knobs-ui .knobs-cmp-mode{display:inline-flex;align-items:center;height:100%;padding:0 8px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:default;white-space:nowrap;
581
+ transition:background calc(var(--_k-hover-out) * 1ms)}
582
+ .knobs-ui .knobs-cmp-mode:not([aria-checked="true"]):is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
582
583
  /* Toggles: icon buttons, the accent's wash when on. */
583
- .knobs-ui .knobs-cmp-toggle{display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:var(--_k-radius);color:var(--_k-fg-3);cursor:pointer;
584
- transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
585
- .knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
584
+ .knobs-ui .knobs-cmp-toggle{display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:default;
585
+ transition:background calc(var(--_k-hover-out) * 1ms)}
586
+ .knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
586
587
  .knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
587
588
  .knobs-cmp-toggle svg{display:block}
588
589
  .knobs-cmp-fill{flex:1}
@@ -596,11 +597,10 @@ var compareCss = `
596
597
  .knobs-cmp-stage{position:relative;flex:1;min-height:96px;overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_ku-cmp-stage);touch-action:pan-y;outline:none}
597
598
  .knobs-compare[data-frame="flat"] .knobs-cmp-stage{border-radius:0}
598
599
  .knobs-compare[data-mode="wipe"] .knobs-cmp-stage{cursor:ew-resize}
599
- /* Videos: a click on the stage plays and pauses. */
600
- .knobs-compare[data-video]:not([data-mode="wipe"],[data-zoomed]) .knobs-cmp-stage{cursor:pointer}
601
600
  /* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
602
- .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
603
- .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
601
+ /* Zoomed, it pans: a move cursor, never a hand. */
602
+ .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:move;touch-action:none}
603
+ .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:move!important}
604
604
  /* Wiping, the grip stands in for the cursor. */
605
605
  .knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:none!important}
606
606
  .knobs-cmp-sizer{position:absolute;left:0;right:0;top:0;height:0;visibility:hidden;pointer-events:none}
@@ -647,8 +647,8 @@ var compareCss = `
647
647
  opacity:0;scale:.96;pointer-events:none;transition:opacity .16s cubic-bezier(.4,0,1,1),scale .16s cubic-bezier(.4,0,1,1)}
648
648
  .knobs-cmp-zoom[data-on]{opacity:1;scale:1;pointer-events:auto;transition:opacity .2s cubic-bezier(.2,.8,.2,1),scale .2s cubic-bezier(.2,.8,.2,1)}
649
649
  :where(.knobs-ui) .knobs-cmp-zoom-reset{all:revert;box-sizing:border-box;font:inherit;letter-spacing:inherit;margin:0;border:0;outline:none;box-shadow:none;
650
- display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border-radius:999px;background:transparent;color:var(--_k-fg-2);cursor:pointer;transition:color .15s,background .15s}
651
- :where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){color:var(--_k-fg);background:color-mix(in srgb,var(--_k-fg) 12%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
650
+ display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border-radius:999px;background:transparent;color:var(--_k-fg-2);cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
651
+ :where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
652
652
  .knobs-cmp-note{position:absolute;left:50%;top:50%;translate:-50% -50%;color:var(--_k-fg-3);font-size:var(--_k-text-meta);pointer-events:none}
653
653
  .knobs-cmp-note:empty{display:none}
654
654
  .knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
@@ -666,23 +666,29 @@ var playerCss = `
666
666
  .knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;
667
667
  font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
668
668
  :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-ui.knobs-player,.knobs-ui.knobs-player[data-nested]{--_kp-stage:var(--k-compare-stage,transparent)}
669
- .knobs-player-frame{position:relative;width:100%;aspect-ratio:var(--kp-aspect,16/9);overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_kp-stage);cursor:pointer}
669
+ .knobs-player-frame{position:relative;width:100%;aspect-ratio:var(--kp-aspect,16/9);overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_kp-stage)}
670
670
  .knobs-player[data-frame="flat"] .knobs-player-frame{border-radius:0}
671
671
  .knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;
672
672
  opacity:0;filter:blur(4px);transition:opacity calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default),filter calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default)}
673
673
  .knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}
674
- .knobs-player-glyph{position:absolute;left:50%;top:50%;display:grid;place-items:center;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;color:var(--_k-fg);pointer-events:none;
675
- opacity:0;filter:blur(4px);scale:.96;
676
- background:color-mix(in srgb,var(--_k-bg) 52%,transparent);-webkit-backdrop-filter:blur(14px) saturate(1.6);backdrop-filter:blur(14px) saturate(1.6);
677
- box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim);
678
- transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),filter calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
679
- .knobs-player-glyph svg{width:16px;height:16px}
680
- .knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
681
674
  .knobs-player-line{flex:none;min-height:0}
682
- /* Hover in is immediate (0 ms), out eases. The timeline's own stylesheet is look.ts's; its hover
683
- states are set here, after it, so the player's line and every timeline share them. */
675
+ /* The timeline's own stylesheet is look.ts's; what the player's row and every timeline share
676
+ about the hand is set here, after it. No hand cursor anywhere (the arrow, or a resize where it
677
+ resizes); hover is a quiet fill (fill-hover), in at once (hover-in, 0 ms), out easing. */
678
+ .knobs-ui .knobs-tl-play,.knobs-ui.knobs-timeline :is(.knobs-tl-pin,.knobs-tl-rlabel),
679
+ .knobs-ui.knobs-timeline[data-dragging] .knobs-tl-marker :is(.knobs-tl-pin,.knobs-tl-mlabel){cursor:default}
684
680
  .knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,
685
681
  .knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}
682
+ /* The line's row: a minimal play/pause (the filled pair, a 28px target, no fill at rest), then
683
+ the line, centred on it. */
684
+ .knobs-ui.knobs-timeline[data-variant="line"]{gap:4px;align-items:center}
685
+ .knobs-timeline[data-variant="line"] .knobs-tl-transport{display:flex}
686
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-play{width:28px;height:28px;min-width:28px;min-height:28px;border-radius:var(--_k-radius);background:transparent;color:var(--_k-fg-2);
687
+ transition:background calc(var(--_k-hover-out) * 1ms),transform .15s var(--_ku-ease)}
688
+ .knobs-ui.knobs-timeline[data-variant="line"][data-playing] .knobs-tl-play{background:transparent;color:var(--_k-fg-2)}
689
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-play:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
690
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track{cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
691
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
686
692
  `;
687
693
 
688
694
  // src/ui/overlay-css.ts
@@ -3234,16 +3240,17 @@ var init_diff_css = __esm(() => {
3234
3240
  .knobs-diff-nav>span{min-width:3.6em;text-align:center}
3235
3241
  :where(.knobs-ui) :is(.knobs-diff-btn,.knobs-diff-choice,.knobs-diff-viewed,.knobs-diff-filter>input,.knobs-diff-filter>button){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;border-radius:0;
3236
3242
  margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
3237
- .knobs-ui .knobs-diff-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-3);cursor:pointer;
3238
- transition:background .15s,color .15s,opacity .15s}
3239
- .knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3243
+ .knobs-ui .knobs-diff-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-2);cursor:default;
3244
+ transition:background calc(var(--_k-hover-out) * 1ms),opacity .15s}
3245
+ /* Hover is a quiet fill under the arrow cursor (no hand, no brightening), there at once. */
3246
+ .knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3240
3247
  .knobs-ui .knobs-diff-btn[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
3241
3248
  .knobs-ui .knobs-diff-btn:disabled{opacity:.35;cursor:default;background:transparent}
3242
3249
  .knobs-diff-btn svg{width:16px;height:16px}
3243
3250
  /* Viewed: a check with its word, as on GitHub; the path dims once it's ticked. */
3244
3251
  .knobs-ui .knobs-diff-viewed{flex:none;display:inline-flex;align-items:center;gap:5px;height:var(--_k-control-h);padding:0 8px 0 6px;border-radius:var(--kd-ctl);color:var(--_k-fg-2);
3245
- font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:background .15s,color .15s}
3246
- .knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3252
+ font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:default;transition:background calc(var(--_k-hover-out) * 1ms),color .15s}
3253
+ .knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3247
3254
  .knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
3248
3255
  .knobs-diff-viewed svg{width:16px;height:16px}
3249
3256
  .knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
@@ -3262,8 +3269,8 @@ var init_diff_css = __esm(() => {
3262
3269
  choice isn't brighter for being picked. Choices are styled by class, as the copy holds them too. */
3263
3270
  .knobs-diff-seg{position:relative;flex:none;display:grid;grid-template-columns:1fr 1fr;height:var(--_k-control-h);padding:2px;border-radius:var(--kd-ctl);background:var(--_k-fill)}
3264
3271
  .knobs-ui .knobs-diff-choice{padding:0 9px;border-radius:max(0px,calc(var(--kd-ctl) - 2px));color:var(--_k-fg-2);font-size:var(--_k-text-meta);
3265
- font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
3266
- .knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3272
+ font-weight:var(--_k-weight-label);white-space:nowrap;cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
3273
+ .knobs-ui .knobs-diff-choice:not([aria-checked="true"]):is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3267
3274
  /* The diff: one layer per file or layout on show, the old fading under the new. */
3268
3275
  .knobs-diff-body{position:relative;flex:1;min-height:0}
3269
3276
  .knobs-diff-probe{position:absolute;left:0;top:0;width:0;height:var(--kd-line);visibility:hidden;pointer-events:none}
@@ -3359,8 +3366,8 @@ var init_diff_css = __esm(() => {
3359
3366
  .knobs-diff-filter svg{width:12px;height:12px;flex:none}
3360
3367
  .knobs-ui .knobs-diff-filter>input{flex:1;min-width:0;height:100%;font:var(--_k-weight) var(--_k-font-size)/1 var(--_k-font);color:var(--_k-fg);caret-color:var(--_k-accent);background:transparent}
3361
3368
  .knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
3362
- .knobs-ui .knobs-diff-filter>button{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:999px;color:var(--_k-fg-3);cursor:pointer;transition:background .12s,color .12s,opacity .12s}
3363
- .knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3369
+ .knobs-ui .knobs-diff-filter>button{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:999px;color:var(--_k-fg-3);cursor:default;transition:background calc(var(--_k-hover-out) * 1ms),opacity .12s}
3370
+ .knobs-ui .knobs-diff-filter>button:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3364
3371
  .knobs-diff-filter>button svg{width:12px;height:12px}
3365
3372
  .knobs-diff-list{position:relative;flex:1;min-height:0}
3366
3373
  .knobs-diff-files .knobs-diff-counts{font-size:var(--_k-text-meta);align-items:center}
@@ -11216,29 +11223,6 @@ var init_compare_strip = __esm(() => {
11216
11223
  DIFF = { w: 32, h: 18 };
11217
11224
  });
11218
11225
 
11219
- // src/ui/player.ts
11220
- function playGlyph(doc) {
11221
- const element = doc.createElement("span");
11222
- element.className = "knobs-player-glyph";
11223
- element.setAttribute("aria-hidden", "true");
11224
- let timer = 0;
11225
- const win = doc.defaultView;
11226
- return {
11227
- element,
11228
- flash(playing) {
11229
- element.innerHTML = playing ? PLAY2 : PAUSE;
11230
- element.setAttribute("data-flash", "");
11231
- win.clearTimeout(timer);
11232
- timer = win.setTimeout(() => element.removeAttribute("data-flash"), FLASH_MS);
11233
- },
11234
- dispose: () => win.clearTimeout(timer)
11235
- };
11236
- }
11237
- var FLASH_MS = 450;
11238
- var init_player = __esm(() => {
11239
- init_timeline();
11240
- });
11241
-
11242
11226
  // src/ui/compare.ts
11243
11227
  function compare(container, options) {
11244
11228
  const doc = container.ownerDocument;
@@ -11357,8 +11341,7 @@ function compare(container, options) {
11357
11341
  grip.setAttribute("aria-hidden", "true");
11358
11342
  handle.append(grip);
11359
11343
  const note = el("div", "knobs-cmp-note");
11360
- const glyph = playGlyph(doc);
11361
- stage.append(canvas, split, seam, labels[0], labels[1], handle, zoomPill, note, glyph.element);
11344
+ stage.append(canvas, split, seam, labels[0], labels[1], handle, zoomPill, note);
11362
11345
  root.append(bar, stage);
11363
11346
  const wantsStrip = options.strip === true;
11364
11347
  let tl = bound;
@@ -11400,7 +11383,6 @@ function compare(container, options) {
11400
11383
  let drag = null;
11401
11384
  const touches = new Map;
11402
11385
  let pinch = null;
11403
- let press = null;
11404
11386
  const zu = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
11405
11387
  const zv = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
11406
11388
  const zw = new Spring(1, ZOOM.omega, ZOOM.zeta, 0.00001);
@@ -12229,10 +12211,8 @@ function compare(container, options) {
12229
12211
  root.focus({ preventScroll: true });
12230
12212
  touches.set(e.pointerId, p);
12231
12213
  stage.setPointerCapture(e.pointerId);
12232
- press = touches.size === 1 && mode !== "wipe" && master() ? p : null;
12233
12214
  if (touches.size >= 2) {
12234
12215
  e.preventDefault();
12235
- press = null;
12236
12216
  return startPinch();
12237
12217
  }
12238
12218
  if (mode === "wipe") {
@@ -12260,8 +12240,6 @@ function compare(container, options) {
12260
12240
  const onMove = (e) => {
12261
12241
  const p = local(e);
12262
12242
  pointer = p;
12263
- if (press && Math.hypot(p.x - press.x, p.y - press.y) > 4)
12264
- press = null;
12265
12243
  if (touches.has(e.pointerId))
12266
12244
  touches.set(e.pointerId, p);
12267
12245
  if (pinch && touches.size >= 2) {
@@ -12291,11 +12269,6 @@ function compare(container, options) {
12291
12269
  drawNow();
12292
12270
  };
12293
12271
  const endDrag = (e) => {
12294
- if (press && e.type === "pointerup") {
12295
- api.toggle();
12296
- glyph.flash(isPlaying());
12297
- }
12298
- press = null;
12299
12272
  touches.delete(e.pointerId);
12300
12273
  if (touches.size < 2)
12301
12274
  pinch = null;
@@ -12457,7 +12430,6 @@ function compare(container, options) {
12457
12430
  old.el.replaceWith(s.el);
12458
12431
  old.preview.replaceWith(s.preview);
12459
12432
  sides[index] = s;
12460
- root.toggleAttribute("data-video", sides.some((x) => x.kind === "video"));
12461
12433
  note.textContent = "";
12462
12434
  setLabel(index, labelOf(index));
12463
12435
  if (near)
@@ -12472,7 +12444,6 @@ function compare(container, options) {
12472
12444
  stage.insertBefore(sides[0].preview, labels[0]);
12473
12445
  setLabel(0, labelOf(0));
12474
12446
  setLabel(1, labelOf(1));
12475
- root.toggleAttribute("data-video", sides.some((s) => s.kind === "video"));
12476
12447
  loupeBtn.setAttribute("aria-pressed", String(loupeOn));
12477
12448
  loopBtn.setAttribute("aria-pressed", String(loopNow() !== null));
12478
12449
  const modePill = modeList.length > 1 ? selectionPill(modes, {
@@ -12673,7 +12644,6 @@ function compare(container, options) {
12673
12644
  disposed = true;
12674
12645
  loop.stop();
12675
12646
  modePill?.dispose();
12676
- glyph.dispose();
12677
12647
  win.clearTimeout(releaseTimer);
12678
12648
  ro.disconnect();
12679
12649
  nearIo?.disconnect();
@@ -12714,7 +12684,6 @@ var init_compare = __esm(() => {
12714
12684
  init_icons();
12715
12685
  init_look();
12716
12686
  init_rgba();
12717
- init_player();
12718
12687
  init_selection_pill();
12719
12688
  init_timeline();
12720
12689
  init_timeline_model();
@@ -18244,9 +18213,10 @@ function knownPRs(doc, sections) {
18244
18213
  }
18245
18214
  function findPR(prs, n, repo) {
18246
18215
  const known = prs[n];
18216
+ const href = repo ? `https://github.com/${repo}/pull/${n}` : undefined;
18247
18217
  if (known)
18248
- return known;
18249
- return repo ? { href: `https://github.com/${repo}/pull/${n}` } : null;
18218
+ return known.href || !href ? known : { ...known, href };
18219
+ return href ? { href } : null;
18250
18220
  }
18251
18221
  var isPullRequest = (href) => /\/(?:pull|merge_requests)\/\d+(?:[/?#]|$)/.test(href);
18252
18222
  function withKickers(sections, source, kickers = "pr") {
@@ -19211,7 +19181,7 @@ function mountHistory(host, m, steps) {
19211
19181
  context: () => ({ kind: "history" })
19212
19182
  };
19213
19183
  }
19214
- function mountStack(host, m, steps) {
19184
+ function mountStack(host, m, steps, findPR) {
19215
19185
  const doc = host.ownerDocument;
19216
19186
  const layout = stackLayout(m);
19217
19187
  const root = el2(doc, "div", "knobs-wt-stack");
@@ -19253,7 +19223,10 @@ function mountStack(host, m, steps) {
19253
19223
  const line = el2(doc, "div", "knobs-wt-sline");
19254
19224
  if (it.order)
19255
19225
  line.append(text2(doc, "span", "knobs-wt-sord", it.order));
19256
- line.append(link(doc, "knobs-wt-spr", it.pr, it.href));
19226
+ const known = findPR?.(it.pr.replace(/^#/, "")) ?? null;
19227
+ const pr = link(doc, "knobs-wt-spr", it.pr, it.href ?? known?.href);
19228
+ pr.addEventListener("click", (e) => e.stopPropagation());
19229
+ line.append(pr);
19257
19230
  const target = it.step ? steps?.find(it.step) : null;
19258
19231
  if (target && steps) {
19259
19232
  const title = text2(doc, "button", "knobs-wt-stitle", it.title);
@@ -19288,7 +19261,11 @@ function mountStack(host, m, steps) {
19288
19261
  if (meta.childNodes.length)
19289
19262
  main.append(meta);
19290
19263
  row.append(main);
19291
- if (it.status) {
19264
+ const said = prStatus(it.status);
19265
+ const state = known?.state ?? said.state;
19266
+ if (state)
19267
+ row.append(prStateLabel(doc, state, said.checks, it.status));
19268
+ else if (it.status) {
19292
19269
  const chip = text2(doc, "span", "knobs-wt-chip knobs-wt-sstatus", it.status);
19293
19270
  chip.dataset.tone = it.tone ?? "dim";
19294
19271
  row.append(chip);
@@ -19595,8 +19572,34 @@ var PR_PATHS = {
19595
19572
  pull: "M104,64A32,32,0,1,0,64,95v66a32,32,0,1,0,16,0V95A32.06,32.06,0,0,0,104,64ZM56,64A16,16,0,1,1,72,80,16,16,0,0,1,56,64ZM88,192a16,16,0,1,1-16-16A16,16,0,0,1,88,192Zm120-31V110.63a23.85,23.85,0,0,0-7-17L163.31,56H192a8,8,0,0,0,0-16H144a8,8,0,0,0-8,8V96a8,8,0,0,0,16,0V67.31L189.66,105a8,8,0,0,1,2.34,5.66V161a32,32,0,1,0,16,0Zm-8,47a16,16,0,1,1,16-16A16,16,0,0,1,200,208Z",
19596
19573
  merge: "M208,112a32.05,32.05,0,0,0-30.69,23l-42.21-6a8,8,0,0,1-4.95-2.71L94.43,84.55A32,32,0,1,0,72,87v82a32,32,0,1,0,16,0V101.63l30,35a24,24,0,0,0,14.83,8.14l44,6.28A32,32,0,1,0,208,112ZM64,56A16,16,0,1,1,80,72,16,16,0,0,1,64,56ZM96,200a16,16,0,1,1-16-16A16,16,0,0,1,96,200Zm112-40a16,16,0,1,1,16-16A16,16,0,0,1,208,160Z"
19597
19574
  };
19598
- var prGlyph = (merged) => `<svg class="knobs-icon" viewBox="0 0 256 256" width="12" height="12" fill="currentColor" aria-hidden="true" focusable="false"><path d="${merged ? PR_PATHS.merge : PR_PATHS.pull}"/></svg>`;
19575
+ var DRAFT = "M104,64A32,32,0,1,0,64,95v66a32,32,0,1,0,16,0V95A32.06,32.06,0,0,0,104,64ZM56,64A16,16,0,1,1,72,80,16,16,0,0,1,56,64ZM88,192a16,16,0,1,1-16-16A16,16,0,0,1,88,192ZM232,192a32,32,0,1,1-32-32A32,32,0,0,1,232,192ZM216,192a16,16,0,1,0-16,16A16,16,0,0,0,216,192ZM212,120a12,12,0,1,1-12-12A12,12,0,0,1,212,120ZM212,60a12,12,0,1,1-12-12A12,12,0,0,1,212,60Z";
19576
+ var X = "M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z";
19577
+ var CHECK3 = "M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z";
19578
+ var svg12 = (d, evenodd = false) => `<svg class="knobs-icon" viewBox="0 0 256 256" width="12" height="12" fill="currentColor"${evenodd ? ' fill-rule="evenodd"' : ""} aria-hidden="true" focusable="false"><path d="${d}"/></svg>`;
19579
+ var prGlyph = (state) => state === "merged" ? svg12(PR_PATHS.merge) : state === "draft" ? svg12(DRAFT, true) : state === "closed" ? svg12(X) : svg12(PR_PATHS.pull);
19599
19580
  var PR_STATE = { merged: "Merged", open: "Open", draft: "Draft", closed: "Closed" };
19581
+ function prStatus(status) {
19582
+ const words = (status ?? "").trim().toLowerCase();
19583
+ const first = /^[a-z]+/.exec(words)?.[0] ?? "";
19584
+ const state = first in PR_STATE ? first : null;
19585
+ return { state, checks: /\b(green|passing|passed|ci|checks? pass)/.test(words) };
19586
+ }
19587
+ function prStateLabel(doc, state, checks, title) {
19588
+ const s = el2(doc, "span", "knobs-wt-prstate");
19589
+ s.dataset.state = state;
19590
+ s.innerHTML = prGlyph(state);
19591
+ s.append(text2(doc, "span", "", PR_STATE[state]));
19592
+ if (checks) {
19593
+ const c = el2(doc, "span", "knobs-wt-prchecks");
19594
+ c.innerHTML = svg12(CHECK3);
19595
+ c.title = "Checks pass";
19596
+ c.setAttribute("aria-label", "checks pass");
19597
+ s.append(c);
19598
+ }
19599
+ if (title)
19600
+ s.title = title;
19601
+ return s;
19602
+ }
19600
19603
  function prChip(doc, n, pr) {
19601
19604
  const chip = el2(doc, pr.href ? "a" : "span", "knobs-wt-pr");
19602
19605
  if (pr.href) {
@@ -19606,7 +19609,7 @@ function prChip(doc, n, pr) {
19606
19609
  }
19607
19610
  if (pr.state)
19608
19611
  chip.dataset.state = pr.state;
19609
- chip.innerHTML = prGlyph(pr.state === "merged");
19612
+ chip.innerHTML = prGlyph(pr.state);
19610
19613
  chip.append(text2(doc, "span", "", `#${n}`));
19611
19614
  const tip = [`#${n}`, pr.title, pr.state ? PR_STATE[pr.state] : ""].filter(Boolean).join(" · ");
19612
19615
  chip.title = tip;
@@ -20083,12 +20086,21 @@ var walkthroughCss = `
20083
20086
  .knobs-wt-add{color:var(--_k-diff-add)}
20084
20087
  .knobs-wt-del{color:var(--_k-diff-del)}
20085
20088
  .knobs-wt-sstatus{margin-top:calc((var(--kw-line) - 20px) / 2)}
20089
+ /* A PR's state the GitHub way: its glyph and word, open and merged quietly tinted (as PR chips),
20090
+ draft and closed faint; no fill, no dot. A check after it where the checks pass, faint too. One
20091
+ width for every row, so the glyphs and words line up. */
20092
+ .knobs-ui .knobs-wt-prstate{--kw-pr:var(--_k-fg-3);display:inline-flex;align-items:center;gap:4px;min-width:calc(7ch + 32px);height:var(--kw-line);color:var(--kw-pr);
20093
+ font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);white-space:nowrap}
20094
+ .knobs-ui .knobs-wt-prstate[data-state="open"]{--kw-pr:color-mix(in oklch,var(--_k-ok) 60%,var(--_k-fg-2))}
20095
+ .knobs-ui .knobs-wt-prstate[data-state="merged"]{--kw-pr:color-mix(in oklch,var(--_k-series-5) 60%,var(--_k-fg-2))}
20096
+ .knobs-wt-prstate svg{flex:none}
20097
+ .knobs-ui .knobs-wt-prchecks{display:inline-flex;margin-left:2px;color:var(--_k-fg-3)}
20086
20098
  .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr) auto}
20087
20099
  .knobs-wt-stack[data-flat] .knobs-wt-srow+.knobs-wt-srow{box-shadow:inset 0 1px 0 var(--_k-border)}
20088
20100
  @container knobs-wt-slot (max-width:520px){
20089
20101
  .knobs-wt-srow:not([data-trunk]){grid-template-columns:auto minmax(0,1fr)}
20090
20102
  .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr)}
20091
- .knobs-wt-sstatus{grid-column:-2;justify-self:start;margin-top:4px}
20103
+ :is(.knobs-wt-sstatus,.knobs-wt-prstate){grid-column:-2;justify-self:start;margin-top:4px}
20092
20104
  }
20093
20105
 
20094
20106
  /* A table: a real one, its head stuck while it scrolls, both ways; edges fade where there's more
@@ -20734,7 +20746,7 @@ function mediaSlot(container, options) {
20734
20746
  case "history":
20735
20747
  return done(mountHistory(host, m, options.steps));
20736
20748
  case "stack":
20737
- return done(mountStack(host, m, options.steps));
20749
+ return done(mountStack(host, m, options.steps, options.findPR));
20738
20750
  case "table":
20739
20751
  return done(mountTable(host, m, options.steps));
20740
20752
  case "mount": {
@@ -20788,7 +20800,7 @@ function mediaSlot(container, options) {
20788
20800
  }
20789
20801
  };
20790
20802
  }
20791
- var CHECK3 = icon("check", { small: true });
20803
+ var CHECK4 = icon("check", { small: true });
20792
20804
 
20793
20805
  // src/ui/walkthrough.ts
20794
20806
  var el3 = (doc, tag, cls) => {
@@ -20875,7 +20887,7 @@ function tasteTest(container, options) {
20875
20887
  pick.append(key);
20876
20888
  }
20877
20889
  const check = el3(doc, "span", "knobs-wt-check");
20878
- check.innerHTML = CHECK3;
20890
+ check.innerHTML = CHECK4;
20879
20891
  pick.append(check);
20880
20892
  pick.addEventListener("click", () => {
20881
20893
  const next = picked === o.id ? null : o.id;
@@ -22036,7 +22048,7 @@ function walkthrough(container, options) {
22036
22048
  media = el3(doc, "div", "knobs-wt-media");
22037
22049
  media.style.setProperty("--kw-n", String(Math.min(step.media.length, step.columns)));
22038
22050
  for (const m of step.media)
22039
- slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks }));
22051
+ slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks, findPR: findPRIn }));
22040
22052
  card.append(media);
22041
22053
  }
22042
22054
  let taste = null;