@kitlangton/knobs 0.4.0-next.1 → 0.4.0-next.3
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/README.md +78 -8
- package/dist/dev/{chunk-ayb292zj.js → chunk-j6zmtens.js} +1 -1
- package/dist/dev/{chunk-1qzffbpa.js → chunk-zwj6yngd.js} +66 -33
- package/dist/dev/index.js +3 -3
- package/dist/panel.js +66 -33
- package/dist/types/ui/compare-css.d.ts +1 -1
- package/dist/types/ui/compare-gpu.d.ts +0 -5
- package/dist/types/ui/compare-model.d.ts +31 -2
- package/dist/types/ui/compare.d.ts +17 -4
- package/dist/types/ui/icons.d.ts +40 -0
- package/dist/types/ui/index.d.ts +6 -4
- package/dist/types/ui/selection-pill-css.d.ts +1 -0
- package/dist/types/ui/selection-pill.d.ts +103 -0
- package/dist/types/ui/timeline-model.d.ts +23 -0
- package/dist/types/ui/timeline.d.ts +11 -1
- package/dist/types/ui/tree.d.ts +3 -2
- package/dist/types/ui/walkthrough-blocks.d.ts +43 -0
- package/dist/types/ui/walkthrough-media.d.ts +4 -0
- package/dist/types/ui/walkthrough-model.d.ts +237 -3
- package/dist/types/ui/walkthrough.d.ts +3 -1
- package/dist/ui-react.js +1544 -360
- package/dist/ui-solid.js +1544 -360
- package/dist/ui.js +1565 -360
- package/package.json +1 -1
package/dist/ui.js
CHANGED
|
@@ -570,12 +570,20 @@ var compareCss = `
|
|
|
570
570
|
display:flex;flex-direction:column;gap:6px;width:100%;height:100%;min-height:160px;outline:none;user-select:none;-webkit-user-select:none;
|
|
571
571
|
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
|
|
572
572
|
:where(.knobs-ui) :is(.knobs-cmp-mode,.knobs-cmp-toggle){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;margin:0;padding:0;outline:none;box-shadow:none}
|
|
573
|
-
.knobs-cmp-bar{flex:none;display:flex;align-items:center;gap:
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
.knobs-
|
|
578
|
-
.knobs-ui .knobs-cmp-mode
|
|
573
|
+
.knobs-cmp-bar{flex:none;display:flex;align-items:center;gap:var(--_k-gap);height:var(--_k-control-h)}
|
|
574
|
+
/* The modes: one fill, the selection pill on the one picked; a mode isn't brighter for being
|
|
575
|
+
picked: the pill's bright copy is. */
|
|
576
|
+
.knobs-cmp-modes{position:relative;display:inline-flex;height:var(--_k-control-h);border-radius:var(--_k-radius);background:var(--_k-fill)}
|
|
577
|
+
.knobs-cmp-modes[hidden]{display:none}
|
|
578
|
+
.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;
|
|
579
|
+
transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
580
|
+
.knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg)}
|
|
581
|
+
/* Toggles: icon buttons, the accent's wash when on. */
|
|
582
|
+
.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;
|
|
583
|
+
transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
|
|
584
|
+
.knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash)}
|
|
585
|
+
.knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
|
|
586
|
+
.knobs-cmp-toggle svg{display:block}
|
|
579
587
|
.knobs-cmp-fill{flex:1}
|
|
580
588
|
.knobs-cmp-toggle[hidden]{display:none}
|
|
581
589
|
/* One surface: on a card (a walkthrough's step, a split's pane, anything .knobs-surface) or marked
|
|
@@ -590,7 +598,8 @@ var compareCss = `
|
|
|
590
598
|
/* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
|
|
591
599
|
.knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
|
|
592
600
|
.knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
|
|
593
|
-
.
|
|
601
|
+
/* Wiping, the grip stands in for the cursor. */
|
|
602
|
+
.knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:none!important}
|
|
594
603
|
.knobs-cmp-sizer{position:absolute;left:0;right:0;top:0;height:0;visibility:hidden;pointer-events:none}
|
|
595
604
|
.knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
|
|
596
605
|
.knobs-cmp-media{position:absolute;left:0;top:0;display:block;max-width:none;margin:0;padding:0;border:0;object-fit:fill;transform-origin:0 0;pointer-events:none;will-change:transform}
|
|
@@ -598,18 +607,21 @@ var compareCss = `
|
|
|
598
607
|
/* Glass: what's behind shows through, blurred, on the knobs tokens. */
|
|
599
608
|
.knobs-cmp-label,.knobs-cmp-zoom{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);
|
|
600
609
|
box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim),0 2px 10px rgba(0,0,0,calc(.18 * var(--_k-shadow-k,1)))}
|
|
601
|
-
.knobs-cmp-label{position:absolute;left:0;top:0;padding:1px 7px;border-radius:999px;font-size:var(--_k-text-meta);line-height:16px;color:var(--_k-fg-2);white-space:nowrap;pointer-events:none;will-change:transform;transition:color
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
/* The wipe: a hairline down the line with a grip at its middle; a wider strip takes the hand and
|
|
605
|
-
the keys (a slider). It fades with the mode. */
|
|
610
|
+
.knobs-cmp-label{position:absolute;left:0;top:0;padding:1px 7px;border-radius:999px;font-size:var(--_k-text-meta);line-height:16px;color:var(--_k-fg-2);white-space:nowrap;pointer-events:none;will-change:transform;transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
611
|
+
/* The wipe: a hairline down the line with a raised grip at the pointer's height; a wider strip
|
|
612
|
+
takes the hand and the keys (a slider). It fades with the mode; wiping, line and grip brighten. */
|
|
606
613
|
.knobs-cmp-handle{cursor:ew-resize;position:absolute;left:-12px;top:0;width:24px;height:0;opacity:0;pointer-events:none;outline:none;will-change:transform;transition:opacity .18s cubic-bezier(.4,0,1,1)}
|
|
607
614
|
.knobs-cmp-handle[data-on]{opacity:1;pointer-events:auto;transition:opacity .22s cubic-bezier(.2,.8,.2,1)}
|
|
608
|
-
.knobs-cmp-handle::before{content:"";position:absolute;top:0;bottom:0;left:11.5px;width:1px;background:var(--_ku-cmp-line);box-shadow:0 0 0 .5px rgba(0,0,0,.28),0 0 6px rgba(0,0,0,.25)}
|
|
609
|
-
.knobs-cmp-handle::
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
.knobs-cmp-
|
|
615
|
+
.knobs-cmp-handle::before{content:"";position:absolute;top:0;bottom:0;left:11.5px;width:1px;background:color-mix(in srgb,var(--_ku-cmp-line) 72%,transparent);box-shadow:0 0 0 .5px rgba(0,0,0,.28),0 0 6px rgba(0,0,0,.25)}
|
|
616
|
+
.knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}
|
|
617
|
+
.knobs-cmp-grip{position:absolute;top:0;left:4px;width:16px;height:28px;border-radius:min(8px,calc(var(--_k-radius) * 999));background:var(--_k-bg-overlay);
|
|
618
|
+
box-shadow:var(--_k-shadow-overlay),inset 0 0 0 1px var(--_k-border),inset 0 1px 0 var(--_k-rim);will-change:transform;transition:box-shadow calc(var(--_k-hover-out) * 1ms)}
|
|
619
|
+
.knobs-cmp-grip::before,.knobs-cmp-grip::after{content:"";position:absolute;top:10px;width:1px;height:8px;background:var(--_k-fg-3);transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
620
|
+
.knobs-cmp-grip::before{left:6px}
|
|
621
|
+
.knobs-cmp-grip::after{left:9px}
|
|
622
|
+
.knobs-compare[data-dragging] .knobs-cmp-grip{box-shadow:var(--_k-shadow-overlay),inset 0 0 0 1px var(--_k-border-strong),inset 0 1px 0 var(--_k-rim)}
|
|
623
|
+
.knobs-compare[data-dragging] .knobs-cmp-grip::before,.knobs-compare[data-dragging] .knobs-cmp-grip::after{background:var(--_k-fg)}
|
|
624
|
+
.knobs-cmp-handle:focus-visible .knobs-cmp-grip,.knobs-compare[data-keyboard] .knobs-cmp-handle:focus .knobs-cmp-grip{box-shadow:var(--_k-shadow-overlay),0 0 0 2px var(--_k-accent)}
|
|
613
625
|
/* The split's hairline, down the middle side by side. */
|
|
614
626
|
.knobs-cmp-split{position:absolute;left:0;top:0;width:1px;height:0;margin-left:-.5px;background:color-mix(in srgb,var(--_k-bg) 72%,transparent);pointer-events:none;will-change:transform,opacity}
|
|
615
627
|
/* Zoomed: how far, and a way back, in the stage's corner; it fades in and out, nothing shifts. */
|
|
@@ -618,12 +630,12 @@ var compareCss = `
|
|
|
618
630
|
opacity:0;scale:.96;pointer-events:none;transition:opacity .16s cubic-bezier(.4,0,1,1),scale .16s cubic-bezier(.4,0,1,1)}
|
|
619
631
|
.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)}
|
|
620
632
|
: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;
|
|
621
|
-
height:20px;padding:0
|
|
633
|
+
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}
|
|
622
634
|
: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)}
|
|
623
635
|
.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}
|
|
624
636
|
.knobs-cmp-note:empty{display:none}
|
|
625
637
|
.knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
|
|
626
|
-
.knobs-cmp-timeline-host{flex:none;
|
|
638
|
+
.knobs-cmp-timeline-host{flex:none;min-height:0}
|
|
627
639
|
/* The frame strip: Before over After per column, a difference bar under them, a dot where it peaks. */
|
|
628
640
|
.knobs-ui.knobs-cmp-strip{--_ku-cmp-diff:var(--k-compare-diff,var(--_k-ok));position:relative;width:100%;height:100%;min-height:40px;overflow:hidden;cursor:col-resize;touch-action:none;
|
|
629
641
|
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg)}
|
|
@@ -766,7 +778,7 @@ var init_picker_css = __esm(() => {
|
|
|
766
778
|
.knobs-picker-suggested{flex:none;color:var(--_k-fg-3);font-size:var(--_k-text-meta)}
|
|
767
779
|
.knobs-picker-check{flex:none;display:flex;width:14px;height:14px;color:var(--_k-accent);opacity:0;scale:.6;filter:blur(2px);
|
|
768
780
|
transition:opacity .11s ease-in,scale .11s ease-in,filter .11s ease-in}
|
|
769
|
-
.knobs-picker-check svg{display:block;width:
|
|
781
|
+
.knobs-picker-check svg{display:block;width:12px;height:12px}
|
|
770
782
|
.knobs-picker-option[data-chosen] .knobs-picker-check{opacity:1;scale:1;filter:none;transition:opacity .15s ease-out,scale var(--kp-snappy-ms) var(--kp-snappy),filter .15s ease-out}
|
|
771
783
|
.knobs-picker-body{position:relative;overflow:visible}
|
|
772
784
|
.knobs-picker-body[data-moving]{overflow:hidden}
|
|
@@ -870,6 +882,15 @@ var readoutCss = `
|
|
|
870
882
|
@media(prefers-reduced-motion:reduce){.knobs-rd :where(*){transition:none!important}}
|
|
871
883
|
`;
|
|
872
884
|
|
|
885
|
+
// src/ui/selection-pill-css.ts
|
|
886
|
+
var selectionPillCss = `
|
|
887
|
+
.knobs-pick-pill,.knobs-pick-copy{position:absolute;left:0;top:0;width:0;height:0;pointer-events:none;transition:opacity .1s linear}
|
|
888
|
+
.knobs-pick-pill{background:var(--_k-fill-selected);will-change:transform}
|
|
889
|
+
.knobs-pick-copy{overflow:hidden;will-change:transform}
|
|
890
|
+
.knobs-pick-copy-rows{position:absolute;left:0;top:0;width:0;height:0}
|
|
891
|
+
:is(.knobs-pick-pill,.knobs-pick-copy)[data-off]{opacity:0}
|
|
892
|
+
`;
|
|
893
|
+
|
|
873
894
|
// src/tokens/color.ts
|
|
874
895
|
function linearToOklch([r, g, b, a]) {
|
|
875
896
|
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
@@ -2797,11 +2818,9 @@ var css2 = `
|
|
|
2797
2818
|
.knobs-tree:focus .knobs-tree-row[data-active][aria-selected="true"]::before{background:var(--_ku-select-focus)}
|
|
2798
2819
|
.knobs-tree-row[data-drop="into"]::before{opacity:1;background:color-mix(in srgb,var(--_k-accent) 30%,transparent)}
|
|
2799
2820
|
.knobs-tree-row[data-dragged]{opacity:.38}
|
|
2800
|
-
/* glide:
|
|
2801
|
-
.knobs-tree-
|
|
2802
|
-
|
|
2803
|
-
.knobs-tree-cursor[data-on]{opacity:1}
|
|
2804
|
-
.knobs-tree:focus .knobs-tree-cursor{background:var(--_ku-select-focus)}
|
|
2821
|
+
/* glide: the selection pill on the selected row, in the selection's colour (the row's own stays off). */
|
|
2822
|
+
.knobs-tree>.knobs-pick-pill{background:var(--_ku-select)}
|
|
2823
|
+
.knobs-tree:focus>.knobs-pick-pill{background:var(--_ku-select-focus)}
|
|
2805
2824
|
.knobs-tree[data-glide] .knobs-tree-row[aria-selected="true"]::before{opacity:0!important}
|
|
2806
2825
|
.knobs-tree-row>*{position:relative}
|
|
2807
2826
|
.knobs-tree-caret{flex:none;display:flex;align-items:center;justify-content:center;width:16px;height:100%;color:var(--_k-fg-3);transition:transform var(--_ku-ease-ms) var(--_ku-ease)}
|
|
@@ -2833,22 +2852,20 @@ var css2 = `
|
|
|
2833
2852
|
box-shadow:0 0 0 1px var(--_k-border-strong),0 6px 18px rgba(0,0,0,calc(.35 * var(--_k-shadow-k,1)));pointer-events:none;white-space:nowrap;font:var(--_k-weight) var(--_k-font-size)/1.6 var(--_k-font)}
|
|
2834
2853
|
.knobs-tree-empty{padding:10px var(--_k-inset);color:var(--_k-fg-3)}
|
|
2835
2854
|
|
|
2836
|
-
/* Timeline:
|
|
2837
|
-
|
|
2855
|
+
/* Timeline: play beside a track: the ruler's canvas over a lane of markers, ranges as soft bands,
|
|
2856
|
+
and the playhead, a hairline in the accent with a cap. No clock: the time shows under the
|
|
2857
|
+
pointer on the track (the tag), in seconds, while hovering or scrubbing. */
|
|
2838
2858
|
:where(.knobs-timeline,.knobs-timeline *){box-sizing:border-box}
|
|
2839
2859
|
.knobs-ui.knobs-timeline{display:flex;align-items:stretch;gap:10px;width:100%;height:100%;min-height:52px;padding:6px var(--_k-inset) 8px 8px;outline:none;user-select:none;-webkit-user-select:none;
|
|
2840
2860
|
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
|
|
2841
2861
|
:where(.knobs-ui) :is(.knobs-tl-play,.knobs-tl-edit){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;margin:0;padding:0;outline:none;box-shadow:none}
|
|
2842
|
-
.knobs-tl-transport{flex:none;display:flex;align-items:center
|
|
2862
|
+
.knobs-tl-transport{flex:none;display:flex;align-items:center}
|
|
2843
2863
|
.knobs-ui .knobs-tl-play{flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);min-width:22px;min-height:22px;border-radius:calc(var(--_k-radius) * 999);
|
|
2844
2864
|
background:var(--_k-fill-selected);color:var(--_k-fg);cursor:pointer;transition:background .15s,transform .15s var(--_ku-ease)}
|
|
2845
2865
|
.knobs-ui .knobs-tl-play:hover{background:color-mix(in srgb,var(--_k-fg) 16%,transparent)}
|
|
2846
2866
|
.knobs-ui .knobs-tl-play:active{transform:scale(.94)}
|
|
2847
2867
|
.knobs-tl-play svg{width:12px;height:12px}
|
|
2848
2868
|
.knobs-timeline[data-playing] .knobs-tl-play{background:var(--_k-accent);color:var(--_k-bg)}
|
|
2849
|
-
.knobs-tl-time{display:flex;flex-direction:column;line-height:1.2;font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
|
|
2850
|
-
.knobs-tl-now{font:var(--_k-weight) var(--_k-value-size)/1.2 var(--_k-value-font);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";color:var(--_k-fg);white-space:nowrap}
|
|
2851
|
-
.knobs-tl-sub{font-size:var(--_k-text-meta);color:var(--_k-fg-3);white-space:nowrap}
|
|
2852
2869
|
.knobs-tl-track{position:relative;flex:1;min-width:0;overflow:hidden;border-radius:var(--_k-radius);background:var(--_k-fill);cursor:col-resize;touch-action:none;
|
|
2853
2870
|
transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
2854
2871
|
.knobs-timeline:focus-visible .knobs-tl-track,.knobs-timeline[data-keyboard]:focus .knobs-tl-track{background:color-mix(in srgb,var(--_k-fill-selected) 80%,transparent)}
|
|
@@ -2872,7 +2889,8 @@ var css2 = `
|
|
|
2872
2889
|
.knobs-tl-pin{position:absolute;top:13px;left:-5px;width:10px;height:10px;pointer-events:auto;cursor:grab}
|
|
2873
2890
|
.knobs-tl-pin::after{content:"";position:absolute;inset:1.5px;border-radius:min(2px,var(--_k-radius));background:var(--knobs-tl-c);rotate:45deg;transition:background .15s,scale .2s var(--_ku-ease)}
|
|
2874
2891
|
.knobs-tl-mlabel{position:absolute;top:26px;left:3px;max-width:140px;padding:0 6px;border-radius:max(0px,calc(var(--_k-radius) - 3px));background:var(--_k-fill-selected);color:var(--_k-fg);
|
|
2875
|
-
font-size:var(--_k-text-meta);line-height:17px;white-space:nowrap;
|
|
2892
|
+
font-size:var(--_k-text-meta);line-height:17px;white-space:nowrap;pointer-events:auto;cursor:grab;transition:background .15s,color .15s}
|
|
2893
|
+
.knobs-tl-mtext{display:block;overflow:hidden;white-space:nowrap}
|
|
2876
2894
|
.knobs-tl-marker[data-selected]{--knobs-tl-c:var(--_k-accent)}
|
|
2877
2895
|
.knobs-tl-marker[data-selected] .knobs-tl-mlabel{background:color-mix(in srgb,var(--_k-accent) 24%,var(--_k-fill-selected))}
|
|
2878
2896
|
.knobs-tl-marker[data-selected] .knobs-tl-pin::after{scale:1.2}
|
|
@@ -2893,7 +2911,22 @@ var css2 = `
|
|
|
2893
2911
|
.knobs-timeline[data-scrubbing] .knobs-tl-hover{opacity:0}
|
|
2894
2912
|
@media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
|
|
2895
2913
|
@media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
|
|
2896
|
-
|
|
2914
|
+
.knobs-tl-tag{position:absolute;left:0;bottom:2px;font-size:var(--_k-text-meta);line-height:16px;color:var(--_k-fg);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";white-space:nowrap;pointer-events:none;opacity:0}
|
|
2915
|
+
.knobs-tl-tag[data-on]{opacity:1}
|
|
2916
|
+
/* The lane (variant: "lane"): one slim track of ticks under a compare or a video; markers are
|
|
2917
|
+
faint chapter ticks, the current one in text colour; nothing to edit. */
|
|
2918
|
+
.knobs-ui.knobs-timeline[data-variant="lane"]{height:auto;min-height:0;padding:0;gap:var(--_k-gap)}
|
|
2919
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-track{height:var(--_k-control-h)}
|
|
2920
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-ruler{height:100%}
|
|
2921
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-lane{top:0}
|
|
2922
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-marker{top:0}
|
|
2923
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-marker::before{top:0;bottom:50%;left:-.75px;width:1.5px;background:var(--_k-fg-3)}
|
|
2924
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-marker[data-current]::before{background:var(--_k-fg)}
|
|
2925
|
+
.knobs-timeline[data-variant="lane"] :is(.knobs-tl-pin,.knobs-tl-mlabel,.knobs-tl-rlabel){display:none}
|
|
2926
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-head::before{top:0}
|
|
2927
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-head::after{content:none}
|
|
2928
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-tag{top:2px;bottom:auto}
|
|
2929
|
+
`, uiCss = () => aliased(css2 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + selectionPillCss) + rolling_css_default, RESTYLED = ".knobs-ui,.knobs-panel,.knobs-chip,.knobs-notice", lookDefaults, tune = (s, m) => {
|
|
2897
2930
|
s.omega = m.omega;
|
|
2898
2931
|
s.zeta = m.zeta;
|
|
2899
2932
|
return s;
|
|
@@ -4659,6 +4692,283 @@ var init_tree_model = __esm(() => {
|
|
|
4659
4692
|
};
|
|
4660
4693
|
});
|
|
4661
4694
|
|
|
4695
|
+
// src/ui/icons.ts
|
|
4696
|
+
function icon(name, options = {}) {
|
|
4697
|
+
const size = options.small ? 12 : 16;
|
|
4698
|
+
return `<svg class="knobs-icon" viewBox="0 0 256 256" width="${size}" height="${size}" fill="currentColor" aria-hidden="true" focusable="false"><path d="${PATHS[name]}"/></svg>`;
|
|
4699
|
+
}
|
|
4700
|
+
var PATHS, ICON_NAMES;
|
|
4701
|
+
var init_icons = __esm(() => {
|
|
4702
|
+
PATHS = {
|
|
4703
|
+
arrowLeft: "M224,128a8,8,0,0,1-8,8H59.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L59.31,120H216A8,8,0,0,1,224,128Z",
|
|
4704
|
+
arrowUpRight: "M200,64V168a8,8,0,0,1-16,0V83.31L69.66,197.66a8,8,0,0,1-11.32-11.32L172.69,72H88a8,8,0,0,1,0-16H192A8,8,0,0,1,200,64Z",
|
|
4705
|
+
arrowSquareOut: "M224,104a8,8,0,0,1-16,0V59.32l-66.33,66.34a8,8,0,0,1-11.32-11.32L196.68,48H152a8,8,0,0,1,0-16h64a8,8,0,0,1,8,8Zm-40,24a8,8,0,0,0-8,8v72H48V80h72a8,8,0,0,0,0-16H48A16,16,0,0,0,32,80V208a16,16,0,0,0,16,16H176a16,16,0,0,0,16-16V136A8,8,0,0,0,184,128Z",
|
|
4706
|
+
arrowCounterClockwise: "M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.8a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0V85.8L60.25,60A96,96,0,0,1,224,128Z",
|
|
4707
|
+
arrowUDownLeft: "M232,112a64.07,64.07,0,0,1-64,64H51.31l34.35,34.34a8,8,0,0,1-11.32,11.32l-48-48a8,8,0,0,1,0-11.32l48-48a8,8,0,0,1,11.32,11.32L51.31,160H168a48,48,0,0,0,0-96H80a8,8,0,0,1,0-16h88A64.07,64.07,0,0,1,232,112Z",
|
|
4708
|
+
arrowsHorizontal: "M136,40V216a8,8,0,0,1-16,0V40a8,8,0,0,1,16,0ZM96,120H35.31l18.35-18.34A8,8,0,0,0,42.34,90.34l-32,32a8,8,0,0,0,0,11.32l32,32a8,8,0,0,0,11.32-11.32L35.31,136H96a8,8,0,0,0,0-16Zm149.66,2.34-32-32a8,8,0,0,0-11.32,11.32L220.69,120H160a8,8,0,0,0,0,16h60.69l-18.35,18.34a8,8,0,0,0,11.32,11.32l32-32A8,8,0,0,0,245.66,122.34Z",
|
|
4709
|
+
caretDown: "M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z",
|
|
4710
|
+
caretLeft: "M165.66,202.34a8,8,0,0,1-11.32,11.32l-80-80a8,8,0,0,1,0-11.32l80-80a8,8,0,0,1,11.32,11.32L91.31,128Z",
|
|
4711
|
+
caretRight: "M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z",
|
|
4712
|
+
caretUp: "M213.66,165.66a8,8,0,0,1-11.32,0L128,91.31,53.66,165.66a8,8,0,0,1-11.32-11.32l80-80a8,8,0,0,1,11.32,0l80,80A8,8,0,0,1,213.66,165.66Z",
|
|
4713
|
+
check: "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",
|
|
4714
|
+
checkCircle: "M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z",
|
|
4715
|
+
circle: "M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Z",
|
|
4716
|
+
copy: "M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z",
|
|
4717
|
+
cornersIn: "M152,96V48a8,8,0,0,1,16,0V88h40a8,8,0,0,1,0,16H160A8,8,0,0,1,152,96ZM96,152H48a8,8,0,0,0,0,16H88v40a8,8,0,0,0,16,0V160A8,8,0,0,0,96,152Zm112,0H160a8,8,0,0,0-8,8v48a8,8,0,0,0,16,0V168h40a8,8,0,0,0,0-16ZM96,40a8,8,0,0,0-8,8V88H48a8,8,0,0,0,0,16H96a8,8,0,0,0,8-8V48A8,8,0,0,0,96,40Z",
|
|
4718
|
+
cornersOut: "M216,48V88a8,8,0,0,1-16,0V56H168a8,8,0,0,1,0-16h40A8,8,0,0,1,216,48ZM88,200H56V168a8,8,0,0,0-16,0v40a8,8,0,0,0,8,8H88a8,8,0,0,0,0-16Zm120-40a8,8,0,0,0-8,8v32H168a8,8,0,0,0,0,16h40a8,8,0,0,0,8-8V168A8,8,0,0,0,208,160ZM88,40H48a8,8,0,0,0-8,8V88a8,8,0,0,0,16,0V56H88a8,8,0,0,0,0-16Z",
|
|
4719
|
+
folder: "M216,72H130.67L102.93,51.2a16.12,16.12,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V200a16,16,0,0,0,16,16H216.89A15.13,15.13,0,0,0,232,200.89V88A16,16,0,0,0,216,72Zm0,128H40V64H93.33L123.2,86.4A8,8,0,0,0,128,88h88Z",
|
|
4720
|
+
keyboard: "M224,48H32A16,16,0,0,0,16,64V192a16,16,0,0,0,16,16H224a16,16,0,0,0,16-16V64A16,16,0,0,0,224,48Zm0,144H32V64H224V192Zm-16-64a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16H200A8,8,0,0,1,208,128Zm0-32a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16H200A8,8,0,0,1,208,96ZM72,160a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16h8A8,8,0,0,1,72,160Zm96,0a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,160Zm40,0a8,8,0,0,1-8,8h-8a8,8,0,0,1,0-16h8A8,8,0,0,1,208,160Z",
|
|
4721
|
+
magnifyingGlass: "M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z",
|
|
4722
|
+
magnifyingGlassMinus: "M152,112a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h64A8,8,0,0,1,152,112Zm77.66,117.66a8,8,0,0,1-11.32,0l-50.06-50.07a88.11,88.11,0,1,1,11.31-11.31l50.07,50.06A8,8,0,0,1,229.66,229.66ZM112,184a72,72,0,1,0-72-72A72.08,72.08,0,0,0,112,184Z",
|
|
4723
|
+
magnifyingGlassPlus: "M152,112a8,8,0,0,1-8,8H120v24a8,8,0,0,1-16,0V120H80a8,8,0,0,1,0-16h24V80a8,8,0,0,1,16,0v24h24A8,8,0,0,1,152,112Zm77.66,117.66a8,8,0,0,1-11.32,0l-50.06-50.07a88.11,88.11,0,1,1,11.31-11.31l50.07,50.06A8,8,0,0,1,229.66,229.66ZM112,184a72,72,0,1,0-72-72A72.08,72.08,0,0,0,112,184Z",
|
|
4724
|
+
microphone: "M128,176a48.05,48.05,0,0,0,48-48V64a48,48,0,0,0-96,0v64A48.05,48.05,0,0,0,128,176ZM96,64a32,32,0,0,1,64,0v64a32,32,0,0,1-64,0Zm40,143.6V240a8,8,0,0,1-16,0V207.6A80.11,80.11,0,0,1,48,128a8,8,0,0,1,16,0,64,64,0,0,0,128,0,8,8,0,0,1,16,0A80.11,80.11,0,0,1,136,207.6Z",
|
|
4725
|
+
paperPlaneRight: "M231.87,114l-168-95.89A16,16,0,0,0,40.92,37.34L71.55,128,40.92,218.67A16,16,0,0,0,56,240a16.15,16.15,0,0,0,7.93-2.1l167.92-96.05a16,16,0,0,0,.05-27.89ZM56,224a.56.56,0,0,0,0-.12L85.74,136H144a8,8,0,0,0,0-16H85.74L56.06,32.16A.46.46,0,0,0,56,32l168,95.83Z",
|
|
4726
|
+
pencilSimple: "M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z",
|
|
4727
|
+
repeat: "M24,128A72.08,72.08,0,0,1,96,56H204.69L194.34,45.66a8,8,0,0,1,11.32-11.32l24,24a8,8,0,0,1,0,11.32l-24,24a8,8,0,0,1-11.32-11.32L204.69,72H96a56.06,56.06,0,0,0-56,56,8,8,0,0,1-16,0Zm200-8a8,8,0,0,0-8,8,56.06,56.06,0,0,1-56,56H51.31l10.35-10.34a8,8,0,0,0-11.32-11.32l-24,24a8,8,0,0,0,0,11.32l24,24a8,8,0,0,0,11.32-11.32L51.31,200H160a72.08,72.08,0,0,0,72-72A8,8,0,0,0,224,120Z",
|
|
4728
|
+
sidebar: "M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM40,56H80V200H40ZM216,200H96V56H216V200Z",
|
|
4729
|
+
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"
|
|
4730
|
+
};
|
|
4731
|
+
ICON_NAMES = Object.keys(PATHS);
|
|
4732
|
+
});
|
|
4733
|
+
|
|
4734
|
+
// src/ui/selection-pill.ts
|
|
4735
|
+
function pillSpring(distance, base = SPRINGS.default) {
|
|
4736
|
+
return distance > 400 ? { duration: base.duration * 1.4, bounce: base.bounce } : base;
|
|
4737
|
+
}
|
|
4738
|
+
function pillBoxOf(row, container) {
|
|
4739
|
+
return {
|
|
4740
|
+
x: row.left - container.left - container.clientLeft + container.scrollLeft,
|
|
4741
|
+
y: row.top - container.top - container.clientTop + container.scrollTop,
|
|
4742
|
+
w: row.width,
|
|
4743
|
+
h: row.height
|
|
4744
|
+
};
|
|
4745
|
+
}
|
|
4746
|
+
function pillRadius(from, to, h, k) {
|
|
4747
|
+
const t = Math.abs(to.h - from.h) > 0.5 ? (h - from.h) / (to.h - from.h) : Number.isFinite(k) ? k : 1;
|
|
4748
|
+
return from.r + (to.r - from.r) * Math.min(1, Math.max(0, t));
|
|
4749
|
+
}
|
|
4750
|
+
function pillProgress(from, to, at) {
|
|
4751
|
+
const d = Math.hypot(to.x - from.x, to.y - from.y);
|
|
4752
|
+
if (d < 0.5)
|
|
4753
|
+
return 1;
|
|
4754
|
+
const p = ((at.x - from.x) * (to.x - from.x) + (at.y - from.y) * (to.y - from.y)) / (d * d);
|
|
4755
|
+
return Math.min(1, Math.max(0, p));
|
|
4756
|
+
}
|
|
4757
|
+
function planPill(state, next, flags = {}) {
|
|
4758
|
+
if (!next)
|
|
4759
|
+
return { kind: "hide" };
|
|
4760
|
+
if (!state.shown || !state.at || flags.first || flags.reduced || flags.snap)
|
|
4761
|
+
return { kind: "land", box: next };
|
|
4762
|
+
const t = state.target;
|
|
4763
|
+
if (t && t.x === next.x && t.y === next.y && t.w === next.w && t.h === next.h)
|
|
4764
|
+
return { kind: "stay" };
|
|
4765
|
+
const d = Math.hypot(next.x - state.at.x, next.y - state.at.y);
|
|
4766
|
+
return { kind: "travel", box: next, spring: pillSpring(d) };
|
|
4767
|
+
}
|
|
4768
|
+
function selectionPill(container, options) {
|
|
4769
|
+
const doc = container.ownerDocument;
|
|
4770
|
+
const win = doc.defaultView;
|
|
4771
|
+
injectUiStyles(doc);
|
|
4772
|
+
if (win.getComputedStyle(container).position === "static")
|
|
4773
|
+
container.style.position = "relative";
|
|
4774
|
+
const pill = doc.createElement("i");
|
|
4775
|
+
pill.className = "knobs-pick-pill";
|
|
4776
|
+
pill.setAttribute("aria-hidden", "true");
|
|
4777
|
+
pill.dataset.off = "";
|
|
4778
|
+
const copyOn = options.copy !== false;
|
|
4779
|
+
const copy = doc.createElement("div");
|
|
4780
|
+
copy.className = "knobs-pick-copy";
|
|
4781
|
+
copy.setAttribute("aria-hidden", "true");
|
|
4782
|
+
copy.inert = true;
|
|
4783
|
+
copy.dataset.off = "";
|
|
4784
|
+
const copyRows = doc.createElement("div");
|
|
4785
|
+
copyRows.className = "knobs-pick-copy-rows";
|
|
4786
|
+
copy.append(copyRows);
|
|
4787
|
+
container.prepend(pill);
|
|
4788
|
+
if (copyOn)
|
|
4789
|
+
container.append(copy);
|
|
4790
|
+
const { omega, zeta } = springMotion(SPRINGS.default);
|
|
4791
|
+
const springs = [0, 0, 0, 0].map(() => new Spring(0, omega, zeta, 0.01));
|
|
4792
|
+
const [sx, sy, sw, sh] = springs;
|
|
4793
|
+
let shown = false;
|
|
4794
|
+
let first = true;
|
|
4795
|
+
let target = null;
|
|
4796
|
+
let from = { x: 0, y: 0, w: 0, h: 0 };
|
|
4797
|
+
let fromR = 0;
|
|
4798
|
+
let toR = 0;
|
|
4799
|
+
let rowBoxes = [];
|
|
4800
|
+
const clones = new Map;
|
|
4801
|
+
let disposed = false;
|
|
4802
|
+
const radiusOf = (row) => options.radius?.(row) ?? (Number.parseFloat(win.getComputedStyle(row).borderTopLeftRadius) || 0);
|
|
4803
|
+
const rowsNow = () => options.rows ? [...options.rows()] : [...container.children].filter((n) => n !== pill && n !== copy);
|
|
4804
|
+
const measure = (row) => {
|
|
4805
|
+
const c = container.getBoundingClientRect();
|
|
4806
|
+
const r = row.getBoundingClientRect();
|
|
4807
|
+
return pillBoxOf(r, {
|
|
4808
|
+
left: c.left,
|
|
4809
|
+
top: c.top,
|
|
4810
|
+
scrollLeft: container.scrollLeft,
|
|
4811
|
+
scrollTop: container.scrollTop,
|
|
4812
|
+
clientLeft: container.clientLeft,
|
|
4813
|
+
clientTop: container.clientTop
|
|
4814
|
+
});
|
|
4815
|
+
};
|
|
4816
|
+
const at = () => ({ x: sx.x, y: sy.x, w: sw.x, h: sh.x });
|
|
4817
|
+
const syncCopy = (span) => {
|
|
4818
|
+
if (!copyOn)
|
|
4819
|
+
return;
|
|
4820
|
+
const keep = new Set;
|
|
4821
|
+
for (const { row, box } of rowBoxes) {
|
|
4822
|
+
if (!boxesMeet(box, span))
|
|
4823
|
+
continue;
|
|
4824
|
+
keep.add(row);
|
|
4825
|
+
let n = clones.get(row);
|
|
4826
|
+
if (!n) {
|
|
4827
|
+
n = row.cloneNode(true);
|
|
4828
|
+
n.removeAttribute("id");
|
|
4829
|
+
for (const d of n.querySelectorAll("[id]"))
|
|
4830
|
+
d.removeAttribute("id");
|
|
4831
|
+
clones.set(row, n);
|
|
4832
|
+
copyRows.append(n);
|
|
4833
|
+
}
|
|
4834
|
+
const s = n.style;
|
|
4835
|
+
s.position = "absolute";
|
|
4836
|
+
s.inset = "auto";
|
|
4837
|
+
s.left = `${box.x}px`;
|
|
4838
|
+
s.top = `${box.y}px`;
|
|
4839
|
+
s.width = `${box.w}px`;
|
|
4840
|
+
s.height = `${box.h}px`;
|
|
4841
|
+
s.margin = "0";
|
|
4842
|
+
s.transform = "none";
|
|
4843
|
+
s.clipPath = "";
|
|
4844
|
+
s.opacity = "";
|
|
4845
|
+
s.boxSizing = "border-box";
|
|
4846
|
+
s.color = options.color ?? "var(--_k-fg)";
|
|
4847
|
+
}
|
|
4848
|
+
for (const [row, n] of clones)
|
|
4849
|
+
if (!keep.has(row)) {
|
|
4850
|
+
n.remove();
|
|
4851
|
+
clones.delete(row);
|
|
4852
|
+
}
|
|
4853
|
+
};
|
|
4854
|
+
const paint = () => {
|
|
4855
|
+
const b = at();
|
|
4856
|
+
const x0 = grid(b.x);
|
|
4857
|
+
const y0 = grid(b.y);
|
|
4858
|
+
const w = grid(b.x + b.w) - x0;
|
|
4859
|
+
const h = grid(b.y + b.h) - y0;
|
|
4860
|
+
const r = target ? pillRadius({ h: from.h, r: fromR }, { h: target.h, r: toR }, b.h, pillProgress(from, target, b)) : toR;
|
|
4861
|
+
const radius = `${Math.max(0, Math.min(r, h / 2, w / 2)).toFixed(2)}px`;
|
|
4862
|
+
for (const n of copyOn ? [pill, copy] : [pill]) {
|
|
4863
|
+
n.style.transform = `translate3d(${x0}px,${y0}px,0)`;
|
|
4864
|
+
n.style.width = `${w}px`;
|
|
4865
|
+
n.style.height = `${h}px`;
|
|
4866
|
+
n.style.borderRadius = radius;
|
|
4867
|
+
}
|
|
4868
|
+
if (copyOn)
|
|
4869
|
+
copyRows.style.transform = `translate3d(${-x0}px,${-y0}px,0)`;
|
|
4870
|
+
};
|
|
4871
|
+
const loop = frameLoop(win, (dt) => {
|
|
4872
|
+
for (const s of springs)
|
|
4873
|
+
s.step(dt);
|
|
4874
|
+
paint();
|
|
4875
|
+
return springs.some((s) => !s.resting);
|
|
4876
|
+
});
|
|
4877
|
+
const show = (on) => {
|
|
4878
|
+
shown = on;
|
|
4879
|
+
pill.toggleAttribute("data-off", !on);
|
|
4880
|
+
copy.toggleAttribute("data-off", !on);
|
|
4881
|
+
container.toggleAttribute("data-pick-empty", !on);
|
|
4882
|
+
};
|
|
4883
|
+
const update = (o) => {
|
|
4884
|
+
if (disposed)
|
|
4885
|
+
return;
|
|
4886
|
+
const row = options.selected();
|
|
4887
|
+
const next = row?.isConnected ? measure(row) : null;
|
|
4888
|
+
const plan = planPill({ shown, at: shown ? at() : null, target }, next, {
|
|
4889
|
+
first,
|
|
4890
|
+
reduced: reducedMotion(win),
|
|
4891
|
+
...o?.snap ? { snap: true } : {}
|
|
4892
|
+
});
|
|
4893
|
+
first = false;
|
|
4894
|
+
for (const n of clones.values())
|
|
4895
|
+
n.remove();
|
|
4896
|
+
clones.clear();
|
|
4897
|
+
rowBoxes = copyOn ? rowsNow().filter((r) => r.isConnected).map((r) => ({ row: r, box: measure(r) })) : [];
|
|
4898
|
+
if (plan.kind === "hide") {
|
|
4899
|
+
if (shown)
|
|
4900
|
+
show(false);
|
|
4901
|
+
return;
|
|
4902
|
+
}
|
|
4903
|
+
if (plan.kind === "stay") {
|
|
4904
|
+
syncCopy(boxUnion(at(), target));
|
|
4905
|
+
return;
|
|
4906
|
+
}
|
|
4907
|
+
const r = radiusOf(row);
|
|
4908
|
+
if (plan.kind === "land") {
|
|
4909
|
+
sx.snap(plan.box.x);
|
|
4910
|
+
sy.snap(plan.box.y);
|
|
4911
|
+
sw.snap(plan.box.w);
|
|
4912
|
+
sh.snap(plan.box.h);
|
|
4913
|
+
from = plan.box;
|
|
4914
|
+
fromR = r;
|
|
4915
|
+
toR = r;
|
|
4916
|
+
target = plan.box;
|
|
4917
|
+
syncCopy(plan.box);
|
|
4918
|
+
paint();
|
|
4919
|
+
loop.stop();
|
|
4920
|
+
if (!shown)
|
|
4921
|
+
show(true);
|
|
4922
|
+
return;
|
|
4923
|
+
}
|
|
4924
|
+
const m = springMotion(plan.spring);
|
|
4925
|
+
for (const s of springs) {
|
|
4926
|
+
s.omega = m.omega;
|
|
4927
|
+
s.zeta = m.zeta;
|
|
4928
|
+
}
|
|
4929
|
+
const now = at();
|
|
4930
|
+
fromR = target ? pillRadius({ h: from.h, r: fromR }, { h: target.h, r: toR }, now.h, pillProgress(from, target, now)) : r;
|
|
4931
|
+
from = now;
|
|
4932
|
+
toR = r;
|
|
4933
|
+
target = plan.box;
|
|
4934
|
+
sx.set(plan.box.x);
|
|
4935
|
+
sy.set(plan.box.y);
|
|
4936
|
+
sw.set(plan.box.w);
|
|
4937
|
+
sh.set(plan.box.h);
|
|
4938
|
+
syncCopy(boxUnion(now, plan.box));
|
|
4939
|
+
loop.wake();
|
|
4940
|
+
};
|
|
4941
|
+
const ro = "ResizeObserver" in win ? new win.ResizeObserver(() => update({ snap: true })) : null;
|
|
4942
|
+
ro?.observe(container);
|
|
4943
|
+
update();
|
|
4944
|
+
return {
|
|
4945
|
+
element: pill,
|
|
4946
|
+
update,
|
|
4947
|
+
dispose() {
|
|
4948
|
+
disposed = true;
|
|
4949
|
+
loop.stop();
|
|
4950
|
+
ro?.disconnect();
|
|
4951
|
+
pill.remove();
|
|
4952
|
+
copy.remove();
|
|
4953
|
+
container.removeAttribute("data-pick-empty");
|
|
4954
|
+
}
|
|
4955
|
+
};
|
|
4956
|
+
}
|
|
4957
|
+
var boxesMeet = (a, b) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h, boxUnion = (a, b) => {
|
|
4958
|
+
const x = Math.min(a.x, b.x);
|
|
4959
|
+
const y = Math.min(a.y, b.y);
|
|
4960
|
+
return {
|
|
4961
|
+
x,
|
|
4962
|
+
y,
|
|
4963
|
+
w: Math.max(a.x + a.w, b.x + b.w) - x,
|
|
4964
|
+
h: Math.max(a.y + a.h, b.y + b.h) - y
|
|
4965
|
+
};
|
|
4966
|
+
}, grid = (v) => Math.round(v * 64) / 64;
|
|
4967
|
+
var init_selection_pill = __esm(() => {
|
|
4968
|
+
init_motion();
|
|
4969
|
+
init_look();
|
|
4970
|
+
});
|
|
4971
|
+
|
|
4662
4972
|
// src/core/storage.ts
|
|
4663
4973
|
function browserStorage() {
|
|
4664
4974
|
return typeof window === "undefined" ? null : window.localStorage;
|
|
@@ -4981,10 +5291,25 @@ function advance(t, dt, o) {
|
|
|
4981
5291
|
}
|
|
4982
5292
|
return { t: hi, ended: true, wrapped: false };
|
|
4983
5293
|
}
|
|
4984
|
-
|
|
5294
|
+
function moveHead(head, t, motion, catching, minGap = 0) {
|
|
5295
|
+
if (motion === "jump" && Math.abs(t - head.x) > minGap)
|
|
5296
|
+
return { x: head.x, target: t, catching: true };
|
|
5297
|
+
if (catching)
|
|
5298
|
+
return { x: head.x + (t - head.target), target: t, catching: true };
|
|
5299
|
+
return { x: t, target: t, catching: false };
|
|
5300
|
+
}
|
|
5301
|
+
function currentMarker(markers, t, fps) {
|
|
5302
|
+
let best = null;
|
|
5303
|
+
for (const m of markers)
|
|
5304
|
+
if (m.time <= t + 0.5 / fps && (!best || m.time >= best.time))
|
|
5305
|
+
best = m;
|
|
5306
|
+
return best;
|
|
5307
|
+
}
|
|
5308
|
+
var SECOND_STEPS, FRAME_STEPS, toFrame = (t, fps) => Math.round(t * fps), frameTime = (t, fps) => Math.round(t * fps) / fps, round = (t) => Math.round(t * 1e6) / 1e6, CATCH_UP;
|
|
4985
5309
|
var init_timeline_model = __esm(() => {
|
|
4986
5310
|
SECOND_STEPS = [0.1, 0.2, 0.25, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600];
|
|
4987
5311
|
FRAME_STEPS = [1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 9000, 18000];
|
|
5312
|
+
CATCH_UP = { duration: 0.15, bounce: 0 };
|
|
4988
5313
|
});
|
|
4989
5314
|
|
|
4990
5315
|
// src/ui/timeline.ts
|
|
@@ -4997,6 +5322,7 @@ function timeline(container, options) {
|
|
|
4997
5322
|
let speed = options.speed ?? 1;
|
|
4998
5323
|
let repeat = options.repeat ?? true;
|
|
4999
5324
|
const arrows = options.arrows ?? "frames";
|
|
5325
|
+
const lane = options.variant === "lane";
|
|
5000
5326
|
let time = Math.min(duration, Math.max(0, options.time ?? 0));
|
|
5001
5327
|
let playing = false;
|
|
5002
5328
|
let markers = [...options.markers ?? []];
|
|
@@ -5030,27 +5356,27 @@ function timeline(container, options) {
|
|
|
5030
5356
|
root.setAttribute("aria-label", options.label ?? "Timeline");
|
|
5031
5357
|
root.setAttribute("aria-keyshortcuts", "Space ArrowLeft ArrowRight , . Home End [ ] I O M");
|
|
5032
5358
|
applyLook2(root, options);
|
|
5359
|
+
if (lane)
|
|
5360
|
+
root.dataset.variant = "lane";
|
|
5033
5361
|
const transport = el("div", "knobs-tl-transport");
|
|
5034
5362
|
const playBtn = el("button", "knobs-tl-play");
|
|
5035
5363
|
playBtn.type = "button";
|
|
5036
5364
|
playBtn.tabIndex = -1;
|
|
5037
|
-
|
|
5038
|
-
const now = el("span", "knobs-tl-now");
|
|
5039
|
-
const sub = el("span", "knobs-tl-sub");
|
|
5040
|
-
readout.append(now, sub);
|
|
5041
|
-
transport.append(playBtn, readout);
|
|
5365
|
+
transport.append(playBtn);
|
|
5042
5366
|
const track = el("div", "knobs-tl-track");
|
|
5043
5367
|
const canvas = el("canvas", "knobs-tl-ruler");
|
|
5044
5368
|
canvas.setAttribute("aria-hidden", "true");
|
|
5045
|
-
const
|
|
5369
|
+
const markerLane = el("div", "knobs-tl-lane");
|
|
5046
5370
|
const hover = el("div", "knobs-tl-hover");
|
|
5371
|
+
const tag = el("span", "knobs-tl-tag");
|
|
5372
|
+
tag.setAttribute("aria-hidden", "true");
|
|
5047
5373
|
const head = el("div", "knobs-tl-head");
|
|
5048
5374
|
head.id = `${id}-head`;
|
|
5049
5375
|
head.setAttribute("role", "slider");
|
|
5050
5376
|
head.setAttribute("aria-label", "Playhead");
|
|
5051
5377
|
head.setAttribute("aria-valuemin", "0");
|
|
5052
5378
|
head.setAttribute("aria-valuemax", String(duration));
|
|
5053
|
-
track.append(canvas,
|
|
5379
|
+
track.append(canvas, markerLane, hover, head, tag);
|
|
5054
5380
|
root.append(transport, track);
|
|
5055
5381
|
container.append(root);
|
|
5056
5382
|
let width = 1;
|
|
@@ -5097,13 +5423,14 @@ function timeline(container, options) {
|
|
|
5097
5423
|
let drawn = "";
|
|
5098
5424
|
const drawRuler = () => {
|
|
5099
5425
|
const v = view();
|
|
5100
|
-
const key = `${v.start.toFixed(6)}:${v.span.toFixed(6)}:${width}:${units}:${fps}`;
|
|
5426
|
+
const key = `${v.start.toFixed(6)}:${v.span.toFixed(6)}:${width}:${height}:${units}:${fps}`;
|
|
5101
5427
|
if (key === drawn)
|
|
5102
5428
|
return;
|
|
5103
5429
|
drawn = key;
|
|
5104
5430
|
const dpr = win.devicePixelRatio || 1;
|
|
5105
5431
|
const w = Math.max(1, Math.round(width * dpr));
|
|
5106
|
-
const
|
|
5432
|
+
const rh = lane ? height : RULER;
|
|
5433
|
+
const h = Math.round(rh * dpr);
|
|
5107
5434
|
if (canvas.width !== w || canvas.height !== h) {
|
|
5108
5435
|
canvas.width = w;
|
|
5109
5436
|
canvas.height = h;
|
|
@@ -5112,8 +5439,19 @@ function timeline(container, options) {
|
|
|
5112
5439
|
if (!ctx)
|
|
5113
5440
|
return;
|
|
5114
5441
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
5115
|
-
ctx.clearRect(0, 0, width,
|
|
5442
|
+
ctx.clearRect(0, 0, width, rh);
|
|
5116
5443
|
const ticks = rulerTicks(v, width, { fps, units });
|
|
5444
|
+
if (lane) {
|
|
5445
|
+
ctx.fillStyle = colors.tick;
|
|
5446
|
+
for (const t of ticks.minor)
|
|
5447
|
+
if (t >= -0.000000001 && t <= duration + 0.000000001)
|
|
5448
|
+
ctx.fillRect(xOf(t) - 0.5, rh - 4, 1, 4);
|
|
5449
|
+
ctx.fillStyle = colors.major;
|
|
5450
|
+
for (const m of ticks.major)
|
|
5451
|
+
if (m.t >= -0.000000001 && m.t <= duration + 0.000000001)
|
|
5452
|
+
ctx.fillRect(xOf(m.t) - 0.5, rh - 7, 1, 7);
|
|
5453
|
+
return;
|
|
5454
|
+
}
|
|
5117
5455
|
const px = (t) => Math.round(xOf(t)) + 0.5;
|
|
5118
5456
|
ctx.fillStyle = colors.line;
|
|
5119
5457
|
ctx.fillRect(0, RULER - 1, width, 1);
|
|
@@ -5153,18 +5491,21 @@ function timeline(container, options) {
|
|
|
5153
5491
|
n.dataset.id = m.id;
|
|
5154
5492
|
const pin = el("span", "knobs-tl-pin");
|
|
5155
5493
|
const label = el("span", "knobs-tl-mlabel");
|
|
5494
|
+
label.append(el("span", "knobs-tl-mtext knobs-scroll-fade-x"));
|
|
5156
5495
|
n.append(pin, label);
|
|
5157
|
-
|
|
5496
|
+
markerLane.append(n);
|
|
5158
5497
|
markerEls.set(m.id, n);
|
|
5159
5498
|
if (built)
|
|
5160
5499
|
enter(n);
|
|
5161
5500
|
}
|
|
5162
5501
|
const label = n.querySelector(".knobs-tl-mlabel");
|
|
5163
|
-
|
|
5164
|
-
|
|
5502
|
+
const text = label.firstElementChild;
|
|
5503
|
+
if (text.textContent !== (m.label ?? ""))
|
|
5504
|
+
text.textContent = m.label ?? "";
|
|
5165
5505
|
label.hidden = !m.label;
|
|
5166
5506
|
n.style.setProperty("--knobs-tl-color", m.color ?? "");
|
|
5167
5507
|
n.toggleAttribute("data-selected", selected?.kind === "marker" && selected.id === m.id);
|
|
5508
|
+
n.toggleAttribute("data-current", m.id === currentId);
|
|
5168
5509
|
n.title = `${m.label ? `${m.label} · ` : ""}${clock(m.time)}`;
|
|
5169
5510
|
}
|
|
5170
5511
|
placeMarkers();
|
|
@@ -5192,7 +5533,7 @@ function timeline(container, options) {
|
|
|
5192
5533
|
b.dataset.edge = "end";
|
|
5193
5534
|
const label = el("span", "knobs-tl-rlabel");
|
|
5194
5535
|
n.append(label, a, b);
|
|
5195
|
-
track.insertBefore(n,
|
|
5536
|
+
track.insertBefore(n, markerLane);
|
|
5196
5537
|
rangeEls.set(r.id, n);
|
|
5197
5538
|
if (built)
|
|
5198
5539
|
enter(n);
|
|
@@ -5241,16 +5582,17 @@ function timeline(container, options) {
|
|
|
5241
5582
|
};
|
|
5242
5583
|
const clock = (t) => units === "frames" ? `f${toFrame(t, fps)}` : `${timeLabel(t, "seconds", fps, 0.01)}`;
|
|
5243
5584
|
let shownHead = time;
|
|
5585
|
+
let currentId = null;
|
|
5244
5586
|
const renderHead = () => {
|
|
5245
5587
|
head.style.transform = `translateX(${xOf(shownHead).toFixed(2)}px)`;
|
|
5246
|
-
const main = units === "frames" ? `f${toFrame(time, fps)}` : `${time.toFixed(2)}s`;
|
|
5247
|
-
const minor = units === "frames" ? `${time.toFixed(2)}s` : `f${toFrame(time, fps)}`;
|
|
5248
|
-
if (now.textContent !== main)
|
|
5249
|
-
now.textContent = main;
|
|
5250
|
-
if (sub.textContent !== minor)
|
|
5251
|
-
sub.textContent = minor;
|
|
5252
5588
|
head.setAttribute("aria-valuenow", time.toFixed(3));
|
|
5253
5589
|
head.setAttribute("aria-valuetext", `${time.toFixed(2)} seconds, frame ${toFrame(time, fps)}`);
|
|
5590
|
+
const cur = currentMarker(markers, time, fps)?.id ?? null;
|
|
5591
|
+
if (cur !== currentId) {
|
|
5592
|
+
markerEls.get(currentId ?? "")?.removeAttribute("data-current");
|
|
5593
|
+
currentId = cur;
|
|
5594
|
+
markerEls.get(cur ?? "")?.setAttribute("data-current", "");
|
|
5595
|
+
}
|
|
5254
5596
|
};
|
|
5255
5597
|
let viewSent = "";
|
|
5256
5598
|
const render = () => {
|
|
@@ -5276,18 +5618,27 @@ function timeline(container, options) {
|
|
|
5276
5618
|
};
|
|
5277
5619
|
const caret = new Spring(time, GLIDE.omega, GLIDE.zeta, 0.00001);
|
|
5278
5620
|
let gliding = false;
|
|
5279
|
-
const
|
|
5280
|
-
const
|
|
5281
|
-
|
|
5621
|
+
const headTo = (shown, motion) => {
|
|
5622
|
+
const from = gliding ? { x: caret.x, target: caret.target } : { x: shownHead, target: shownHead };
|
|
5623
|
+
const jump = motion === "jump" && !reducedMotion(win);
|
|
5624
|
+
const next = moveHead(from, shown, jump ? "jump" : "follow", gliding, 2 / pxPerSec());
|
|
5625
|
+
if (next.catching) {
|
|
5282
5626
|
if (!gliding)
|
|
5283
|
-
caret.snap(
|
|
5284
|
-
caret.
|
|
5627
|
+
caret.snap(from.x);
|
|
5628
|
+
caret.x = next.x;
|
|
5629
|
+
caret.target = next.target;
|
|
5285
5630
|
gliding = true;
|
|
5631
|
+
shownHead = caret.x;
|
|
5286
5632
|
loop.wake();
|
|
5287
5633
|
} else {
|
|
5288
5634
|
gliding = false;
|
|
5289
|
-
|
|
5635
|
+
caret.snap(next.x);
|
|
5636
|
+
shownHead = next.x;
|
|
5290
5637
|
}
|
|
5638
|
+
};
|
|
5639
|
+
const setTime = (t, source, shown = t, glide = false) => {
|
|
5640
|
+
const next = Math.min(duration, Math.max(0, t));
|
|
5641
|
+
headTo(Math.min(duration, Math.max(0, shown)), glide ? "jump" : "follow");
|
|
5291
5642
|
const moved = next !== time;
|
|
5292
5643
|
time = next;
|
|
5293
5644
|
renderHead();
|
|
@@ -5328,8 +5679,7 @@ function timeline(container, options) {
|
|
|
5328
5679
|
options.onCross?.(m);
|
|
5329
5680
|
}
|
|
5330
5681
|
time = step.t;
|
|
5331
|
-
|
|
5332
|
-
gliding = false;
|
|
5682
|
+
headTo(time, step.wrapped ? "jump" : "follow");
|
|
5333
5683
|
emitSeek(time, "play");
|
|
5334
5684
|
keepInView();
|
|
5335
5685
|
if (step.ended) {
|
|
@@ -5342,7 +5692,7 @@ function timeline(container, options) {
|
|
|
5342
5692
|
shownHead = caret.step(dt);
|
|
5343
5693
|
if (caret.resting) {
|
|
5344
5694
|
gliding = false;
|
|
5345
|
-
shownHead =
|
|
5695
|
+
shownHead = caret.target;
|
|
5346
5696
|
}
|
|
5347
5697
|
}
|
|
5348
5698
|
vStart.step(dt);
|
|
@@ -5355,9 +5705,9 @@ function timeline(container, options) {
|
|
|
5355
5705
|
return;
|
|
5356
5706
|
const r = loopRange();
|
|
5357
5707
|
if (r && (time < r.start || time >= r.end - 0.5 / fps))
|
|
5358
|
-
setTime(r.start, "code");
|
|
5708
|
+
setTime(r.start, "code", r.start, true);
|
|
5359
5709
|
else if (!r && time >= duration - 0.5 / fps)
|
|
5360
|
-
setTime(0, "code");
|
|
5710
|
+
setTime(0, "code", 0, true);
|
|
5361
5711
|
playing = true;
|
|
5362
5712
|
syncPlay();
|
|
5363
5713
|
emitPlay(true);
|
|
@@ -5486,24 +5836,35 @@ function timeline(container, options) {
|
|
|
5486
5836
|
};
|
|
5487
5837
|
const localX = (e) => e.clientX - track.getBoundingClientRect().left;
|
|
5488
5838
|
let drag = null;
|
|
5489
|
-
const scrubTo = (x) => {
|
|
5839
|
+
const scrubTo = (x, press = false) => {
|
|
5490
5840
|
const shownX = softClamp(x, xOf(0), xOf(duration), 10);
|
|
5491
5841
|
const s = scrubTime(tOf(shownX), {
|
|
5492
5842
|
duration,
|
|
5493
5843
|
fps,
|
|
5494
5844
|
pxPerSec: pxPerSec(),
|
|
5495
|
-
stops: snapMarkers ? markers.map((m) => m.time) : [],
|
|
5845
|
+
stops: snapMarkers && !lane ? markers.map((m) => m.time) : [],
|
|
5496
5846
|
frames: snapFrames
|
|
5497
5847
|
});
|
|
5498
|
-
setTime(s.t, "scrub", s.shown);
|
|
5848
|
+
setTime(s.t, "scrub", s.shown, press);
|
|
5499
5849
|
root.toggleAttribute("data-stuck", s.stop !== null);
|
|
5500
5850
|
};
|
|
5851
|
+
const showTag = (x) => {
|
|
5852
|
+
const at = Math.min(duration, Math.max(0, tOf(x)));
|
|
5853
|
+
const text = `${at.toFixed(2)} s`;
|
|
5854
|
+
if (tag.textContent !== text)
|
|
5855
|
+
tag.textContent = text;
|
|
5856
|
+
const cx = Math.min(width, Math.max(0, x));
|
|
5857
|
+
const flip = cx > width - tag.offsetWidth - 12;
|
|
5858
|
+
tag.style.transform = `translateX(${cx.toFixed(1)}px) translateX(${flip ? "calc(-100% - 4px)" : "4px"})`;
|
|
5859
|
+
tag.dataset.on = "";
|
|
5860
|
+
};
|
|
5861
|
+
const hideTag = () => delete tag.dataset.on;
|
|
5501
5862
|
track.addEventListener("pointerdown", (e) => {
|
|
5502
5863
|
if (e.button !== 0 || editing)
|
|
5503
5864
|
return;
|
|
5504
5865
|
root.focus({ preventScroll: true });
|
|
5505
5866
|
const t = e.target;
|
|
5506
|
-
const markerEl = t.closest(".knobs-tl-marker");
|
|
5867
|
+
const markerEl = lane ? null : t.closest(".knobs-tl-marker");
|
|
5507
5868
|
const edgeEl = t.closest(".knobs-tl-edge");
|
|
5508
5869
|
const rangeEl = t.closest(".knobs-tl-range");
|
|
5509
5870
|
track.setPointerCapture(e.pointerId);
|
|
@@ -5548,13 +5909,16 @@ function timeline(container, options) {
|
|
|
5548
5909
|
root.dataset.scrubbing = "";
|
|
5549
5910
|
for (const l of listeners)
|
|
5550
5911
|
l.onScrub?.(true);
|
|
5551
|
-
scrubTo(localX(e));
|
|
5912
|
+
scrubTo(localX(e), true);
|
|
5913
|
+
showTag(localX(e));
|
|
5552
5914
|
}
|
|
5553
5915
|
e.preventDefault();
|
|
5554
5916
|
});
|
|
5555
5917
|
track.addEventListener("pointermove", (e) => {
|
|
5556
5918
|
const x = localX(e);
|
|
5557
5919
|
hover.style.transform = `translateX(${x.toFixed(1)}px)`;
|
|
5920
|
+
if (!drag || drag.kind === "scrub")
|
|
5921
|
+
showTag(x);
|
|
5558
5922
|
if (!drag)
|
|
5559
5923
|
return;
|
|
5560
5924
|
if (drag.kind === "scrub")
|
|
@@ -5600,8 +5964,10 @@ function timeline(container, options) {
|
|
|
5600
5964
|
delete root.dataset.dragging;
|
|
5601
5965
|
root.removeAttribute("data-stuck");
|
|
5602
5966
|
if (d.kind === "scrub") {
|
|
5603
|
-
|
|
5967
|
+
headTo(time, "follow");
|
|
5604
5968
|
renderHead();
|
|
5969
|
+
if (!track.matches(":hover"))
|
|
5970
|
+
hideTag();
|
|
5605
5971
|
for (const l of listeners)
|
|
5606
5972
|
l.onScrub?.(false);
|
|
5607
5973
|
if (d.wasPlaying)
|
|
@@ -5614,7 +5980,7 @@ function timeline(container, options) {
|
|
|
5614
5980
|
} else {
|
|
5615
5981
|
const m = markers.find((x) => x.id === d.id);
|
|
5616
5982
|
if (m)
|
|
5617
|
-
setTime(m.time, "marker");
|
|
5983
|
+
setTime(m.time, "marker", m.time, true);
|
|
5618
5984
|
}
|
|
5619
5985
|
} else if (d.kind === "edge" || d.moved)
|
|
5620
5986
|
emitRanges();
|
|
@@ -5623,10 +5989,14 @@ function timeline(container, options) {
|
|
|
5623
5989
|
track.addEventListener("pointercancel", endDrag);
|
|
5624
5990
|
track.addEventListener("lostpointercapture", endDrag);
|
|
5625
5991
|
track.addEventListener("pointerleave", () => {
|
|
5626
|
-
if (
|
|
5627
|
-
|
|
5992
|
+
if (drag)
|
|
5993
|
+
return;
|
|
5994
|
+
hover.style.transform = "translateX(-100px)";
|
|
5995
|
+
hideTag();
|
|
5628
5996
|
});
|
|
5629
5997
|
track.addEventListener("dblclick", (e) => {
|
|
5998
|
+
if (lane)
|
|
5999
|
+
return;
|
|
5630
6000
|
const markerEl = e.target.closest(".knobs-tl-marker");
|
|
5631
6001
|
if (markerEl)
|
|
5632
6002
|
return rename(markerEl.dataset.id);
|
|
@@ -5637,6 +6007,8 @@ function timeline(container, options) {
|
|
|
5637
6007
|
rename(m.id);
|
|
5638
6008
|
});
|
|
5639
6009
|
track.addEventListener("wheel", (e) => {
|
|
6010
|
+
if (lane)
|
|
6011
|
+
return;
|
|
5640
6012
|
e.preventDefault();
|
|
5641
6013
|
const scale = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? height : 1;
|
|
5642
6014
|
if (e.ctrlKey || e.metaKey) {
|
|
@@ -5700,7 +6072,7 @@ function timeline(container, options) {
|
|
|
5700
6072
|
if (!m)
|
|
5701
6073
|
return;
|
|
5702
6074
|
select({ kind: "marker", id: m.id });
|
|
5703
|
-
return setTime(m.time, "marker");
|
|
6075
|
+
return setTime(m.time, "marker", m.time, true);
|
|
5704
6076
|
}
|
|
5705
6077
|
case "i":
|
|
5706
6078
|
case "I":
|
|
@@ -5712,11 +6084,13 @@ function timeline(container, options) {
|
|
|
5712
6084
|
return setEdge("end", time);
|
|
5713
6085
|
case "m":
|
|
5714
6086
|
case "M":
|
|
6087
|
+
if (lane)
|
|
6088
|
+
return;
|
|
5715
6089
|
claim();
|
|
5716
6090
|
addMarker(time);
|
|
5717
6091
|
return;
|
|
5718
6092
|
case "Enter":
|
|
5719
|
-
if (selected?.kind === "marker") {
|
|
6093
|
+
if (selected?.kind === "marker" && !lane) {
|
|
5720
6094
|
claim();
|
|
5721
6095
|
rename(selected.id);
|
|
5722
6096
|
}
|
|
@@ -5734,19 +6108,25 @@ function timeline(container, options) {
|
|
|
5734
6108
|
return;
|
|
5735
6109
|
case "=":
|
|
5736
6110
|
case "+":
|
|
6111
|
+
if (lane)
|
|
6112
|
+
return;
|
|
5737
6113
|
claim();
|
|
5738
6114
|
return zoom(1.5);
|
|
5739
6115
|
case "-":
|
|
6116
|
+
if (lane)
|
|
6117
|
+
return;
|
|
5740
6118
|
claim();
|
|
5741
6119
|
return zoom(1 / 1.5);
|
|
5742
6120
|
case "0":
|
|
6121
|
+
if (lane)
|
|
6122
|
+
return;
|
|
5743
6123
|
claim();
|
|
5744
6124
|
return fit();
|
|
5745
6125
|
}
|
|
5746
6126
|
});
|
|
5747
6127
|
const unregisterHints = options.hints === false ? undefined : hints.register(root, () => [
|
|
5748
6128
|
{ el: playBtn, label: playing ? "Pause" : "Play", activate: toggle },
|
|
5749
|
-
...markers.flatMap((m) => {
|
|
6129
|
+
...(lane ? [] : markers).flatMap((m) => {
|
|
5750
6130
|
const n = markerEls.get(m.id);
|
|
5751
6131
|
if (!n || n.dataset.leaving !== undefined)
|
|
5752
6132
|
return [];
|
|
@@ -5758,7 +6138,7 @@ function timeline(container, options) {
|
|
|
5758
6138
|
...label && !label.hidden ? { labelEl: label } : {},
|
|
5759
6139
|
activate: () => {
|
|
5760
6140
|
select({ kind: "marker", id: m.id });
|
|
5761
|
-
setTime(m.time, "marker");
|
|
6141
|
+
setTime(m.time, "marker", m.time, true);
|
|
5762
6142
|
root.focus({ preventScroll: true });
|
|
5763
6143
|
}
|
|
5764
6144
|
}
|
|
@@ -5770,7 +6150,7 @@ function timeline(container, options) {
|
|
|
5770
6150
|
activate: () => root.focus({ preventScroll: true })
|
|
5771
6151
|
}
|
|
5772
6152
|
], { listen: false, rank: 1 });
|
|
5773
|
-
const menu = contextMenu(track, (e) => {
|
|
6153
|
+
const menu = lane ? null : contextMenu(track, (e) => {
|
|
5774
6154
|
const t = e?.target ?? null;
|
|
5775
6155
|
const markerEl = t?.closest(".knobs-tl-marker");
|
|
5776
6156
|
const rangeEl = t?.closest(".knobs-tl-range");
|
|
@@ -5785,7 +6165,7 @@ function timeline(container, options) {
|
|
|
5785
6165
|
run: () => {
|
|
5786
6166
|
const m = markers.find((x) => x.id === mid);
|
|
5787
6167
|
if (m)
|
|
5788
|
-
setTime(m.time, "marker");
|
|
6168
|
+
setTime(m.time, "marker", m.time, true);
|
|
5789
6169
|
}
|
|
5790
6170
|
},
|
|
5791
6171
|
{ label: "Rename…", shortcut: "Enter", run: () => rename(mid) },
|
|
@@ -5949,7 +6329,7 @@ function timeline(container, options) {
|
|
|
5949
6329
|
listeners.clear();
|
|
5950
6330
|
loop.stop();
|
|
5951
6331
|
ro.disconnect();
|
|
5952
|
-
menu
|
|
6332
|
+
menu?.dispose();
|
|
5953
6333
|
root.remove();
|
|
5954
6334
|
}
|
|
5955
6335
|
};
|
|
@@ -5966,10 +6346,21 @@ var init_timeline = __esm(() => {
|
|
|
5966
6346
|
init_timeline_model();
|
|
5967
6347
|
INSTANT = springMotion(SPRINGS.instant);
|
|
5968
6348
|
CALM = springMotion(SPRINGS.calm);
|
|
5969
|
-
GLIDE = springMotion(
|
|
6349
|
+
GLIDE = springMotion(CATCH_UP);
|
|
5970
6350
|
});
|
|
5971
6351
|
|
|
5972
6352
|
// src/ui/compare-model.ts
|
|
6353
|
+
function compareModes(modes, mode) {
|
|
6354
|
+
const list = [...new Set((modes ?? ["side", "wipe"]).filter((m) => ALL_MODES.includes(m)))];
|
|
6355
|
+
const offered = list.length ? list : ["side", "wipe"];
|
|
6356
|
+
return { modes: offered, mode: mode && offered.includes(mode) ? mode : offered[0] };
|
|
6357
|
+
}
|
|
6358
|
+
function modeForKey(key, modes) {
|
|
6359
|
+
const k = key.toLowerCase();
|
|
6360
|
+
if (/^[1-9]$/.test(k))
|
|
6361
|
+
return modes[Number(k) - 1] ?? null;
|
|
6362
|
+
return modes.find((m) => MODE_KEYS[m] === k) ?? null;
|
|
6363
|
+
}
|
|
5973
6364
|
function mediaKind(src, kind) {
|
|
5974
6365
|
if (kind)
|
|
5975
6366
|
return kind;
|
|
@@ -6048,6 +6439,18 @@ function wipeGrab(x, rect, wipe, slop = 12) {
|
|
|
6048
6439
|
const onLine = Math.abs(x - line) <= slop;
|
|
6049
6440
|
return { onLine, offset: onLine ? x - line : 0 };
|
|
6050
6441
|
}
|
|
6442
|
+
function seamLabels(x, left, right, wb, wa, gap = 8) {
|
|
6443
|
+
const room = (space, w) => Math.min(1, Math.max(0, (space - w - 2 * gap) / gap));
|
|
6444
|
+
return {
|
|
6445
|
+
before: { x: x - gap - wb, opacity: room(x - left, wb) },
|
|
6446
|
+
after: { x: x + gap, opacity: room(right - x, wa) }
|
|
6447
|
+
};
|
|
6448
|
+
}
|
|
6449
|
+
function gripTop(y, rect, grip, margin = 8) {
|
|
6450
|
+
const lo = rect.y + margin;
|
|
6451
|
+
const hi = rect.y + rect.h - margin - grip;
|
|
6452
|
+
return hi < lo ? rect.y + (rect.h - grip) / 2 : Math.min(hi, Math.max(lo, y - grip / 2));
|
|
6453
|
+
}
|
|
6051
6454
|
function stripColumns(view, width, o) {
|
|
6052
6455
|
const { fps } = o;
|
|
6053
6456
|
const total = Math.max(1, Math.round(o.duration * fps));
|
|
@@ -6098,7 +6501,7 @@ function stripPeaks(diffs, o = {}) {
|
|
|
6098
6501
|
});
|
|
6099
6502
|
return out;
|
|
6100
6503
|
}
|
|
6101
|
-
var VIDEO, frameOf = (t, fps) => Math.max(0, Math.floor(t * fps + 0.0001)), lastFrame = (duration, fps) => Math.max(0, Math.ceil(duration * fps - 0.0001) - 1), seekTime = (t, fps, duration) => frameMidpoint(frameOf(t, fps), fps, duration), sameFrame = (a, b, fps) => frameOf(a, fps) === frameOf(b, fps), SEEK_IDLE, splitHeight = (w, media) => media.w > 0 && media.h > 0 ? w / 2 * (media.h / media.w) : w / 2 * (9 / 16), ZOOM_FIT, ZOOM_MAX = 16, panBy = (r, dx, dy, base) => clampZoom({
|
|
6504
|
+
var ALL_MODES, MODE_KEYS, modeKey = (mode) => MODE_KEYS[mode].toUpperCase(), VIDEO, frameOf = (t, fps) => Math.max(0, Math.floor(t * fps + 0.0001)), lastFrame = (duration, fps) => Math.max(0, Math.ceil(duration * fps - 0.0001) - 1), seekTime = (t, fps, duration) => frameMidpoint(frameOf(t, fps), fps, duration), sameFrame = (a, b, fps) => frameOf(a, fps) === frameOf(b, fps), SEEK_IDLE, splitHeight = (w, media) => media.w > 0 && media.h > 0 ? w / 2 * (media.h / media.w) : w / 2 * (9 / 16), ZOOM_FIT, ZOOM_MAX = 16, panBy = (r, dx, dy, base) => clampZoom({
|
|
6102
6505
|
u: r.u - dx / Math.max(1, base.w) * r.w,
|
|
6103
6506
|
v: r.v - dy / Math.max(1, base.h) * r.h,
|
|
6104
6507
|
w: r.w
|
|
@@ -6117,6 +6520,8 @@ var VIDEO, frameOf = (t, fps) => Math.max(0, Math.floor(t * fps + 0.0001)), last
|
|
|
6117
6520
|
h: a.h + (b.h - a.h) * k
|
|
6118
6521
|
}), wipeX = (rect, wipe) => rect.x + Math.min(1, Math.max(0, wipe)) * rect.w, wipeAt = (x, rect, offset = 0) => rect.w <= 0 ? 0.5 : Math.min(1, Math.max(0, (x - offset - rect.x) / rect.w)), wipeKey = (wipe, dir, big = false) => Math.min(1, Math.max(0, Math.round((wipe + dir * (big ? 0.1 : 0.01)) * 1000) / 1000));
|
|
6119
6522
|
var init_compare_model = __esm(() => {
|
|
6523
|
+
ALL_MODES = ["side", "wipe", "difference"];
|
|
6524
|
+
MODE_KEYS = { side: "s", wipe: "w", difference: "d" };
|
|
6120
6525
|
VIDEO = /\.(mp4|m4v|mov|webm|ogv|mkv)$/i;
|
|
6121
6526
|
SEEK_IDLE = { seeking: false, pending: null };
|
|
6122
6527
|
ZOOM_FIT = { u: 0, v: 0, w: 1, h: 1 };
|
|
@@ -6916,8 +7321,8 @@ async function createCompareGpu(canvas, onLost) {
|
|
|
6916
7321
|
r(0, f.before);
|
|
6917
7322
|
r(4, f.after);
|
|
6918
7323
|
data.set([f.width, f.height, f.dpr, 0], 8);
|
|
6919
|
-
data.set([f.wipeX, f.soft, 0,
|
|
6920
|
-
data.set([f.from, f.to, f.mix,
|
|
7324
|
+
data.set([f.wipeX, f.soft, 0, 0], 12);
|
|
7325
|
+
data.set([f.from, f.to, f.mix, 0], 16);
|
|
6921
7326
|
data.set([f.threshold, f.gain, ready[0], ready[1]], 20);
|
|
6922
7327
|
data.set([f.accent[0], f.accent[1], f.accent[2], 1], 24);
|
|
6923
7328
|
const l = f.loupe;
|
|
@@ -6959,8 +7364,8 @@ var MODE_INDEX, WGSL = `
|
|
|
6959
7364
|
struct U {
|
|
6960
7365
|
ra: vec4f, rb: vec4f,
|
|
6961
7366
|
view: vec4f, // w, h, dpr, _
|
|
6962
|
-
wipe: vec4f, // x, soft, _,
|
|
6963
|
-
modes: vec4f, // from, to, mix,
|
|
7367
|
+
wipe: vec4f, // x, soft, _, _
|
|
7368
|
+
modes: vec4f, // from, to, mix, _
|
|
6964
7369
|
diff: vec4f, // threshold, gain, readyA, readyB
|
|
6965
7370
|
accent: vec4f,
|
|
6966
7371
|
loupe: vec4f, // x, y, r, zoom (0: none)
|
|
@@ -7011,21 +7416,14 @@ fn takeB(p: vec2f, r: vec4f, c0: vec4f, near: bool) -> vec4f {
|
|
|
7011
7416
|
|
|
7012
7417
|
fn pm(c: vec4f) -> vec4f { return vec4f(c.rgb * c.a, c.a); }
|
|
7013
7418
|
|
|
7014
|
-
fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f
|
|
7015
|
-
let flick = u.modes.w > 0.5;
|
|
7419
|
+
fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f) -> vec4f {
|
|
7016
7420
|
switch mode {
|
|
7017
|
-
case 0u: {
|
|
7018
|
-
let bb = select(b, vec4f(a2.rgb, b.a), flick);
|
|
7019
|
-
return vec4f(a.rgb * a.a + bb.rgb * bb.a * (1.0 - a.a), a.a + bb.a * (1.0 - a.a));
|
|
7020
|
-
}
|
|
7021
7421
|
case 1u: {
|
|
7022
|
-
if (flick) { return pm(a); }
|
|
7023
7422
|
let s = max(u.wipe.y, 0.5);
|
|
7024
7423
|
let m = smoothstep(u.wipe.x - s, u.wipe.x + s, p.x);
|
|
7025
7424
|
return pm(vec4f(mix(a.rgb, b.rgb, m), mix(a.a, b.a, m)));
|
|
7026
7425
|
}
|
|
7027
7426
|
case 2u: {
|
|
7028
|
-
if (flick) { return pm(a); }
|
|
7029
7427
|
let d = abs(a.rgb - b.rgb);
|
|
7030
7428
|
let h = smoothstep(u.diff.x, u.diff.x + 0.06, max(d.r, max(d.g, d.b)) * u.diff.y) * min(a.a, b.a);
|
|
7031
7429
|
let c = mix(vec3f(luma(b.rgb) * 0.35), u.accent.rgb, h);
|
|
@@ -7033,8 +7431,8 @@ fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f, a2: vec4f) -> vec4f {
|
|
|
7033
7431
|
}
|
|
7034
7432
|
default: {}
|
|
7035
7433
|
}
|
|
7036
|
-
//
|
|
7037
|
-
return
|
|
7434
|
+
// Side by side: each side in its own place, Before over After where they meet.
|
|
7435
|
+
return vec4f(a.rgb * a.a + b.rgb * b.a * (1.0 - a.a), a.a + b.a * (1.0 - a.a));
|
|
7038
7436
|
}
|
|
7039
7437
|
|
|
7040
7438
|
@fragment fn fs(@builtin(position) q: vec4f) -> @location(0) vec4f {
|
|
@@ -7052,16 +7450,14 @@ fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f, a2: vec4f) -> vec4f {
|
|
|
7052
7450
|
}
|
|
7053
7451
|
let a = takeA(p, u.ra, u.ca, near);
|
|
7054
7452
|
let b = takeB(p, u.rb, u.cb, near);
|
|
7055
|
-
|
|
7056
|
-
let a2 = takeA(p, u.rb, u.cb, near);
|
|
7057
|
-
var o = mix(composite(u32(u.modes.x), p, a, b, a2), composite(u32(u.modes.y), p, a, b, a2), u.modes.z);
|
|
7453
|
+
var o = mix(composite(u32(u.modes.x), p, a, b), composite(u32(u.modes.y), p, a, b), u.modes.z);
|
|
7058
7454
|
if (near) { o = o + vec4f(0.05, 0.05, 0.06, 1.0) * (1.0 - o.a); }
|
|
7059
7455
|
o = mix(o, vec4f(0.92), ring * 0.9);
|
|
7060
7456
|
return o;
|
|
7061
7457
|
}
|
|
7062
7458
|
`, TEXTURE_BINDING = 4, COPY_DST = 2, RENDER_ATTACHMENT = 16, shared = null, lostListeners;
|
|
7063
7459
|
var init_compare_gpu = __esm(() => {
|
|
7064
|
-
MODE_INDEX = { side: 0, wipe: 1, difference: 2
|
|
7460
|
+
MODE_INDEX = { side: 0, wipe: 1, difference: 2 };
|
|
7065
7461
|
lostListeners = new Set;
|
|
7066
7462
|
});
|
|
7067
7463
|
|
|
@@ -7447,7 +7843,9 @@ function compare(container, options) {
|
|
|
7447
7843
|
...options.theme ? { theme: options.theme } : {},
|
|
7448
7844
|
...options.appearance ? { appearance: options.appearance } : {}
|
|
7449
7845
|
};
|
|
7450
|
-
|
|
7846
|
+
const offered = compareModes(options.modes, options.mode);
|
|
7847
|
+
const modeList = offered.modes;
|
|
7848
|
+
let mode = offered.mode;
|
|
7451
7849
|
let wipe = Math.min(1, Math.max(0, options.wipe ?? 0.5));
|
|
7452
7850
|
let loupeOn = options.loupe ?? false;
|
|
7453
7851
|
let threshold = options.threshold ?? 4 / 255;
|
|
@@ -7465,7 +7863,7 @@ function compare(container, options) {
|
|
|
7465
7863
|
root.setAttribute("role", "group");
|
|
7466
7864
|
root.setAttribute("aria-roledescription", "compare");
|
|
7467
7865
|
root.setAttribute("aria-label", options.label ?? "Compare");
|
|
7468
|
-
root.setAttribute("aria-keyshortcuts",
|
|
7866
|
+
root.setAttribute("aria-keyshortcuts", `Space ArrowLeft ArrowRight Shift+ArrowLeft Shift+ArrowRight , . ${modeList.map((m, i) => `${i + 1} ${modeKey(m)}`).join(" ")} Z L 0 = -`);
|
|
7469
7867
|
applyLook2(root, options);
|
|
7470
7868
|
root.dataset.mode = mode;
|
|
7471
7869
|
if (auto)
|
|
@@ -7474,35 +7872,37 @@ function compare(container, options) {
|
|
|
7474
7872
|
const modes = el("div", "knobs-cmp-modes");
|
|
7475
7873
|
modes.setAttribute("role", "radiogroup");
|
|
7476
7874
|
modes.setAttribute("aria-label", "Mode");
|
|
7477
|
-
|
|
7875
|
+
modes.hidden = modeList.length < 2;
|
|
7876
|
+
const modeBtns = modeList.map((m, i) => {
|
|
7478
7877
|
const b = el("button", "knobs-cmp-mode");
|
|
7479
7878
|
b.type = "button";
|
|
7480
7879
|
b.tabIndex = -1;
|
|
7481
|
-
b.textContent = m.
|
|
7482
|
-
b.title = `${m.
|
|
7880
|
+
b.textContent = MODE_LABEL[m].short;
|
|
7881
|
+
b.title = `${MODE_LABEL[m].long} · ${i + 1} or ${modeKey(m)}`;
|
|
7483
7882
|
b.setAttribute("role", "radio");
|
|
7484
|
-
b.setAttribute("aria-keyshortcuts",
|
|
7883
|
+
b.setAttribute("aria-keyshortcuts", `${i + 1} ${modeKey(m)}`);
|
|
7485
7884
|
b.addEventListener("click", () => {
|
|
7486
|
-
setMode(m
|
|
7885
|
+
setMode(m);
|
|
7487
7886
|
root.focus({ preventScroll: true });
|
|
7488
7887
|
});
|
|
7489
7888
|
modes.append(b);
|
|
7490
7889
|
return b;
|
|
7491
7890
|
});
|
|
7492
|
-
const
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7891
|
+
const toggleBtn = (name, label, title) => {
|
|
7892
|
+
const b = el("button", "knobs-cmp-toggle");
|
|
7893
|
+
b.type = "button";
|
|
7894
|
+
b.tabIndex = -1;
|
|
7895
|
+
b.innerHTML = icon(name);
|
|
7896
|
+
b.setAttribute("aria-label", label);
|
|
7897
|
+
b.title = title;
|
|
7898
|
+
return b;
|
|
7899
|
+
};
|
|
7900
|
+
const loupeBtn = toggleBtn("magnifyingGlass", "Loupe", "Loupe · hold Z");
|
|
7497
7901
|
loupeBtn.addEventListener("click", () => {
|
|
7498
7902
|
setLoupe(!loupeOn);
|
|
7499
7903
|
root.focus({ preventScroll: true });
|
|
7500
7904
|
});
|
|
7501
|
-
const loopBtn =
|
|
7502
|
-
loopBtn.type = "button";
|
|
7503
|
-
loopBtn.tabIndex = -1;
|
|
7504
|
-
loopBtn.textContent = "Loop";
|
|
7505
|
-
loopBtn.title = "Loop · L (drag its ends on the timeline)";
|
|
7905
|
+
const loopBtn = toggleBtn("repeat", "Loop", "Loop · L (drag its ends on the timeline)");
|
|
7506
7906
|
loopBtn.setAttribute("aria-keyshortcuts", "L");
|
|
7507
7907
|
loopBtn.addEventListener("click", () => {
|
|
7508
7908
|
api.setLoop(!loopNow());
|
|
@@ -7519,7 +7919,8 @@ function compare(container, options) {
|
|
|
7519
7919
|
const zoomReset = el("button", "knobs-cmp-zoom-reset");
|
|
7520
7920
|
zoomReset.type = "button";
|
|
7521
7921
|
zoomReset.tabIndex = -1;
|
|
7522
|
-
zoomReset.
|
|
7922
|
+
zoomReset.innerHTML = icon("arrowCounterClockwise", { small: true });
|
|
7923
|
+
zoomReset.setAttribute("aria-label", "Reset zoom");
|
|
7523
7924
|
zoomReset.title = "Fit · 0 or double-click";
|
|
7524
7925
|
zoomReset.addEventListener("click", () => {
|
|
7525
7926
|
api.setZoom(null);
|
|
@@ -7534,6 +7935,9 @@ function compare(container, options) {
|
|
|
7534
7935
|
handle.setAttribute("aria-label", "Wipe");
|
|
7535
7936
|
handle.setAttribute("aria-valuemin", "0");
|
|
7536
7937
|
handle.setAttribute("aria-valuemax", "100");
|
|
7938
|
+
const grip = el("span", "knobs-cmp-grip");
|
|
7939
|
+
grip.setAttribute("aria-hidden", "true");
|
|
7940
|
+
handle.append(grip);
|
|
7537
7941
|
const note = el("div", "knobs-cmp-note");
|
|
7538
7942
|
stage.append(canvas, split, labels[0], labels[1], handle, zoomPill, note);
|
|
7539
7943
|
root.append(bar, stage);
|
|
@@ -7550,6 +7954,7 @@ function compare(container, options) {
|
|
|
7550
7954
|
root.append(host);
|
|
7551
7955
|
tl = timeline(host, {
|
|
7552
7956
|
...look,
|
|
7957
|
+
variant: "lane",
|
|
7553
7958
|
duration: 1,
|
|
7554
7959
|
fps: fps(),
|
|
7555
7960
|
arrows: { step: 0.5, shift: 2 },
|
|
@@ -7568,9 +7973,9 @@ function compare(container, options) {
|
|
|
7568
7973
|
const fade = new Spring(1, FADE2.omega, FADE2.zeta, 0.001);
|
|
7569
7974
|
let fromMode = mode;
|
|
7570
7975
|
let toMode = mode;
|
|
7571
|
-
|
|
7572
|
-
let
|
|
7573
|
-
let
|
|
7976
|
+
const wipeLine = new Spring(wipe, WIPE_CATCH.omega, WIPE_CATCH.zeta, 0.0001);
|
|
7977
|
+
let wipeCatching = false;
|
|
7978
|
+
let gripY = null;
|
|
7574
7979
|
let loupeHeld = false;
|
|
7575
7980
|
let pointer = null;
|
|
7576
7981
|
let drag = null;
|
|
@@ -7888,25 +8293,16 @@ function compare(container, options) {
|
|
|
7888
8293
|
gpu = g;
|
|
7889
8294
|
setRenderer("gpu");
|
|
7890
8295
|
};
|
|
7891
|
-
const syncFlicker = () => {
|
|
7892
|
-
win.clearInterval(flickerTimer);
|
|
7893
|
-
flickerTimer = 0;
|
|
7894
|
-
phase = false;
|
|
7895
|
-
if (mode === "flicker")
|
|
7896
|
-
flickerTimer = win.setInterval(() => {
|
|
7897
|
-
phase = !phase;
|
|
7898
|
-
requestDraw();
|
|
7899
|
-
}, FLICKER_MS);
|
|
7900
|
-
};
|
|
7901
8296
|
const syncModeUi = () => {
|
|
7902
8297
|
root.dataset.mode = mode;
|
|
7903
|
-
|
|
7904
|
-
modeBtns[i].setAttribute("aria-checked", String(m
|
|
8298
|
+
modeList.forEach((m, i) => {
|
|
8299
|
+
modeBtns[i].setAttribute("aria-checked", String(m === mode));
|
|
7905
8300
|
});
|
|
7906
8301
|
handle.tabIndex = mode === "wipe" ? 0 : -1;
|
|
8302
|
+
modePill?.update();
|
|
7907
8303
|
};
|
|
7908
8304
|
const setMode = (next, emit = true) => {
|
|
7909
|
-
if (next === mode)
|
|
8305
|
+
if (next === mode || !modeList.includes(next))
|
|
7910
8306
|
return;
|
|
7911
8307
|
mode = next;
|
|
7912
8308
|
const reduced = reducedMotion(win);
|
|
@@ -7922,19 +8318,29 @@ function compare(container, options) {
|
|
|
7922
8318
|
else
|
|
7923
8319
|
lay.set(next === "side" ? 0 : 1);
|
|
7924
8320
|
syncModeUi();
|
|
7925
|
-
syncFlicker();
|
|
7926
8321
|
if (emit)
|
|
7927
8322
|
options.onMode?.(next);
|
|
7928
8323
|
requestDraw();
|
|
7929
8324
|
};
|
|
7930
|
-
const setWipe = (next, emit = true) => {
|
|
8325
|
+
const setWipe = (next, emit = true, jump = false) => {
|
|
7931
8326
|
const w = Math.min(1, Math.max(0, next));
|
|
7932
|
-
if (w === wipe)
|
|
8327
|
+
if (w === wipe && !jump)
|
|
7933
8328
|
return;
|
|
7934
8329
|
wipe = w;
|
|
8330
|
+
const minGap = 2 / Math.max(1, wipeRect(layoutNow().L.overlay).w);
|
|
8331
|
+
const head = moveHead({ x: wipeLine.x, target: wipeLine.target }, w, jump && !reducedMotion(win) ? "jump" : "follow", wipeCatching, minGap);
|
|
8332
|
+
wipeCatching = head.catching;
|
|
8333
|
+
if (head.catching) {
|
|
8334
|
+
wipeLine.x = head.x;
|
|
8335
|
+
wipeLine.target = head.target;
|
|
8336
|
+
} else
|
|
8337
|
+
wipeLine.snap(head.x);
|
|
7935
8338
|
if (emit)
|
|
7936
8339
|
options.onWipe?.(w);
|
|
7937
|
-
|
|
8340
|
+
if (head.catching)
|
|
8341
|
+
requestDraw();
|
|
8342
|
+
else
|
|
8343
|
+
drawNow();
|
|
7938
8344
|
};
|
|
7939
8345
|
const setLoupe = (on) => {
|
|
7940
8346
|
loupeOn = on;
|
|
@@ -8087,28 +8493,44 @@ function compare(container, options) {
|
|
|
8087
8493
|
viewSent = key;
|
|
8088
8494
|
options.onView({ zoom: clampZoom(z), loop: l });
|
|
8089
8495
|
};
|
|
8496
|
+
const labelsIn = (m, r, wr, wx) => {
|
|
8497
|
+
if (m === "wipe") {
|
|
8498
|
+
const at = seamLabels(wx, wr.x, wr.x + wr.w, labelW[0], labelW[1], INSET);
|
|
8499
|
+
return [at.before, at.after];
|
|
8500
|
+
}
|
|
8501
|
+
const ax = r.after.x + INSET + (r.after.w - labelW[1] - INSET * 2) * lay.x;
|
|
8502
|
+
return [
|
|
8503
|
+
{ x: r.before.x + INSET, opacity: 1 },
|
|
8504
|
+
{ x: ax, opacity: 1 }
|
|
8505
|
+
];
|
|
8506
|
+
};
|
|
8507
|
+
const placeGrip = (wr) => {
|
|
8508
|
+
const gy = gripTop(gripY ?? wr.y + wr.h / 2, wr, GRIP.h) - wr.y;
|
|
8509
|
+
grip.style.transform = `translate3d(0,${gy.toFixed(2)}px,0)`;
|
|
8510
|
+
};
|
|
8090
8511
|
const draw = () => {
|
|
8091
8512
|
dirty = false;
|
|
8092
8513
|
const r = layoutNow();
|
|
8093
8514
|
const wr = wipeRect(r.L.overlay);
|
|
8094
|
-
const wx = wipeX(wr,
|
|
8515
|
+
const wx = wipeX(wr, wipeLine.x);
|
|
8095
8516
|
const k = lay.x;
|
|
8096
|
-
const
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
const
|
|
8100
|
-
const
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8517
|
+
const a = labelsIn(fromMode, r, wr, wx);
|
|
8518
|
+
const b = labelsIn(toMode, r, wr, wx);
|
|
8519
|
+
const f = Math.min(1, Math.max(0, fade.x));
|
|
8520
|
+
const ys = [r.before.y + INSET, r.after.y + INSET];
|
|
8521
|
+
for (const i of [0, 1]) {
|
|
8522
|
+
const x = a[i].x + (b[i].x - a[i].x) * f;
|
|
8523
|
+
labels[i].style.transform = `translate(${x.toFixed(2)}px,${ys[i].toFixed(2)}px)`;
|
|
8524
|
+
labels[i].style.opacity = (a[i].opacity + (b[i].opacity - a[i].opacity) * f).toFixed(3);
|
|
8525
|
+
}
|
|
8105
8526
|
const top = Math.min(r.L.side.before.y, r.L.side.after.y);
|
|
8106
8527
|
split.style.transform = `translate(${r.L.side.split.toFixed(2)}px,${top.toFixed(2)}px)`;
|
|
8107
8528
|
split.style.height = `${Math.max(0, r.L.side.after.h).toFixed(2)}px`;
|
|
8108
8529
|
split.style.opacity = String(Math.max(0, 1 - k * 2));
|
|
8109
|
-
handle.toggleAttribute("data-on", mode === "wipe"
|
|
8530
|
+
handle.toggleAttribute("data-on", mode === "wipe");
|
|
8110
8531
|
handle.style.transform = `translate(${wx.toFixed(2)}px,${wr.y.toFixed(2)}px)`;
|
|
8111
8532
|
handle.style.height = `${Math.max(0, wr.h).toFixed(2)}px`;
|
|
8533
|
+
placeGrip(wr);
|
|
8112
8534
|
handle.setAttribute("aria-valuenow", String(Math.round(wipe * 100)));
|
|
8113
8535
|
handle.setAttribute("aria-valuetext", `${Math.round(wipe * 100)}% Before`);
|
|
8114
8536
|
const pct = `${Math.round(100 / zw.x)}%`;
|
|
@@ -8131,7 +8553,7 @@ function compare(container, options) {
|
|
|
8131
8553
|
const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w, cutAt));
|
|
8132
8554
|
st.clipPath = t + l + b + right > 0.01 ? `inset(${t.toFixed(2)}px ${right.toFixed(2)}px ${b.toFixed(2)}px ${l.toFixed(2)}px)` : "";
|
|
8133
8555
|
};
|
|
8134
|
-
const cut = mode === "wipe"
|
|
8556
|
+
const cut = mode === "wipe" ? wx : Number.POSITIVE_INFINITY;
|
|
8135
8557
|
for (const s of sides) {
|
|
8136
8558
|
const rect = s.index === 0 ? r.zBefore : r.zAfter;
|
|
8137
8559
|
const clip = s.index === 0 ? r.before : r.after;
|
|
@@ -8140,10 +8562,8 @@ function compare(container, options) {
|
|
|
8140
8562
|
place(s.preview, rect, clip, at);
|
|
8141
8563
|
if (s.index !== 0)
|
|
8142
8564
|
continue;
|
|
8143
|
-
for (const n of [s.el, s.preview])
|
|
8144
|
-
n.style.mixBlendMode = mode === "difference"
|
|
8145
|
-
n.style.opacity = mode === "flicker" && !showsBefore ? "0" : "";
|
|
8146
|
-
}
|
|
8565
|
+
for (const n of [s.el, s.preview])
|
|
8566
|
+
n.style.mixBlendMode = mode === "difference" ? "difference" : "";
|
|
8147
8567
|
}
|
|
8148
8568
|
return;
|
|
8149
8569
|
}
|
|
@@ -8164,8 +8584,6 @@ function compare(container, options) {
|
|
|
8164
8584
|
from: MODE_INDEX[fromMode],
|
|
8165
8585
|
to: MODE_INDEX[toMode],
|
|
8166
8586
|
mix: Math.min(1, Math.max(0, fade.x)),
|
|
8167
|
-
flick: flickHeld,
|
|
8168
|
-
phase,
|
|
8169
8587
|
threshold,
|
|
8170
8588
|
gain: 1,
|
|
8171
8589
|
accent,
|
|
@@ -8181,6 +8599,11 @@ function compare(container, options) {
|
|
|
8181
8599
|
const loop = frameLoop(win, (dt) => {
|
|
8182
8600
|
lay.step(dt);
|
|
8183
8601
|
fade.step(dt);
|
|
8602
|
+
if (wipeCatching) {
|
|
8603
|
+
wipeLine.step(dt);
|
|
8604
|
+
if (wipeLine.resting)
|
|
8605
|
+
wipeCatching = false;
|
|
8606
|
+
}
|
|
8184
8607
|
zu.step(dt);
|
|
8185
8608
|
zv.step(dt);
|
|
8186
8609
|
zw.step(dt);
|
|
@@ -8214,7 +8637,7 @@ function compare(container, options) {
|
|
|
8214
8637
|
pushFrame(s);
|
|
8215
8638
|
}
|
|
8216
8639
|
}
|
|
8217
|
-
const moving = !lay.resting || !fade.resting || !zoomResting();
|
|
8640
|
+
const moving = !lay.resting || !fade.resting || !zoomResting() || wipeCatching;
|
|
8218
8641
|
if (moving)
|
|
8219
8642
|
dirty = true;
|
|
8220
8643
|
if (auto && !lay.resting)
|
|
@@ -8357,16 +8780,19 @@ function compare(container, options) {
|
|
|
8357
8780
|
e.preventDefault();
|
|
8358
8781
|
return startPinch();
|
|
8359
8782
|
}
|
|
8360
|
-
if (mode === "wipe"
|
|
8783
|
+
if (mode === "wipe") {
|
|
8361
8784
|
const wr = wipeRect(layoutNow().L.overlay);
|
|
8362
8785
|
const inside = p.x >= wr.x - SLOP && p.x <= wr.x + wr.w + SLOP && p.y >= wr.y && p.y <= wr.y + wr.h;
|
|
8363
|
-
const grab = wipeGrab(p.x, wr,
|
|
8786
|
+
const grab = wipeGrab(p.x, wr, wipeLine.x, SLOP);
|
|
8364
8787
|
if (inside && (grab.onLine || !zoomed())) {
|
|
8365
8788
|
drag = { kind: "wipe", offset: grab.offset };
|
|
8366
8789
|
root.dataset.dragging = "";
|
|
8790
|
+
gripY = p.y;
|
|
8367
8791
|
e.preventDefault();
|
|
8368
8792
|
if (!grab.onLine)
|
|
8369
|
-
setWipe(wipeAt(p.x, wr));
|
|
8793
|
+
setWipe(wipeAt(p.x, wr), true, true);
|
|
8794
|
+
else
|
|
8795
|
+
drawNow();
|
|
8370
8796
|
return;
|
|
8371
8797
|
}
|
|
8372
8798
|
}
|
|
@@ -8392,6 +8818,11 @@ function compare(container, options) {
|
|
|
8392
8818
|
pinch = { d, x: mid.x, y: mid.y };
|
|
8393
8819
|
return handZoom(zoomAbout(panned, k, q.pu, q.pv));
|
|
8394
8820
|
}
|
|
8821
|
+
if (mode === "wipe" && drag?.kind !== "pan") {
|
|
8822
|
+
gripY = p.y;
|
|
8823
|
+
if (!drag)
|
|
8824
|
+
placeGrip(wipeRect(layoutNow().L.overlay));
|
|
8825
|
+
}
|
|
8395
8826
|
if (drag?.kind === "wipe")
|
|
8396
8827
|
setWipe(wipeAt(p.x, wipeRect(layoutNow().L.overlay), drag.offset));
|
|
8397
8828
|
else if (drag?.kind === "pan") {
|
|
@@ -8464,12 +8895,6 @@ function compare(container, options) {
|
|
|
8464
8895
|
const z = { u: zu.target, v: zv.target, w: zw.target, h: zw.target };
|
|
8465
8896
|
glideZoom(zoomAbout(z, factor, 0.5, 0.5));
|
|
8466
8897
|
};
|
|
8467
|
-
const holdFlick = (on) => {
|
|
8468
|
-
if (flickHeld === on)
|
|
8469
|
-
return;
|
|
8470
|
-
flickHeld = on;
|
|
8471
|
-
drawNow();
|
|
8472
|
-
};
|
|
8473
8898
|
const holdLoupe = (on) => {
|
|
8474
8899
|
if (loupeHeld === on)
|
|
8475
8900
|
return;
|
|
@@ -8515,25 +8940,17 @@ function compare(container, options) {
|
|
|
8515
8940
|
case "s":
|
|
8516
8941
|
case "w":
|
|
8517
8942
|
case "d":
|
|
8518
|
-
if (e.altKey || e.shiftKey || key === "s" && claimsHintKey(doc, key))
|
|
8519
|
-
return;
|
|
8520
|
-
claim();
|
|
8521
|
-
return setMode(KEY_MODE[key]);
|
|
8522
8943
|
case "1":
|
|
8523
8944
|
case "2":
|
|
8524
|
-
case "3":
|
|
8525
|
-
|
|
8526
|
-
if (e.altKey || e.shiftKey)
|
|
8945
|
+
case "3": {
|
|
8946
|
+
if (e.altKey || e.shiftKey || key === "s" && claimsHintKey(doc, key))
|
|
8527
8947
|
return;
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
case "x":
|
|
8531
|
-
if (e.altKey)
|
|
8948
|
+
const m = modeForKey(key, modeList);
|
|
8949
|
+
if (!m)
|
|
8532
8950
|
return;
|
|
8533
8951
|
claim();
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
return;
|
|
8952
|
+
return setMode(m);
|
|
8953
|
+
}
|
|
8537
8954
|
case "z":
|
|
8538
8955
|
if (e.altKey)
|
|
8539
8956
|
return;
|
|
@@ -8589,8 +9006,11 @@ function compare(container, options) {
|
|
|
8589
9006
|
labels[1].textContent = labelOf(1);
|
|
8590
9007
|
loupeBtn.setAttribute("aria-pressed", String(loupeOn));
|
|
8591
9008
|
loopBtn.setAttribute("aria-pressed", String(loopNow() !== null));
|
|
9009
|
+
const modePill = modeList.length > 1 ? selectionPill(modes, {
|
|
9010
|
+
rows: () => modeBtns,
|
|
9011
|
+
selected: () => modeBtns[modeList.indexOf(mode)] ?? null
|
|
9012
|
+
}) : null;
|
|
8592
9013
|
syncModeUi();
|
|
8593
|
-
syncFlicker();
|
|
8594
9014
|
const unlisten = tl?.listen({
|
|
8595
9015
|
onSeek: (t, source) => {
|
|
8596
9016
|
if (source === "play") {
|
|
@@ -8648,24 +9068,20 @@ function compare(container, options) {
|
|
|
8648
9068
|
});
|
|
8649
9069
|
root.addEventListener("keydown", onKey);
|
|
8650
9070
|
root.addEventListener("keyup", (e) => {
|
|
8651
|
-
|
|
8652
|
-
if (key === "x")
|
|
8653
|
-
holdFlick(false);
|
|
8654
|
-
else if (key === "z")
|
|
9071
|
+
if (e.key.toLowerCase() === "z")
|
|
8655
9072
|
holdLoupe(false);
|
|
8656
9073
|
});
|
|
8657
9074
|
root.addEventListener("focusout", (e) => {
|
|
8658
9075
|
if (root.contains(e.relatedTarget))
|
|
8659
9076
|
return;
|
|
8660
|
-
holdFlick(false);
|
|
8661
9077
|
holdLoupe(false);
|
|
8662
9078
|
});
|
|
8663
9079
|
const unregisterHints = options.hints === false ? undefined : hints.register(root, () => [
|
|
8664
|
-
...
|
|
9080
|
+
...modeList.length > 1 ? modeList.map((m, i) => ({
|
|
8665
9081
|
el: modeBtns[i],
|
|
8666
|
-
label: m.
|
|
8667
|
-
activate: () => setMode(m
|
|
8668
|
-
})),
|
|
9082
|
+
label: MODE_LABEL[m].long,
|
|
9083
|
+
activate: () => setMode(m)
|
|
9084
|
+
})) : [],
|
|
8669
9085
|
{ el: loopBtn, label: "Loop", activate: () => api.setLoop(!loopNow()) },
|
|
8670
9086
|
...loupeBtn.hidden ? [] : [{ el: loupeBtn, label: "Loupe", activate: () => setLoupe(!loupeOn) }],
|
|
8671
9087
|
...zoomed() ? [{ el: zoomReset, label: "Reset zoom", activate: () => api.setZoom(null) }] : [],
|
|
@@ -8690,6 +9106,7 @@ function compare(container, options) {
|
|
|
8690
9106
|
get mode() {
|
|
8691
9107
|
return mode;
|
|
8692
9108
|
},
|
|
9109
|
+
modes: modeList,
|
|
8693
9110
|
get wipe() {
|
|
8694
9111
|
return wipe;
|
|
8695
9112
|
},
|
|
@@ -8745,7 +9162,7 @@ function compare(container, options) {
|
|
|
8745
9162
|
tl?.update({ fps: next.fps });
|
|
8746
9163
|
requestDraw();
|
|
8747
9164
|
},
|
|
8748
|
-
seek: (t) => tl ? tl.seek(t) : ownSeek(t),
|
|
9165
|
+
seek: (t, o) => tl ? tl.seek(t, o?.glide ? { glide: true } : undefined) : ownSeek(t),
|
|
8749
9166
|
seekBy(d) {
|
|
8750
9167
|
const total = duration || tl?.duration || 0;
|
|
8751
9168
|
const t = Math.min(total, Math.max(0, now() + d));
|
|
@@ -8786,7 +9203,7 @@ function compare(container, options) {
|
|
|
8786
9203
|
dispose() {
|
|
8787
9204
|
disposed = true;
|
|
8788
9205
|
loop.stop();
|
|
8789
|
-
|
|
9206
|
+
modePill?.dispose();
|
|
8790
9207
|
win.clearTimeout(releaseTimer);
|
|
8791
9208
|
ro.disconnect();
|
|
8792
9209
|
nearIo?.disconnect();
|
|
@@ -8816,7 +9233,7 @@ function compare(container, options) {
|
|
|
8816
9233
|
wake();
|
|
8817
9234
|
return api;
|
|
8818
9235
|
}
|
|
8819
|
-
var COMPARE_FFMPEG = "ffmpeg -i in.mov -vf fps=30 -fps_mode cfr -c:v libx264 -preset slow -crf 18 -g 15 -keyint_min 15 -pix_fmt yuv420p -movflags +faststart -an out.mp4", LAYOUT, FADE2, FADE_REDUCED, ZOOM,
|
|
9236
|
+
var COMPARE_FFMPEG = "ffmpeg -i in.mov -vf fps=30 -fps_mode cfr -c:v libx264 -preset slow -crf 18 -g 15 -keyint_min 15 -pix_fmt yuv420p -movflags +faststart -an out.mp4", LAYOUT, FADE2, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
|
|
8820
9237
|
var init_compare = __esm(() => {
|
|
8821
9238
|
init_motion();
|
|
8822
9239
|
init_page_hints();
|
|
@@ -8824,21 +9241,24 @@ var init_compare = __esm(() => {
|
|
|
8824
9241
|
init_compare_gpu();
|
|
8825
9242
|
init_compare_model();
|
|
8826
9243
|
init_compare_strip();
|
|
9244
|
+
init_icons();
|
|
8827
9245
|
init_look();
|
|
8828
9246
|
init_rgba();
|
|
9247
|
+
init_selection_pill();
|
|
8829
9248
|
init_timeline();
|
|
9249
|
+
init_timeline_model();
|
|
8830
9250
|
LAYOUT = springMotion({ duration: 0.35, bounce: 0 });
|
|
8831
9251
|
FADE2 = springMotion(SPRINGS.snappy);
|
|
8832
9252
|
FADE_REDUCED = springMotion(FADE);
|
|
8833
9253
|
ZOOM = springMotion({ duration: 0.35, bounce: 0 });
|
|
9254
|
+
WIPE_CATCH = springMotion(CATCH_UP);
|
|
9255
|
+
GRIP = { w: 16, h: 28 };
|
|
8834
9256
|
LOUPE = { r: 88, zoom: 4 };
|
|
8835
|
-
|
|
8836
|
-
{
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
];
|
|
8841
|
-
KEY_MODE = { s: "side", w: "wipe", d: "difference" };
|
|
9257
|
+
MODE_LABEL = {
|
|
9258
|
+
side: { short: "Side", long: "Side by side" },
|
|
9259
|
+
wipe: { short: "Wipe", long: "Wipe" },
|
|
9260
|
+
difference: { short: "Difference", long: "Difference" }
|
|
9261
|
+
};
|
|
8842
9262
|
});
|
|
8843
9263
|
|
|
8844
9264
|
// src/ui/scrollbar.ts
|
|
@@ -8960,34 +9380,27 @@ function tree(container, options) {
|
|
|
8960
9380
|
const empty = doc.createElement("div");
|
|
8961
9381
|
empty.className = "knobs-tree-empty";
|
|
8962
9382
|
empty.hidden = true;
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
9383
|
+
root.append(space, line, empty);
|
|
9384
|
+
let pill = null;
|
|
9385
|
+
let picked = null;
|
|
9386
|
+
let travelUntil = 0;
|
|
9387
|
+
const pickedRow = () => {
|
|
9388
|
+
const id = sel.ids.length === 1 ? sel.ids[0] : null;
|
|
9389
|
+
const v = id === null ? undefined : views.get(id);
|
|
9390
|
+
return v && !v.leaving && rowIndex.has(v.id) ? v : null;
|
|
8970
9391
|
};
|
|
8971
9392
|
const placeCursor = (animate) => {
|
|
8972
|
-
if (!
|
|
9393
|
+
if (!pill)
|
|
8973
9394
|
return;
|
|
8974
9395
|
const id = sel.ids.length === 1 ? sel.ids[0] : null;
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
}
|
|
8982
|
-
|
|
8983
|
-
cursorY.set(i * h);
|
|
8984
|
-
else
|
|
8985
|
-
cursorY.snap(i * h);
|
|
8986
|
-
cursorOn = true;
|
|
8987
|
-
cursor.setAttribute("data-on", "");
|
|
8988
|
-
drawCursor();
|
|
8989
|
-
if (!cursorY.resting)
|
|
8990
|
-
loop.wake();
|
|
9396
|
+
root.toggleAttribute("data-glide", id !== null && rowIndex.has(id));
|
|
9397
|
+
const fresh = id !== picked;
|
|
9398
|
+
picked = id;
|
|
9399
|
+
if (animate && fresh) {
|
|
9400
|
+
travelUntil = performance.now() + 450;
|
|
9401
|
+
pill.update();
|
|
9402
|
+
} else if (performance.now() >= travelUntil)
|
|
9403
|
+
pill.update({ snap: true });
|
|
8991
9404
|
};
|
|
8992
9405
|
container.append(root);
|
|
8993
9406
|
const scrollbar = overlayScrollbar(root, space);
|
|
@@ -9016,6 +9429,12 @@ function tree(container, options) {
|
|
|
9016
9429
|
};
|
|
9017
9430
|
const views = new Map;
|
|
9018
9431
|
const pool = [];
|
|
9432
|
+
if (options.glide)
|
|
9433
|
+
pill = selectionPill(root, {
|
|
9434
|
+
rows: () => [...views.values()].filter((v) => !v.leaving).map((v) => v.el),
|
|
9435
|
+
selected: () => pickedRow()?.el ?? null,
|
|
9436
|
+
radius: (row) => Number.parseFloat(win.getComputedStyle(row, "::before").borderTopLeftRadius) || 0
|
|
9437
|
+
});
|
|
9019
9438
|
const makeView = (id) => {
|
|
9020
9439
|
const v = pool.pop() ?? createView();
|
|
9021
9440
|
v.id = id;
|
|
@@ -9196,12 +9615,6 @@ function tree(container, options) {
|
|
|
9196
9615
|
function frame(dt) {
|
|
9197
9616
|
let moving = false;
|
|
9198
9617
|
let bottom = rows.length * h;
|
|
9199
|
-
if (options.glide && !cursorY.resting) {
|
|
9200
|
-
cursorY.step(dt);
|
|
9201
|
-
drawCursor();
|
|
9202
|
-
if (!cursorY.resting)
|
|
9203
|
-
moving = true;
|
|
9204
|
-
}
|
|
9205
9618
|
for (const v of views.values()) {
|
|
9206
9619
|
v.y.step(dt);
|
|
9207
9620
|
if (!v.y.resting)
|
|
@@ -9221,6 +9634,8 @@ function tree(container, options) {
|
|
|
9221
9634
|
}
|
|
9222
9635
|
setSpace(moving ? bottom : rows.length * h);
|
|
9223
9636
|
trim();
|
|
9637
|
+
if (pill && moving && performance.now() >= travelUntil)
|
|
9638
|
+
pill.update({ snap: true });
|
|
9224
9639
|
return moving;
|
|
9225
9640
|
}
|
|
9226
9641
|
const trim = () => {
|
|
@@ -9849,6 +10264,7 @@ function tree(container, options) {
|
|
|
9849
10264
|
autoScroll.stop();
|
|
9850
10265
|
ro.disconnect();
|
|
9851
10266
|
scrollbar.dispose();
|
|
10267
|
+
pill?.dispose();
|
|
9852
10268
|
dragging?.ghost.remove();
|
|
9853
10269
|
root.remove();
|
|
9854
10270
|
}
|
|
@@ -9867,6 +10283,7 @@ var init_tree = __esm(() => {
|
|
|
9867
10283
|
init_motion();
|
|
9868
10284
|
init_page_hints();
|
|
9869
10285
|
init_look();
|
|
10286
|
+
init_selection_pill();
|
|
9870
10287
|
init_tree_model();
|
|
9871
10288
|
MOVE = springMotion(SPRINGS.default);
|
|
9872
10289
|
});
|
|
@@ -9916,33 +10333,29 @@ var init_diff_css = __esm(() => {
|
|
|
9916
10333
|
.knobs-diff-fill{flex:1}
|
|
9917
10334
|
.knobs-diff-nav{flex:none;display:inline-flex;align-items:center;gap:1px;color:var(--_k-fg-3);font-size:var(--_k-text-meta);font-variant-numeric:tabular-nums}
|
|
9918
10335
|
.knobs-diff-nav>span{min-width:3.6em;text-align:center}
|
|
9919
|
-
:where(.knobs-ui) :is(.knobs-diff-btn,.knobs-diff-
|
|
10336
|
+
: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;
|
|
9920
10337
|
margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
9921
|
-
.knobs-ui .knobs-diff-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:
|
|
10338
|
+
.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;
|
|
9922
10339
|
transition:background .15s,color .15s,opacity .15s}
|
|
9923
10340
|
.knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
9924
10341
|
.knobs-ui .knobs-diff-btn[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
|
|
9925
10342
|
.knobs-ui .knobs-diff-btn:disabled{opacity:.35;cursor:default;background:transparent}
|
|
9926
|
-
.knobs-diff-btn svg{width:
|
|
10343
|
+
.knobs-diff-btn svg{width:16px;height:16px}
|
|
9927
10344
|
/* Viewed: a check with its word, as on GitHub; the path dims once it's ticked. */
|
|
9928
10345
|
.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);
|
|
9929
10346
|
font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:background .15s,color .15s}
|
|
9930
10347
|
.knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
9931
10348
|
.knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
|
|
9932
|
-
.knobs-diff-viewed svg{width:
|
|
10349
|
+
.knobs-diff-viewed svg{width:16px;height:16px}
|
|
9933
10350
|
.knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
|
|
9934
10351
|
.knobs-diff-path{transition:opacity .2s}
|
|
9935
10352
|
.knobs-diff[data-viewed]>.knobs-diff-head .knobs-diff-path,.knobs-diff-part[data-viewed] .knobs-diff-path{opacity:.6}
|
|
9936
|
-
/* Split | Unified:
|
|
9937
|
-
|
|
9938
|
-
.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-
|
|
9939
|
-
.knobs-ui .knobs-diff-
|
|
9940
|
-
font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color
|
|
9941
|
-
.knobs-ui .knobs-diff-
|
|
9942
|
-
.knobs-diff-pill{position:absolute;top:2px;bottom:2px;left:2px;width:calc(50% - 2px);border-radius:max(0px,calc(var(--kd-ctl) - 2px));background:color-mix(in srgb,var(--_k-accent) 18%,var(--_k-segmented-active));
|
|
9943
|
-
box-shadow:0 1px 2px rgba(0,0,0,calc(.16 * var(--k-lift,1)));transition:transform var(--_ku-ease-ms) var(--_ku-ease)}
|
|
9944
|
-
.knobs-diff-seg[data-value="unified"]>.knobs-diff-pill{transform:translateX(100%)}
|
|
9945
|
-
.knobs-diff[data-frame="flat"] .knobs-diff-pill{box-shadow:none}
|
|
10353
|
+
/* Split | Unified: the selection pill on the one picked, its bright copy the picked one's text; a
|
|
10354
|
+
choice isn't brighter for being picked. Choices are styled by class, as the copy holds them too. */
|
|
10355
|
+
.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)}
|
|
10356
|
+
.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);
|
|
10357
|
+
font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
10358
|
+
.knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg)}
|
|
9946
10359
|
/* The diff: one layer per file or layout on show, the old fading under the new. */
|
|
9947
10360
|
.knobs-diff-body{position:relative;flex:1;min-height:0}
|
|
9948
10361
|
.knobs-diff-probe{position:absolute;left:0;top:0;width:0;height:var(--kd-line);visibility:hidden;pointer-events:none}
|
|
@@ -10037,7 +10450,7 @@ var init_diff_css = __esm(() => {
|
|
|
10037
10450
|
.knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
|
|
10038
10451
|
.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}
|
|
10039
10452
|
.knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
10040
|
-
.knobs-diff-filter>button svg{width:
|
|
10453
|
+
.knobs-diff-filter>button svg{width:12px;height:12px}
|
|
10041
10454
|
.knobs-diff-list{position:relative;flex:1;min-height:0}
|
|
10042
10455
|
.knobs-diff-files .knobs-diff-counts{font-size:var(--_k-text-meta);align-items:center}
|
|
10043
10456
|
.knobs-diff-files .knobs-diff-counts{gap:5px}
|
|
@@ -10843,7 +11256,7 @@ function codeDiff(container, options) {
|
|
|
10843
11256
|
};
|
|
10844
11257
|
const nav = make("span", "knobs-diff-nav");
|
|
10845
11258
|
const prev = button("Previous file", PREV);
|
|
10846
|
-
const next = button("Next file",
|
|
11259
|
+
const next = button("Next file", NEXT);
|
|
10847
11260
|
const place = make("span", "");
|
|
10848
11261
|
nav.append(prev, place, next);
|
|
10849
11262
|
const wrapButton = button("Wrap lines", WRAP);
|
|
@@ -10864,9 +11277,8 @@ function codeDiff(container, options) {
|
|
|
10864
11277
|
const seg = make("div", "knobs-diff-seg");
|
|
10865
11278
|
seg.setAttribute("role", "radiogroup");
|
|
10866
11279
|
seg.setAttribute("aria-label", "Layout");
|
|
10867
|
-
const pill = make("i", "knobs-diff-pill");
|
|
10868
11280
|
const choice = (value, label) => {
|
|
10869
|
-
const b = make("button", "");
|
|
11281
|
+
const b = make("button", "knobs-diff-choice");
|
|
10870
11282
|
b.type = "button";
|
|
10871
11283
|
b.setAttribute("role", "radio");
|
|
10872
11284
|
b.dataset.value = value;
|
|
@@ -10875,7 +11287,7 @@ function codeDiff(container, options) {
|
|
|
10875
11287
|
};
|
|
10876
11288
|
const splitButton = choice("split", "Split");
|
|
10877
11289
|
const unifiedButton = choice("unified", "Unified");
|
|
10878
|
-
seg.append(
|
|
11290
|
+
seg.append(splitButton, unifiedButton);
|
|
10879
11291
|
if (options.lead)
|
|
10880
11292
|
head.append(options.lead);
|
|
10881
11293
|
head.append(path, stat, make("span", "knobs-diff-fill"), nav, wrapButton, seg, viewedButton);
|
|
@@ -10886,6 +11298,11 @@ function codeDiff(container, options) {
|
|
|
10886
11298
|
body.append(note, probe);
|
|
10887
11299
|
root.append(head, body);
|
|
10888
11300
|
container.append(root);
|
|
11301
|
+
const segPill = selectionPill(seg, {
|
|
11302
|
+
rows: () => [splitButton, unifiedButton],
|
|
11303
|
+
selected: () => layout === "split" ? splitButton : unifiedButton
|
|
11304
|
+
});
|
|
11305
|
+
let pilled = layout;
|
|
10889
11306
|
const fileNow = () => files.find((f) => f.name === current) ?? null;
|
|
10890
11307
|
const paintHead = () => {
|
|
10891
11308
|
const file = fileNow();
|
|
@@ -10918,6 +11335,10 @@ function codeDiff(container, options) {
|
|
|
10918
11335
|
next.disabled = at >= files.length - 1;
|
|
10919
11336
|
wrapButton.setAttribute("aria-pressed", String(wrap));
|
|
10920
11337
|
seg.dataset.value = layout;
|
|
11338
|
+
if (pilled !== layout) {
|
|
11339
|
+
pilled = layout;
|
|
11340
|
+
segPill.update();
|
|
11341
|
+
}
|
|
10921
11342
|
splitButton.setAttribute("aria-checked", String(layout === "split"));
|
|
10922
11343
|
unifiedButton.setAttribute("aria-checked", String(layout === "unified"));
|
|
10923
11344
|
splitButton.tabIndex = layout === "split" ? 0 : -1;
|
|
@@ -11752,6 +12173,7 @@ function codeDiff(container, options) {
|
|
|
11752
12173
|
dispose() {
|
|
11753
12174
|
disposed = true;
|
|
11754
12175
|
loop.stop();
|
|
12176
|
+
segPill.dispose();
|
|
11755
12177
|
sizes.disconnect();
|
|
11756
12178
|
attaching.disconnect();
|
|
11757
12179
|
resized.disconnect();
|
|
@@ -12107,7 +12529,7 @@ function diffReview(container, options) {
|
|
|
12107
12529
|
}
|
|
12108
12530
|
};
|
|
12109
12531
|
}
|
|
12110
|
-
var lib, load = () => lib ??= import("@pierre/diffs"), SYNTAX, IN, OUT, FADE3, HEIGHT, DRAWER, BLUR = 3, WRAP, PREV, UNVIEWED, VIEWED, SEARCH, CLEAR, SIDEBAR, SEPARATOR = 32;
|
|
12532
|
+
var lib, load = () => lib ??= import("@pierre/diffs"), SYNTAX, IN, OUT, FADE3, HEIGHT, DRAWER, BLUR = 3, WRAP, PREV, NEXT, UNVIEWED, VIEWED, SEARCH, CLEAR, SIDEBAR, SEPARATOR = 32;
|
|
12111
12533
|
var init_diff = __esm(() => {
|
|
12112
12534
|
init_glide();
|
|
12113
12535
|
init_motion();
|
|
@@ -12115,6 +12537,8 @@ var init_diff = __esm(() => {
|
|
|
12115
12537
|
init_diff_model();
|
|
12116
12538
|
init_look();
|
|
12117
12539
|
init_rgba();
|
|
12540
|
+
init_icons();
|
|
12541
|
+
init_selection_pill();
|
|
12118
12542
|
init_tree();
|
|
12119
12543
|
SYNTAX = { dark: "pierre-dark", light: "pierre-light" };
|
|
12120
12544
|
IN = springMotion(SPRINGS.default);
|
|
@@ -12122,13 +12546,14 @@ var init_diff = __esm(() => {
|
|
|
12122
12546
|
FADE3 = springMotion(FADE);
|
|
12123
12547
|
HEIGHT = springMotion(SPRINGS.calm);
|
|
12124
12548
|
DRAWER = springMotion(SPRINGS.calm);
|
|
12125
|
-
WRAP =
|
|
12126
|
-
PREV =
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12549
|
+
WRAP = icon("arrowUDownLeft");
|
|
12550
|
+
PREV = icon("caretLeft");
|
|
12551
|
+
NEXT = icon("caretRight");
|
|
12552
|
+
UNVIEWED = icon("circle");
|
|
12553
|
+
VIEWED = icon("checkCircle");
|
|
12554
|
+
SEARCH = icon("magnifyingGlass", { small: true });
|
|
12555
|
+
CLEAR = icon("x", { small: true });
|
|
12556
|
+
SIDEBAR = icon("sidebar");
|
|
12132
12557
|
});
|
|
12133
12558
|
|
|
12134
12559
|
// src/ui/index.ts
|
|
@@ -14629,6 +15054,11 @@ function readout(field, options = {}) {
|
|
|
14629
15054
|
dispose: () => view.dispose()
|
|
14630
15055
|
};
|
|
14631
15056
|
}
|
|
15057
|
+
|
|
15058
|
+
// src/ui/index.ts
|
|
15059
|
+
init_icons();
|
|
15060
|
+
init_selection_pill();
|
|
15061
|
+
|
|
14632
15062
|
// src/ui/tooltip.ts
|
|
14633
15063
|
init_keys();
|
|
14634
15064
|
init_layer();
|
|
@@ -18390,6 +18820,7 @@ function activity(container, options) {
|
|
|
18390
18820
|
// src/ui/picker.ts
|
|
18391
18821
|
init_motion();
|
|
18392
18822
|
init_look();
|
|
18823
|
+
init_icons();
|
|
18393
18824
|
|
|
18394
18825
|
// src/ui/picker-model.ts
|
|
18395
18826
|
function digitPick(ids, key) {
|
|
@@ -18444,7 +18875,7 @@ var CLOSE3 = springMotion(CLOSE);
|
|
|
18444
18875
|
var CARD = springMotion(SPRINGS.default);
|
|
18445
18876
|
var FADE6 = springMotion(FADE);
|
|
18446
18877
|
var DRIFT = 6;
|
|
18447
|
-
var CHECK2 =
|
|
18878
|
+
var CHECK2 = icon("check", { small: true });
|
|
18448
18879
|
function optionPicker(container, options) {
|
|
18449
18880
|
const doc = container.ownerDocument;
|
|
18450
18881
|
const win = doc.defaultView;
|
|
@@ -18970,7 +19401,7 @@ var walkthroughCss = `
|
|
|
18970
19401
|
.knobs-walkthrough a{color:inherit;text-decoration:underline;text-decoration-color:color-mix(in srgb,currentColor 35%,transparent);text-underline-offset:2px;transition:text-decoration-color .15s}
|
|
18971
19402
|
.knobs-walkthrough a:hover{text-decoration-color:currentColor}
|
|
18972
19403
|
.knobs-walkthrough code,.knobs-wt-slot code{font:calc(1em - 1px) var(--_k-mono);padding:1px 5px;border-radius:5px;background:var(--_k-fill)}
|
|
18973
|
-
:where(.knobs-ui) :is(.knobs-wt-btn,.knobs-wt-pick,.knobs-wt-toc-step){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;
|
|
19404
|
+
:where(.knobs-ui) :is(.knobs-wt-btn,.knobs-wt-pick,.knobs-wt-toc-step,button.knobs-wt-stitle,.knobs-wt-chapter){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;
|
|
18974
19405
|
background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
18975
19406
|
|
|
18976
19407
|
/* ——— layout: the rail beside the column; one column where it's narrow ——— */
|
|
@@ -19029,9 +19460,9 @@ var walkthroughCss = `
|
|
|
19029
19460
|
.knobs-wt-fi-body{max-width:var(--kw-measure);color:var(--_k-fg)}
|
|
19030
19461
|
.knobs-wt-fi-q{color:var(--_k-fg-2)}
|
|
19031
19462
|
.knobs-wt-fi-q::before{content:"You asked: ";color:var(--_k-fg-3)}
|
|
19032
|
-
:where(.knobs-ui) .knobs-wt-fi-step{all:revert;box-sizing:border-box;font:var(--_k-weight) calc(var(--
|
|
19463
|
+
:where(.knobs-ui) :is(.knobs-wt-fi-step,.knobs-wt-steplink){all:revert;box-sizing:border-box;font:var(--_k-weight) calc(var(--k-walkthrough-text,14px) - 2.5px)/1 var(--_k-mono);color:var(--_k-fg-2);background:transparent;border:0;
|
|
19033
19464
|
padding:3px 6px;margin:0;border-radius:6px;cursor:pointer;outline:none;transition:background .15s,color .15s}
|
|
19034
|
-
.knobs-ui .knobs-wt-fi-step:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
19465
|
+
.knobs-ui :is(.knobs-wt-fi-step,.knobs-wt-steplink):is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
19035
19466
|
|
|
19036
19467
|
/* ——— a step's sent notes ——— */
|
|
19037
19468
|
.knobs-wt-notes{display:flex;flex-direction:column}
|
|
@@ -19167,7 +19598,8 @@ var walkthroughCss = `
|
|
|
19167
19598
|
}
|
|
19168
19599
|
|
|
19169
19600
|
/* ——— media slots ——— */
|
|
19170
|
-
.knobs-ui.knobs-wt-slot{position:relative;min-width:0;container:knobs-wt-slot / inline-size;color:var(--_k-fg);font:var(--_k-weight) var(--k-walkthrough-text,14px)/1.5 var(--_k-font)
|
|
19601
|
+
.knobs-ui.knobs-wt-slot{position:relative;min-width:0;container:knobs-wt-slot / inline-size;color:var(--_k-fg);font:var(--_k-weight) var(--k-walkthrough-text,14px)/1.5 var(--_k-font);
|
|
19602
|
+
--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info))}
|
|
19171
19603
|
.knobs-wt-slot-label{display:block;margin:0 0 6px 2px;font:var(--_k-weight-label) calc(var(--k-walkthrough-text,14px) - 3px)/1.3 var(--_k-font);color:var(--_k-fg-2);letter-spacing:.04em;text-transform:uppercase}
|
|
19172
19604
|
/* A compare is exactly as tall as its pictures at this width, so there's no letterbox: the two
|
|
19173
19605
|
sides edge to edge (side) or one (overlaid), plus its mode bar, the timeline and their two gaps
|
|
@@ -19207,9 +19639,141 @@ var walkthroughCss = `
|
|
|
19207
19639
|
.knobs-wt-text{max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
|
|
19208
19640
|
.knobs-wt-hold{border-radius:calc(var(--_k-radius-panel) - 4px);background:var(--_k-fill)}
|
|
19209
19641
|
.knobs-wt-failed{display:flex;align-items:center;justify-content:center;min-height:80px;color:var(--_k-fg-3);font-size:calc(var(--k-walkthrough-text,14px) - 2px)}
|
|
19642
|
+
|
|
19643
|
+
/* ——— data blocks: history, stack, table, chapters. On the step's card, so no card of their own:
|
|
19644
|
+
hairlines and fills only. Tones read the status tokens; hover fades are the panel's own. ——— */
|
|
19645
|
+
:where(.knobs-wt-history,.knobs-wt-stack,.knobs-wt-table,.knobs-wt-chapters){--kw-t:var(--k-walkthrough-text,14px);--kw-line:calc(var(--kw-t) + 6px);--kw-rail:var(--_k-border-strong)}
|
|
19646
|
+
:where(.knobs-wt-slot) [data-tone="good"]{--kw-tone:var(--kw-good)}
|
|
19647
|
+
:where(.knobs-wt-slot) [data-tone="warn"]{--kw-tone:var(--_k-warn)}
|
|
19648
|
+
:where(.knobs-wt-slot) [data-tone="bad"]{--kw-tone:var(--_k-danger)}
|
|
19649
|
+
:where(.knobs-wt-slot) [data-tone="info"]{--kw-tone:var(--kw-info)}
|
|
19650
|
+
:where(.knobs-wt-slot) [data-tone="dim"]{--kw-tone:var(--_k-fg-3)}
|
|
19651
|
+
.knobs-wt-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
|
|
19652
|
+
.knobs-ui .knobs-wt-tag{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:5px;background:var(--_k-fill);color:var(--_k-fg-2);font:var(--_k-weight) calc(var(--kw-t) - 3px)/1 var(--_k-mono);
|
|
19653
|
+
text-decoration:none;white-space:nowrap;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
19654
|
+
.knobs-ui a.knobs-wt-tag:is(:hover,:focus-visible){background:var(--_k-fill-selected);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
19655
|
+
|
|
19656
|
+
/* A history: dates down the left, a spine of tone dots joined by a hairline (broken around each
|
|
19657
|
+
dot), the event beside it. Inferred events take a ring for a dot and a dashed tag. */
|
|
19658
|
+
.knobs-wt-history{--kw-hy:calc(5px + var(--kw-line) / 2);display:flex;flex-direction:column;gap:18px;font-size:calc(var(--kw-t) - 1px);line-height:var(--kw-line);color:var(--_k-fg)}
|
|
19659
|
+
.knobs-wt-hgroup{display:flex;flex-direction:column;gap:6px;min-width:0}
|
|
19660
|
+
.knobs-wt-hhead{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 10px}
|
|
19661
|
+
.knobs-ui .knobs-wt-hheading{margin:0;font:var(--_k-weight-label) calc(var(--kw-t) - 3px)/1.3 var(--_k-font);letter-spacing:.04em;text-transform:uppercase;color:var(--_k-fg-2)}
|
|
19662
|
+
.knobs-wt-hsum{color:var(--_k-fg-3);font-size:calc(var(--kw-t) - 2px);font-variant-numeric:tabular-nums}
|
|
19663
|
+
.knobs-wt-hlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
|
|
19664
|
+
.knobs-wt-hev{display:grid;grid-template-columns:var(--kw-hw,11ch) 14px minmax(0,1fr);grid-template-areas:"when spine main";column-gap:10px;align-items:baseline;padding:5px 0}
|
|
19665
|
+
.knobs-wt-hwhen{grid-area:when;display:flex;flex-direction:column;min-width:0;font:var(--_k-weight) calc(var(--kw-t) - 2px)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-2)}
|
|
19666
|
+
.knobs-wt-hage{color:var(--_k-fg-3);font-size:calc(var(--kw-t) - 3px);line-height:1.35}
|
|
19667
|
+
.knobs-wt-hspine{grid-area:spine;position:relative;align-self:stretch;margin-block:-5px}
|
|
19668
|
+
.knobs-wt-hspine::before,.knobs-wt-hspine::after{content:"";position:absolute;left:6.5px;width:1px;background:var(--kw-rail)}
|
|
19669
|
+
.knobs-wt-hspine::before{top:0;height:calc(var(--kw-hy) - 6px)}
|
|
19670
|
+
.knobs-wt-hspine::after{top:calc(var(--kw-hy) + 6px);bottom:0}
|
|
19671
|
+
.knobs-wt-hev:first-child .knobs-wt-hspine::before,.knobs-wt-hev:last-child .knobs-wt-hspine::after{content:none}
|
|
19672
|
+
.knobs-wt-hdot{position:absolute;left:3.5px;top:calc(var(--kw-hy) - 3.5px);width:7px;height:7px;border-radius:50%;background:var(--kw-tone,var(--_k-fg-3))}
|
|
19673
|
+
.knobs-wt-hev[data-type="release"] .knobs-wt-hdot{border-radius:1.5px;rotate:45deg;scale:.9}
|
|
19674
|
+
.knobs-wt-hev[data-type="audit"] .knobs-wt-hdot{border-radius:1.5px}
|
|
19675
|
+
.knobs-wt-hev[data-provenance="inferred"] .knobs-wt-hdot{background:transparent;box-shadow:inset 0 0 0 1.5px var(--kw-tone,var(--_k-fg-3))}
|
|
19676
|
+
.knobs-wt-hmain{grid-area:main;display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
19677
|
+
.knobs-wt-htitle{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px;font-weight:var(--_k-weight-label);color:var(--_k-fg)}
|
|
19678
|
+
.knobs-wt-prov{padding:0 6px;border-radius:5px;background:var(--_k-fill);color:var(--_k-fg-3);font:var(--_k-weight) calc(var(--kw-t) - 3.5px)/18px var(--_k-mono);white-space:nowrap}
|
|
19679
|
+
.knobs-wt-hev[data-provenance="inferred"] .knobs-wt-prov{background:transparent;color:var(--_k-warn);outline:1px dashed color-mix(in oklch,var(--_k-warn) 50%,transparent);outline-offset:-1px}
|
|
19680
|
+
.knobs-ui .knobs-wt-hdetail{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
|
|
19681
|
+
@container knobs-wt-slot (max-width:520px){
|
|
19682
|
+
.knobs-wt-hev{grid-template-columns:14px minmax(0,1fr);grid-template-areas:"spine when" "spine main"}
|
|
19683
|
+
.knobs-wt-hwhen{flex-direction:row;flex-wrap:wrap;align-items:baseline;column-gap:8px}
|
|
19684
|
+
}
|
|
19685
|
+
|
|
19686
|
+
/* A stack: the trunk at the top, each PR hanging off the branch it merges into, rails drawn as a
|
|
19687
|
+
git graph (solid, so where two meet they don't darken). A PR with a step goes there on a click. */
|
|
19688
|
+
.knobs-wt-stack{--kw-spt:7px;--kw-spb:7px;--kw-sy:calc(var(--kw-spt) + var(--kw-line) / 2);display:flex;flex-direction:column;font-size:calc(var(--kw-t) - 1px);line-height:var(--kw-line);color:var(--_k-fg)}
|
|
19689
|
+
.knobs-wt-srow{display:grid;grid-template-columns:auto minmax(0,1fr) auto;column-gap:10px;align-items:start;margin:0 -8px;padding:var(--kw-spt) 8px var(--kw-spb);
|
|
19690
|
+
border-radius:max(0px,calc(var(--_k-radius-panel) - 8px));transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
19691
|
+
.knobs-wt-srow[data-trunk]{--kw-spb:0px;grid-template-columns:auto minmax(0,1fr)}
|
|
19692
|
+
.knobs-wt-srow[data-go]{cursor:pointer}
|
|
19693
|
+
.knobs-wt-srow[data-go]:hover{background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
19694
|
+
.knobs-wt-lanes{display:flex;align-self:stretch;margin-block:calc(var(--kw-spt) * -1) calc(var(--kw-spb) * -1)}
|
|
19695
|
+
.knobs-wt-lane{position:relative;flex:none;width:16px}
|
|
19696
|
+
.knobs-wt-lane:is([data-rail="pass"],[data-rail="tee"],[data-down])::before{content:"";position:absolute;left:7.5px;top:0;bottom:0;width:1px;background:var(--kw-rail)}
|
|
19697
|
+
.knobs-wt-lane[data-down]::before{top:calc(var(--kw-sy) + 3.5px)}
|
|
19698
|
+
.knobs-wt-lane:is([data-rail="tee"],[data-rail="end"])::after{content:"";position:absolute;left:7.5px;top:0;width:13px;height:calc(var(--kw-sy) + .5px);border:solid var(--kw-rail);border-width:0 0 1px 1px;border-bottom-left-radius:6px}
|
|
19699
|
+
.knobs-wt-lane[data-rail="node"]::after{content:"";position:absolute;left:4.5px;top:calc(var(--kw-sy) - 3.5px);width:7px;height:7px;border-radius:50%;background:var(--_k-fg-3)}
|
|
19700
|
+
.knobs-wt-srow[data-trunk] .knobs-wt-lane[data-rail="node"]::after{background:var(--_k-fg-2)}
|
|
19701
|
+
.knobs-wt-trunk{font:var(--_k-weight-label) calc(var(--kw-t) - 2px)/var(--kw-line) var(--_k-mono);color:var(--_k-fg-2)}
|
|
19702
|
+
.knobs-wt-smain{display:flex;flex-direction:column;gap:1px;min-width:0}
|
|
19703
|
+
.knobs-wt-sline{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 8px}
|
|
19704
|
+
.knobs-wt-sord{font:var(--_k-weight) calc(var(--kw-t) - 2px)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-3);white-space:nowrap}
|
|
19705
|
+
.knobs-ui .knobs-wt-spr{font:var(--_k-weight-label) calc(var(--kw-t) - 1.5px)/var(--kw-line) var(--_k-mono);color:var(--_k-fg-2);text-decoration:none;white-space:nowrap}
|
|
19706
|
+
.knobs-ui a.knobs-wt-spr:is(:hover,:focus-visible){color:var(--_k-fg);text-decoration:underline}
|
|
19707
|
+
.knobs-ui .knobs-wt-stitle{font-weight:var(--_k-weight-label);line-height:var(--kw-line);color:var(--_k-fg);text-align:left}
|
|
19708
|
+
.knobs-ui button.knobs-wt-stitle{cursor:pointer;text-decoration:underline;text-decoration-color:transparent;text-underline-offset:3px;transition:text-decoration-color calc(var(--_k-hover-out) * 1ms)}
|
|
19709
|
+
.knobs-ui :is(.knobs-wt-srow[data-go]:hover button.knobs-wt-stitle,button.knobs-wt-stitle:focus-visible){text-decoration-color:color-mix(in oklch,currentColor 45%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
19710
|
+
.knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
|
|
19711
|
+
.knobs-wt-smeta{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 12px;font:var(--_k-weight) calc(var(--kw-t) - 2px)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-3)}
|
|
19712
|
+
.knobs-wt-delta{display:inline-flex;gap:6px}
|
|
19713
|
+
.knobs-wt-add{color:var(--_k-diff-add)}
|
|
19714
|
+
.knobs-wt-del{color:var(--_k-diff-del)}
|
|
19715
|
+
.knobs-wt-sstatus{margin-top:calc((var(--kw-line) - 20px) / 2)}
|
|
19716
|
+
.knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr) auto}
|
|
19717
|
+
.knobs-wt-stack[data-flat] .knobs-wt-srow+.knobs-wt-srow{box-shadow:inset 0 1px 0 var(--_k-border)}
|
|
19718
|
+
@container knobs-wt-slot (max-width:520px){
|
|
19719
|
+
.knobs-wt-srow:not([data-trunk]){grid-template-columns:auto minmax(0,1fr)}
|
|
19720
|
+
.knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr)}
|
|
19721
|
+
.knobs-wt-sstatus{grid-column:-2;justify-self:start;margin-top:4px}
|
|
19722
|
+
}
|
|
19723
|
+
|
|
19724
|
+
/* A table: a real one, its head stuck while it scrolls, both ways; edges fade where there's more
|
|
19725
|
+
(the top one under the head), and thin bars show on hover. */
|
|
19726
|
+
@property --kw-fade-l{syntax:"<number>";inherits:false;initial-value:1}
|
|
19727
|
+
@property --kw-fade-r{syntax:"<number>";inherits:false;initial-value:1}
|
|
19728
|
+
@keyframes kw-fade-l{from{--kw-fade-l:1}to{--kw-fade-l:0}}
|
|
19729
|
+
@keyframes kw-fade-r{from{--kw-fade-r:0}to{--kw-fade-r:1}}
|
|
19730
|
+
.knobs-wt-table{max-height:520px;overflow:auto;overscroll-behavior-x:contain;outline:none;scrollbar-width:thin;scrollbar-color:transparent transparent;
|
|
19731
|
+
transition:scrollbar-color calc(var(--_k-hover-out) * 1ms)}
|
|
19732
|
+
.knobs-wt-table:is(:hover,:focus-visible){scrollbar-color:color-mix(in oklch,var(--_k-fg) 28%,transparent) transparent;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
19733
|
+
.knobs-wt-table:focus-visible{box-shadow:inset 0 0 0 2px var(--_k-focus);border-radius:calc(var(--_k-radius-panel) - 4px)}
|
|
19734
|
+
@supports (animation-timeline:scroll()){
|
|
19735
|
+
.knobs-wt-table{animation:knobs-fade-a linear both,knobs-fade-b linear both,kw-fade-l linear both,kw-fade-r linear both;animation-duration:auto;
|
|
19736
|
+
animation-timeline:scroll(self block),scroll(self block),scroll(self inline),scroll(self inline);animation-range:0 24px,calc(100% - 24px) 100%,0 8px,calc(100% - 8px) 100%;
|
|
19737
|
+
--kw-table-mask-y:linear-gradient(to bottom,#000 var(--kw-thead,0px),rgb(0 0 0/var(--knobs-fade-a)) var(--kw-thead,0px),#000 calc(var(--kw-thead,0px) + 20px),#000 calc(100% - 20px),rgb(0 0 0/var(--knobs-fade-b)));
|
|
19738
|
+
--kw-table-mask-x:linear-gradient(to right,rgb(0 0 0/var(--kw-fade-l)),#000 24px,#000 calc(100% - 24px),rgb(0 0 0/var(--kw-fade-r)));
|
|
19739
|
+
-webkit-mask-image:var(--kw-table-mask-y),var(--kw-table-mask-x);mask-image:var(--kw-table-mask-y),var(--kw-table-mask-x);-webkit-mask-composite:source-in;mask-composite:intersect}
|
|
19740
|
+
}
|
|
19741
|
+
.knobs-wt-table table{width:100%;margin:0;border-collapse:separate;border-spacing:0;font-size:calc(var(--kw-t) - 1.5px);line-height:1.45;font-variant-numeric:tabular-nums;color:var(--_k-fg-2)}
|
|
19742
|
+
.knobs-wt-table caption{caption-side:bottom;padding-top:8px;text-align:left;color:var(--_k-fg-3);font-size:calc(var(--kw-t) - 2px)}
|
|
19743
|
+
.knobs-wt-table th{position:sticky;top:0;padding:0 12px 7px;text-align:left;vertical-align:bottom;font-weight:var(--_k-weight-label);font-size:calc(var(--kw-t) - 2.5px);color:var(--_k-fg-3);white-space:nowrap;
|
|
19744
|
+
background:var(--_k-pane-bg);box-shadow:inset 0 -1px 0 var(--_k-border)}
|
|
19745
|
+
.knobs-wt-table td{padding:8px 12px;vertical-align:baseline;box-shadow:inset 0 -1px 0 var(--_k-border)}
|
|
19746
|
+
.knobs-wt-table tr:last-child td{box-shadow:none}
|
|
19747
|
+
.knobs-wt-table :is(th,td):first-child{padding-left:0}
|
|
19748
|
+
.knobs-wt-table :is(th,td):last-child{padding-right:0}
|
|
19749
|
+
.knobs-wt-table td:first-child{color:var(--_k-fg)}
|
|
19750
|
+
.knobs-wt-table [data-align="right"]{text-align:right}
|
|
19751
|
+
.knobs-wt-table [data-align="center"]{text-align:center}
|
|
19752
|
+
.knobs-wt-table td[data-mono]{font-family:var(--_k-mono);font-size:calc(var(--kw-t) - 2.5px);color:var(--_k-fg);white-space:nowrap}
|
|
19753
|
+
.knobs-wt-table td[data-long]{min-width:24ch}
|
|
19754
|
+
.knobs-wt-table td>.knobs-wt-steplink{margin-left:4px}
|
|
19755
|
+
.knobs-ui .knobs-wt-table a.knobs-wt-chip{text-decoration:none}
|
|
19756
|
+
.knobs-wt-ctone{color:var(--kw-tone)}
|
|
19757
|
+
|
|
19758
|
+
/* Chapters: timestamp pills under a video; one highlight springs to the one playing. */
|
|
19759
|
+
.knobs-wt-chapters{position:relative;display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}
|
|
19760
|
+
.knobs-wt-chapter-mark{position:absolute;left:0;top:0;width:0;border-radius:calc(var(--_k-radius) + 2px);background:var(--_k-fill-selected);opacity:0;pointer-events:none;will-change:transform;
|
|
19761
|
+
transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms)}
|
|
19762
|
+
.knobs-wt-chapter-mark[data-on]{opacity:1}
|
|
19763
|
+
.knobs-ui .knobs-wt-chapter{position:relative;display:inline-flex;align-items:baseline;gap:6px;height:26px;padding:0 10px 0 8px;border-radius:calc(var(--_k-radius) + 2px);color:var(--_k-fg-2);
|
|
19764
|
+
font-size:calc(var(--kw-t) - 2px);line-height:26px;white-space:nowrap;cursor:pointer;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
19765
|
+
.knobs-ui .knobs-wt-chapter:hover{background:var(--_ku-wash);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
19766
|
+
.knobs-ui .knobs-wt-chapter[aria-current]{background:transparent;color:var(--_k-fg)}
|
|
19767
|
+
.knobs-ui .knobs-wt-chapter:focus-visible{box-shadow:inset 0 0 0 2px var(--_k-focus)}
|
|
19768
|
+
.knobs-wt-chapter-time{font:var(--_k-weight) calc(var(--kw-t) - 3px)/1 var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-3)}
|
|
19769
|
+
.knobs-wt-chapter[aria-current] .knobs-wt-chapter-time{color:var(--_k-fg-2)}
|
|
19210
19770
|
@media(prefers-reduced-motion:reduce){:is(.knobs-walkthrough,.knobs-wt-slot) :where(*,::before,::after){transition-duration:.15s!important;transition-property:opacity,background,color!important}}
|
|
19211
19771
|
`;
|
|
19212
19772
|
|
|
19773
|
+
// src/ui/walkthrough-blocks.ts
|
|
19774
|
+
init_motion();
|
|
19775
|
+
init_look();
|
|
19776
|
+
|
|
19213
19777
|
// src/ui/walkthrough-model.ts
|
|
19214
19778
|
var DEFAULT_VERDICTS = ["ship", "change", "unsure"];
|
|
19215
19779
|
function slug(text) {
|
|
@@ -19251,20 +19815,207 @@ function fillMedia(m, base) {
|
|
|
19251
19815
|
if (typeof m === "string")
|
|
19252
19816
|
return mediaOf(resolveSrc(m, base));
|
|
19253
19817
|
switch (m.kind) {
|
|
19254
|
-
case "compare":
|
|
19255
|
-
|
|
19256
|
-
case "video":
|
|
19818
|
+
case "compare": {
|
|
19819
|
+
const { chapters, ...rest } = m;
|
|
19257
19820
|
return {
|
|
19258
|
-
...
|
|
19821
|
+
...rest,
|
|
19822
|
+
before: source2(m.before, base),
|
|
19823
|
+
after: source2(m.after, base),
|
|
19824
|
+
...chapters?.length ? { chapters: fillChapters(chapters) } : {}
|
|
19825
|
+
};
|
|
19826
|
+
}
|
|
19827
|
+
case "video": {
|
|
19828
|
+
const { chapters, captionsSrc, captions, ...rest } = m;
|
|
19829
|
+
const vtt = captions ?? captionsSrc;
|
|
19830
|
+
return {
|
|
19831
|
+
...rest,
|
|
19259
19832
|
src: resolveSrc(m.src, base),
|
|
19260
|
-
...m.poster ? { poster: resolveSrc(m.poster, base) } : {}
|
|
19833
|
+
...m.poster ? { poster: resolveSrc(m.poster, base) } : {},
|
|
19834
|
+
...vtt ? { captions: resolveSrc(vtt, base) } : {},
|
|
19835
|
+
...chapters?.length ? { chapters: fillChapters(chapters) } : {}
|
|
19261
19836
|
};
|
|
19837
|
+
}
|
|
19262
19838
|
case "image":
|
|
19263
19839
|
return { ...m, src: resolveSrc(m.src, base) };
|
|
19840
|
+
case "history":
|
|
19841
|
+
return { kind: "history", groups: fillHistory(m) };
|
|
19842
|
+
case "stack":
|
|
19843
|
+
return {
|
|
19844
|
+
kind: "stack",
|
|
19845
|
+
...m.base ? { base: m.base } : {},
|
|
19846
|
+
items: m.items.map(fillStackItem)
|
|
19847
|
+
};
|
|
19848
|
+
case "table":
|
|
19849
|
+
return fillTable(m);
|
|
19264
19850
|
default:
|
|
19265
19851
|
return m;
|
|
19266
19852
|
}
|
|
19267
19853
|
}
|
|
19854
|
+
function fillHistory(m) {
|
|
19855
|
+
const groups = [];
|
|
19856
|
+
if (m.events?.length)
|
|
19857
|
+
groups.push({ events: m.events });
|
|
19858
|
+
for (const g of m.groups ?? [])
|
|
19859
|
+
groups.push({
|
|
19860
|
+
...g.heading ? { heading: g.heading } : {},
|
|
19861
|
+
...g.summary ? { summary: g.summary } : {},
|
|
19862
|
+
events: g.events ?? g.rows ?? []
|
|
19863
|
+
});
|
|
19864
|
+
return groups;
|
|
19865
|
+
}
|
|
19866
|
+
function parseBranch(branch) {
|
|
19867
|
+
if (!branch?.trim())
|
|
19868
|
+
return {};
|
|
19869
|
+
const [head, base] = branch.split(/\s*(?:→|->|=>|⇒)\s*/, 2).map((s) => s.trim());
|
|
19870
|
+
return { ...head ? { head } : {}, ...base ? { base } : {} };
|
|
19871
|
+
}
|
|
19872
|
+
function parseDelta(delta) {
|
|
19873
|
+
if (delta === undefined || typeof delta === "object")
|
|
19874
|
+
return delta;
|
|
19875
|
+
const m = /\+\s*([\d,_]+)[^\d+]*?[-−–]\s*([\d,_]+)/.exec(delta);
|
|
19876
|
+
if (!m)
|
|
19877
|
+
return delta.trim() || undefined;
|
|
19878
|
+
const n = (s) => Number(s.replace(/[,_]/g, ""));
|
|
19879
|
+
return { add: n(m[1]), del: n(m[2]) };
|
|
19880
|
+
}
|
|
19881
|
+
function fillStackItem(it) {
|
|
19882
|
+
const parsed = parseBranch(it.branch);
|
|
19883
|
+
const head = parsed.head ?? it.head;
|
|
19884
|
+
const base = parsed.base ?? it.base;
|
|
19885
|
+
const delta = parseDelta(it.delta);
|
|
19886
|
+
const step = it.step ?? it.stepId;
|
|
19887
|
+
const pr = String(it.pr).trim();
|
|
19888
|
+
return {
|
|
19889
|
+
pr: /^\d+$/.test(pr) ? `#${pr}` : pr,
|
|
19890
|
+
...it.href ? { href: it.href } : {},
|
|
19891
|
+
title: it.title,
|
|
19892
|
+
...head ? { head } : {},
|
|
19893
|
+
...base ? { base } : {},
|
|
19894
|
+
...delta !== undefined ? { delta } : {},
|
|
19895
|
+
...it.status ? { status: it.status } : {},
|
|
19896
|
+
...it.tone ? { tone: it.tone } : {},
|
|
19897
|
+
...it.summary ? { summary: it.summary } : {},
|
|
19898
|
+
...step ? { step } : {},
|
|
19899
|
+
...it.order !== undefined && it.order !== "" ? { order: String(it.order) } : {}
|
|
19900
|
+
};
|
|
19901
|
+
}
|
|
19902
|
+
function stackLayout(m) {
|
|
19903
|
+
const items = m.items;
|
|
19904
|
+
const byHead = new Map;
|
|
19905
|
+
items.forEach((it, i) => it.head && !byHead.has(it.head) && byHead.set(it.head, i));
|
|
19906
|
+
let trunk = m.base ?? null;
|
|
19907
|
+
if (!trunk) {
|
|
19908
|
+
const tally = new Map;
|
|
19909
|
+
for (const it of items)
|
|
19910
|
+
if (it.base && !byHead.has(it.base))
|
|
19911
|
+
tally.set(it.base, (tally.get(it.base) ?? 0) + 1);
|
|
19912
|
+
let best = 0;
|
|
19913
|
+
for (const [b, n] of tally)
|
|
19914
|
+
if (n > best)
|
|
19915
|
+
[trunk, best] = [b, n];
|
|
19916
|
+
}
|
|
19917
|
+
const parentOf = items.map((it, i) => {
|
|
19918
|
+
const p = it.base === undefined ? undefined : byHead.get(it.base);
|
|
19919
|
+
return p === undefined || p === i ? null : p;
|
|
19920
|
+
});
|
|
19921
|
+
items.forEach((_, i) => {
|
|
19922
|
+
const seen = new Set;
|
|
19923
|
+
for (let n = i;n !== null; n = parentOf[n]) {
|
|
19924
|
+
if (seen.has(n)) {
|
|
19925
|
+
const loop = [n];
|
|
19926
|
+
for (let k = parentOf[n];k !== n && k !== null; k = parentOf[k])
|
|
19927
|
+
loop.push(k);
|
|
19928
|
+
parentOf[Math.min(...loop)] = null;
|
|
19929
|
+
break;
|
|
19930
|
+
}
|
|
19931
|
+
seen.add(n);
|
|
19932
|
+
}
|
|
19933
|
+
});
|
|
19934
|
+
const kids = items.map(() => []);
|
|
19935
|
+
const roots = [];
|
|
19936
|
+
parentOf.forEach((p, i) => (p === null ? roots : kids[p]).push(i));
|
|
19937
|
+
const rows = [];
|
|
19938
|
+
const walk = (list, depth, rails) => list.forEach((i, n) => {
|
|
19939
|
+
const it = items[i];
|
|
19940
|
+
const last = n === list.length - 1;
|
|
19941
|
+
const off = parentOf[i] === null && it.base && it.base !== trunk ? it.base : undefined;
|
|
19942
|
+
rows.push({
|
|
19943
|
+
item: it,
|
|
19944
|
+
index: i,
|
|
19945
|
+
depth,
|
|
19946
|
+
parent: parentOf[i],
|
|
19947
|
+
last,
|
|
19948
|
+
rails,
|
|
19949
|
+
children: kids[i].length,
|
|
19950
|
+
...off ? { offBase: off } : {}
|
|
19951
|
+
});
|
|
19952
|
+
walk(kids[i], depth + 1, [...rails, !last]);
|
|
19953
|
+
});
|
|
19954
|
+
walk(roots, 0, []);
|
|
19955
|
+
return { trunk, rows, flat: !trunk && rows.every((r) => r.depth === 0 && !r.children) };
|
|
19956
|
+
}
|
|
19957
|
+
var isNum = (c) => typeof c === "number" || typeof c === "object" && typeof c.text === "number";
|
|
19958
|
+
function fillCell(c) {
|
|
19959
|
+
if (typeof c !== "object")
|
|
19960
|
+
return { text: String(c) };
|
|
19961
|
+
return { ...c, text: String(c.text) };
|
|
19962
|
+
}
|
|
19963
|
+
function fillTable(m) {
|
|
19964
|
+
const wrapped = !m.columns.some((c) => c.key === "cells");
|
|
19965
|
+
const raw = m.rows.map((r) => {
|
|
19966
|
+
const keys = Object.keys(r);
|
|
19967
|
+
const cells = r.cells;
|
|
19968
|
+
return wrapped && keys.length === 1 && keys[0] === "cells" && cells && typeof cells === "object" ? cells : r;
|
|
19969
|
+
});
|
|
19970
|
+
const columns = m.columns.map((c) => {
|
|
19971
|
+
const present = raw.map((r) => r[c.key]).filter((v) => v !== undefined && v !== "");
|
|
19972
|
+
const numeric = present.length > 0 && present.every(isNum);
|
|
19973
|
+
return { ...c, align: c.align ?? (numeric ? "right" : "left") };
|
|
19974
|
+
});
|
|
19975
|
+
const rows = raw.map((r) => {
|
|
19976
|
+
const out = {};
|
|
19977
|
+
for (const [k, v] of Object.entries(r))
|
|
19978
|
+
if (v !== undefined && v !== null)
|
|
19979
|
+
out[k] = fillCell(v);
|
|
19980
|
+
return out;
|
|
19981
|
+
});
|
|
19982
|
+
return { kind: "table", columns, rows, ...m.caption ? { caption: m.caption } : {} };
|
|
19983
|
+
}
|
|
19984
|
+
function fillChapters(chapters) {
|
|
19985
|
+
const id = uniqueIds();
|
|
19986
|
+
const starts = chapters.map((c) => c.start).sort((a, b) => a - b);
|
|
19987
|
+
return chapters.map((c) => {
|
|
19988
|
+
const next = starts.find((s) => s > c.start);
|
|
19989
|
+
const end = c.end ?? (c.duration !== undefined ? Math.round((c.start + c.duration) * 1000) / 1000 : next);
|
|
19990
|
+
return {
|
|
19991
|
+
id: id(c.id ?? slug(c.label)),
|
|
19992
|
+
label: c.label,
|
|
19993
|
+
start: c.start,
|
|
19994
|
+
...end !== undefined && end > c.start ? { end } : {}
|
|
19995
|
+
};
|
|
19996
|
+
});
|
|
19997
|
+
}
|
|
19998
|
+
function chapterAt(chapters, time) {
|
|
19999
|
+
let at = -1;
|
|
20000
|
+
chapters.forEach((c, i) => {
|
|
20001
|
+
if (time >= c.start && time < (c.end ?? Infinity) && (at < 0 || c.start >= chapters[at].start))
|
|
20002
|
+
at = i;
|
|
20003
|
+
});
|
|
20004
|
+
return at;
|
|
20005
|
+
}
|
|
20006
|
+
function clock(seconds) {
|
|
20007
|
+
const t = Math.max(0, Math.floor(seconds));
|
|
20008
|
+
const h = Math.floor(t / 3600);
|
|
20009
|
+
const m = Math.floor(t % 3600 / 60);
|
|
20010
|
+
const s = String(t % 60).padStart(2, "0");
|
|
20011
|
+
return h ? `${h}:${String(m).padStart(2, "0")}:${s}` : `${m}:${s}`;
|
|
20012
|
+
}
|
|
20013
|
+
function findStep(steps, ref) {
|
|
20014
|
+
if (ref === undefined)
|
|
20015
|
+
return;
|
|
20016
|
+
const r = ref.trim().replace(/^#/, "");
|
|
20017
|
+
return steps.find((s) => s.key === r || s.id === r || s.number === r);
|
|
20018
|
+
}
|
|
19268
20019
|
function linkLabel(href) {
|
|
19269
20020
|
const pr = /\/(?:pull|issues)\/(\d+)/.exec(href);
|
|
19270
20021
|
if (pr)
|
|
@@ -19321,7 +20072,7 @@ function normalizeWalkthrough(doc) {
|
|
|
19321
20072
|
label: k,
|
|
19322
20073
|
value
|
|
19323
20074
|
}));
|
|
19324
|
-
const stepRef = (ref) =>
|
|
20075
|
+
const stepRef = (ref) => findStep(steps, ref)?.key ?? null;
|
|
19325
20076
|
const feed = sortFeed([
|
|
19326
20077
|
...(doc.updates ?? []).map((u) => ({ kind: "update", at: parseAt(u.at), text: u.text, step: stepRef(u.step) })),
|
|
19327
20078
|
...(doc.qa ?? []).map((x) => ({
|
|
@@ -19488,6 +20239,8 @@ function describeMedia(m) {
|
|
|
19488
20239
|
parts.push(`${m.kind} at ${formatTime(m.time)}`);
|
|
19489
20240
|
else
|
|
19490
20241
|
parts.push(m.kind);
|
|
20242
|
+
if (m.chapter)
|
|
20243
|
+
parts[0] += ` in “${m.chapter}”`;
|
|
19491
20244
|
if (m.loop)
|
|
19492
20245
|
parts.push(`loop ${formatTime(m.loop.start)}–${formatTime(m.loop.end)}`);
|
|
19493
20246
|
if (m.zoom)
|
|
@@ -19647,6 +20400,388 @@ function inlineMarkdown(text) {
|
|
|
19647
20400
|
return s.replace(/\u0000(\d+)\u0000/g, (_, i) => `<code>${escapeHtml(codes[Number(i)])}</code>`);
|
|
19648
20401
|
}
|
|
19649
20402
|
|
|
20403
|
+
// src/ui/walkthrough-blocks.ts
|
|
20404
|
+
var el = (doc, tag, cls) => {
|
|
20405
|
+
const n = doc.createElement(tag);
|
|
20406
|
+
if (cls)
|
|
20407
|
+
n.className = cls;
|
|
20408
|
+
return n;
|
|
20409
|
+
};
|
|
20410
|
+
var text = (doc, tag, cls, value) => {
|
|
20411
|
+
const n = el(doc, tag, cls);
|
|
20412
|
+
n.textContent = value;
|
|
20413
|
+
return n;
|
|
20414
|
+
};
|
|
20415
|
+
var prose = (doc, tag, cls, value) => {
|
|
20416
|
+
const n = el(doc, tag, cls);
|
|
20417
|
+
n.innerHTML = inlineMarkdown(value);
|
|
20418
|
+
return n;
|
|
20419
|
+
};
|
|
20420
|
+
var link = (doc, cls, label, href) => {
|
|
20421
|
+
if (!href)
|
|
20422
|
+
return text(doc, "span", cls, label);
|
|
20423
|
+
const a = text(doc, "a", cls, label);
|
|
20424
|
+
a.href = href;
|
|
20425
|
+
a.target = "_blank";
|
|
20426
|
+
a.rel = "noreferrer";
|
|
20427
|
+
return a;
|
|
20428
|
+
};
|
|
20429
|
+
function stepButton(doc, ref, steps) {
|
|
20430
|
+
const s = ref && steps?.find(ref);
|
|
20431
|
+
if (!s || !steps)
|
|
20432
|
+
return null;
|
|
20433
|
+
const b = text(doc, "button", "knobs-wt-steplink", s.number);
|
|
20434
|
+
b.type = "button";
|
|
20435
|
+
b.title = `Go to ${s.number} ${s.title}`;
|
|
20436
|
+
b.setAttribute("aria-label", b.title);
|
|
20437
|
+
b.addEventListener("click", (e) => {
|
|
20438
|
+
e.stopPropagation();
|
|
20439
|
+
steps.go(s.key);
|
|
20440
|
+
});
|
|
20441
|
+
return b;
|
|
20442
|
+
}
|
|
20443
|
+
var tags = (doc, list) => {
|
|
20444
|
+
if (!list?.length)
|
|
20445
|
+
return null;
|
|
20446
|
+
const row = el(doc, "div", "knobs-wt-tags");
|
|
20447
|
+
for (const e of list)
|
|
20448
|
+
row.append(link(doc, "knobs-wt-tag", e.label, e.href));
|
|
20449
|
+
return row;
|
|
20450
|
+
};
|
|
20451
|
+
function mountHistory(host, m, steps) {
|
|
20452
|
+
const doc = host.ownerDocument;
|
|
20453
|
+
const root = el(doc, "div", "knobs-wt-history");
|
|
20454
|
+
const longest = Math.max(6, ...m.groups.flatMap((g) => g.events.flatMap((e) => [e.date.length, e.age?.length ?? 0])));
|
|
20455
|
+
root.style.setProperty("--kw-hw", `${Math.min(16, longest + 1)}ch`);
|
|
20456
|
+
for (const g of m.groups) {
|
|
20457
|
+
const group = el(doc, "section", "knobs-wt-hgroup");
|
|
20458
|
+
if (g.heading || g.summary) {
|
|
20459
|
+
const head = el(doc, "header", "knobs-wt-hhead");
|
|
20460
|
+
if (g.heading)
|
|
20461
|
+
head.append(text(doc, "h4", "knobs-wt-hheading", g.heading));
|
|
20462
|
+
if (g.summary)
|
|
20463
|
+
head.append(text(doc, "span", "knobs-wt-hsum", g.summary));
|
|
20464
|
+
group.append(head);
|
|
20465
|
+
}
|
|
20466
|
+
const list = el(doc, "ol", "knobs-wt-hlist");
|
|
20467
|
+
for (const e of g.events)
|
|
20468
|
+
list.append(historyEvent(doc, e, steps));
|
|
20469
|
+
group.append(list);
|
|
20470
|
+
root.append(group);
|
|
20471
|
+
}
|
|
20472
|
+
host.append(root);
|
|
20473
|
+
return { dispose: () => root.remove(), context: () => ({ kind: "history" }) };
|
|
20474
|
+
}
|
|
20475
|
+
function historyEvent(doc, e, steps) {
|
|
20476
|
+
const li = el(doc, "li", "knobs-wt-hev");
|
|
20477
|
+
if (e.tone)
|
|
20478
|
+
li.dataset.tone = e.tone;
|
|
20479
|
+
if (e.type)
|
|
20480
|
+
li.dataset.type = e.type;
|
|
20481
|
+
if (e.provenance)
|
|
20482
|
+
li.dataset.provenance = e.provenance;
|
|
20483
|
+
const when = el(doc, "div", "knobs-wt-hwhen");
|
|
20484
|
+
when.append(text(doc, "time", "knobs-wt-hdate", e.date));
|
|
20485
|
+
if (e.age)
|
|
20486
|
+
when.append(text(doc, "span", "knobs-wt-hage", e.age));
|
|
20487
|
+
const spine = el(doc, "span", "knobs-wt-hspine");
|
|
20488
|
+
spine.setAttribute("aria-hidden", "true");
|
|
20489
|
+
spine.append(el(doc, "span", "knobs-wt-hdot"));
|
|
20490
|
+
const main = el(doc, "div", "knobs-wt-hmain");
|
|
20491
|
+
const title = el(doc, "div", "knobs-wt-htitle");
|
|
20492
|
+
title.append(text(doc, "span", "knobs-wt-htext", e.title));
|
|
20493
|
+
if (e.provenance || e.source) {
|
|
20494
|
+
const p = text(doc, "span", "knobs-wt-prov", [e.provenance, e.source].filter(Boolean).join(" · "));
|
|
20495
|
+
if (e.provenance === "inferred")
|
|
20496
|
+
p.title = "Inferred from behaviour, not seen in the record";
|
|
20497
|
+
else if (e.provenance === "verified")
|
|
20498
|
+
p.title = "Seen in the record";
|
|
20499
|
+
title.append(p);
|
|
20500
|
+
}
|
|
20501
|
+
const go = stepButton(doc, e.step, steps);
|
|
20502
|
+
if (go)
|
|
20503
|
+
title.append(go);
|
|
20504
|
+
main.append(title);
|
|
20505
|
+
if (e.detail)
|
|
20506
|
+
main.append(prose(doc, "p", "knobs-wt-hdetail", e.detail));
|
|
20507
|
+
const t = tags(doc, e.evidence);
|
|
20508
|
+
if (t)
|
|
20509
|
+
main.append(t);
|
|
20510
|
+
li.append(when, spine, main);
|
|
20511
|
+
return li;
|
|
20512
|
+
}
|
|
20513
|
+
function mountStack(host, m, steps) {
|
|
20514
|
+
const doc = host.ownerDocument;
|
|
20515
|
+
const layout = stackLayout(m);
|
|
20516
|
+
const root = el(doc, "div", "knobs-wt-stack");
|
|
20517
|
+
root.toggleAttribute("data-flat", layout.flat);
|
|
20518
|
+
const lane = (kind) => {
|
|
20519
|
+
const s = el(doc, "span", "knobs-wt-lane");
|
|
20520
|
+
s.dataset.rail = kind;
|
|
20521
|
+
return s;
|
|
20522
|
+
};
|
|
20523
|
+
const node = (down) => {
|
|
20524
|
+
const s = lane("node");
|
|
20525
|
+
s.toggleAttribute("data-down", down);
|
|
20526
|
+
return s;
|
|
20527
|
+
};
|
|
20528
|
+
const from = layout.trunk ? 0 : 1;
|
|
20529
|
+
if (layout.trunk && layout.rows.length) {
|
|
20530
|
+
const row = el(doc, "div", "knobs-wt-srow");
|
|
20531
|
+
row.dataset.trunk = "";
|
|
20532
|
+
const lanes = el(doc, "span", "knobs-wt-lanes");
|
|
20533
|
+
lanes.setAttribute("aria-hidden", "true");
|
|
20534
|
+
lanes.append(node(true));
|
|
20535
|
+
row.append(lanes, text(doc, "span", "knobs-wt-trunk", layout.trunk));
|
|
20536
|
+
root.append(row);
|
|
20537
|
+
}
|
|
20538
|
+
for (const r of layout.rows) {
|
|
20539
|
+
const it = r.item;
|
|
20540
|
+
const row = el(doc, "div", "knobs-wt-srow");
|
|
20541
|
+
if (!layout.flat) {
|
|
20542
|
+
const lanes = el(doc, "span", "knobs-wt-lanes");
|
|
20543
|
+
lanes.setAttribute("aria-hidden", "true");
|
|
20544
|
+
for (let k = from;k < r.depth; k++)
|
|
20545
|
+
lanes.append(lane(r.rails[k] ? "pass" : "none"));
|
|
20546
|
+
if (r.depth >= from)
|
|
20547
|
+
lanes.append(lane(r.last ? "end" : "tee"));
|
|
20548
|
+
lanes.append(node(r.children > 0));
|
|
20549
|
+
row.append(lanes);
|
|
20550
|
+
}
|
|
20551
|
+
const main = el(doc, "div", "knobs-wt-smain");
|
|
20552
|
+
const line = el(doc, "div", "knobs-wt-sline");
|
|
20553
|
+
if (it.order)
|
|
20554
|
+
line.append(text(doc, "span", "knobs-wt-sord", it.order));
|
|
20555
|
+
line.append(link(doc, "knobs-wt-spr", it.pr, it.href));
|
|
20556
|
+
const target = it.step ? steps?.find(it.step) : null;
|
|
20557
|
+
if (target && steps) {
|
|
20558
|
+
const title = text(doc, "button", "knobs-wt-stitle", it.title);
|
|
20559
|
+
title.type = "button";
|
|
20560
|
+
title.title = `Go to ${target.number} ${target.title}`;
|
|
20561
|
+
title.addEventListener("click", (e) => {
|
|
20562
|
+
e.stopPropagation();
|
|
20563
|
+
steps.go(target.key);
|
|
20564
|
+
});
|
|
20565
|
+
line.append(title);
|
|
20566
|
+
const go = stepButton(doc, it.step, steps);
|
|
20567
|
+
if (go)
|
|
20568
|
+
line.append(go);
|
|
20569
|
+
row.dataset.go = "";
|
|
20570
|
+
row.addEventListener("click", (e) => {
|
|
20571
|
+
const t = e.target;
|
|
20572
|
+
if (t?.closest("a,button"))
|
|
20573
|
+
return;
|
|
20574
|
+
if (doc.getSelection()?.toString())
|
|
20575
|
+
return;
|
|
20576
|
+
steps.go(target.key);
|
|
20577
|
+
});
|
|
20578
|
+
} else
|
|
20579
|
+
line.append(text(doc, "span", "knobs-wt-stitle", it.title));
|
|
20580
|
+
main.append(line);
|
|
20581
|
+
if (it.summary)
|
|
20582
|
+
main.append(prose(doc, "p", "knobs-wt-ssummary", it.summary));
|
|
20583
|
+
const meta = el(doc, "div", "knobs-wt-smeta");
|
|
20584
|
+
if (it.head) {
|
|
20585
|
+
const branch = r.offBase ? `${it.head} → ${r.offBase}` : it.head;
|
|
20586
|
+
meta.append(text(doc, "span", "knobs-wt-sbranch", branch));
|
|
20587
|
+
}
|
|
20588
|
+
if (it.delta !== undefined)
|
|
20589
|
+
meta.append(delta(doc, it.delta));
|
|
20590
|
+
if (meta.childNodes.length)
|
|
20591
|
+
main.append(meta);
|
|
20592
|
+
row.append(main);
|
|
20593
|
+
if (it.status) {
|
|
20594
|
+
const chip = text(doc, "span", "knobs-wt-chip knobs-wt-sstatus", it.status);
|
|
20595
|
+
chip.dataset.tone = it.tone ?? "dim";
|
|
20596
|
+
row.append(chip);
|
|
20597
|
+
}
|
|
20598
|
+
root.append(row);
|
|
20599
|
+
}
|
|
20600
|
+
host.append(root);
|
|
20601
|
+
return { dispose: () => root.remove(), context: () => ({ kind: "stack" }) };
|
|
20602
|
+
}
|
|
20603
|
+
function delta(doc, d) {
|
|
20604
|
+
const s = el(doc, "span", "knobs-wt-delta");
|
|
20605
|
+
if (typeof d === "string") {
|
|
20606
|
+
s.textContent = d;
|
|
20607
|
+
return s;
|
|
20608
|
+
}
|
|
20609
|
+
const n = (v) => v.toLocaleString("en-US");
|
|
20610
|
+
s.append(text(doc, "span", "knobs-wt-add", `+${n(d.add)}`));
|
|
20611
|
+
s.append(text(doc, "span", "knobs-wt-del", `−${n(d.del)}`));
|
|
20612
|
+
s.setAttribute("aria-label", `${d.add} added, ${d.del} removed`);
|
|
20613
|
+
return s;
|
|
20614
|
+
}
|
|
20615
|
+
function mountTable(host, m, steps) {
|
|
20616
|
+
const doc = host.ownerDocument;
|
|
20617
|
+
const win = doc.defaultView;
|
|
20618
|
+
const scroller = el(doc, "div", "knobs-wt-table");
|
|
20619
|
+
const table = el(doc, "table");
|
|
20620
|
+
if (m.caption)
|
|
20621
|
+
table.append(text(doc, "caption", "", m.caption));
|
|
20622
|
+
const head = el(doc, "thead");
|
|
20623
|
+
const hr = el(doc, "tr");
|
|
20624
|
+
for (const c of m.columns) {
|
|
20625
|
+
const th = text(doc, "th", "", c.label);
|
|
20626
|
+
th.scope = "col";
|
|
20627
|
+
th.dataset.align = c.align;
|
|
20628
|
+
hr.append(th);
|
|
20629
|
+
}
|
|
20630
|
+
head.append(hr);
|
|
20631
|
+
const body = el(doc, "tbody");
|
|
20632
|
+
for (const r of m.rows) {
|
|
20633
|
+
const tr = el(doc, "tr");
|
|
20634
|
+
for (const c of m.columns) {
|
|
20635
|
+
const td = el(doc, "td");
|
|
20636
|
+
td.dataset.align = c.align;
|
|
20637
|
+
if (c.mono)
|
|
20638
|
+
td.dataset.mono = "";
|
|
20639
|
+
const cell = r[c.key];
|
|
20640
|
+
if (cell) {
|
|
20641
|
+
if (cell.text.length > 40 && !c.mono && !cell.code)
|
|
20642
|
+
td.dataset.long = "";
|
|
20643
|
+
let content;
|
|
20644
|
+
if (cell.badge) {
|
|
20645
|
+
content = link(doc, "knobs-wt-chip", cell.text, cell.href);
|
|
20646
|
+
content.dataset.tone = cell.badge;
|
|
20647
|
+
} else {
|
|
20648
|
+
const inner = cell.code ? text(doc, "code", "", cell.text) : null;
|
|
20649
|
+
if (cell.href) {
|
|
20650
|
+
content = link(doc, "knobs-wt-clink", inner ? "" : cell.text, cell.href);
|
|
20651
|
+
if (inner)
|
|
20652
|
+
content.append(inner);
|
|
20653
|
+
} else
|
|
20654
|
+
content = inner ?? text(doc, "span", "", cell.text);
|
|
20655
|
+
if (cell.tone) {
|
|
20656
|
+
content.classList.add("knobs-wt-ctone");
|
|
20657
|
+
content.dataset.tone = cell.tone;
|
|
20658
|
+
}
|
|
20659
|
+
}
|
|
20660
|
+
td.append(content);
|
|
20661
|
+
const go = stepButton(doc, cell.step, steps);
|
|
20662
|
+
if (go)
|
|
20663
|
+
td.append(go);
|
|
20664
|
+
}
|
|
20665
|
+
tr.append(td);
|
|
20666
|
+
}
|
|
20667
|
+
body.append(tr);
|
|
20668
|
+
}
|
|
20669
|
+
table.append(head, body);
|
|
20670
|
+
scroller.append(table);
|
|
20671
|
+
host.append(scroller);
|
|
20672
|
+
const measure = () => {
|
|
20673
|
+
scroller.style.setProperty("--kw-thead", `${head.offsetHeight}px`);
|
|
20674
|
+
const scrolls = scroller.scrollWidth > scroller.clientWidth + 1 || scroller.scrollHeight > scroller.clientHeight + 1;
|
|
20675
|
+
if (scrolls) {
|
|
20676
|
+
scroller.tabIndex = 0;
|
|
20677
|
+
scroller.setAttribute("role", "region");
|
|
20678
|
+
scroller.setAttribute("aria-label", m.caption ?? "Table");
|
|
20679
|
+
} else {
|
|
20680
|
+
scroller.removeAttribute("tabindex");
|
|
20681
|
+
scroller.removeAttribute("role");
|
|
20682
|
+
scroller.removeAttribute("aria-label");
|
|
20683
|
+
}
|
|
20684
|
+
};
|
|
20685
|
+
const ro = win && "ResizeObserver" in win ? new win.ResizeObserver(measure) : null;
|
|
20686
|
+
ro?.observe(scroller);
|
|
20687
|
+
measure();
|
|
20688
|
+
return {
|
|
20689
|
+
dispose: () => {
|
|
20690
|
+
ro?.disconnect();
|
|
20691
|
+
scroller.remove();
|
|
20692
|
+
},
|
|
20693
|
+
context: () => ({ kind: "table" })
|
|
20694
|
+
};
|
|
20695
|
+
}
|
|
20696
|
+
var grid2 = (v) => Math.round(v * 64) / 64;
|
|
20697
|
+
function chapterPills(host, chapters, io) {
|
|
20698
|
+
const doc = host.ownerDocument;
|
|
20699
|
+
const win = doc.defaultView;
|
|
20700
|
+
const bar = el(doc, "div", "knobs-wt-chapters");
|
|
20701
|
+
bar.setAttribute("role", "group");
|
|
20702
|
+
bar.setAttribute("aria-label", "Chapters");
|
|
20703
|
+
const mark = el(doc, "span", "knobs-wt-chapter-mark");
|
|
20704
|
+
mark.setAttribute("aria-hidden", "true");
|
|
20705
|
+
bar.append(mark);
|
|
20706
|
+
const pills = chapters.map((c) => {
|
|
20707
|
+
const b = el(doc, "button", "knobs-wt-chapter");
|
|
20708
|
+
b.type = "button";
|
|
20709
|
+
b.append(text(doc, "span", "knobs-wt-chapter-time", clock(c.start)));
|
|
20710
|
+
b.append(text(doc, "span", "knobs-wt-chapter-label", c.label));
|
|
20711
|
+
b.addEventListener("click", () => {
|
|
20712
|
+
io.onEvent?.({ what: "seek", time: Math.round(c.start * 100) / 100, detail: c.label });
|
|
20713
|
+
io.seek(c.start);
|
|
20714
|
+
sync();
|
|
20715
|
+
});
|
|
20716
|
+
bar.append(b);
|
|
20717
|
+
return b;
|
|
20718
|
+
});
|
|
20719
|
+
host.append(bar);
|
|
20720
|
+
const x = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
|
|
20721
|
+
const y = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
|
|
20722
|
+
const w = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
|
|
20723
|
+
const draw = () => {
|
|
20724
|
+
const left = grid2(x.x);
|
|
20725
|
+
mark.style.transform = `translate(${left}px,${grid2(y.x)}px)`;
|
|
20726
|
+
mark.style.width = `${grid2(x.x + w.x) - left}px`;
|
|
20727
|
+
};
|
|
20728
|
+
const loop = frameLoop(win, (dt) => {
|
|
20729
|
+
x.step(dt);
|
|
20730
|
+
y.step(dt);
|
|
20731
|
+
w.step(dt);
|
|
20732
|
+
draw();
|
|
20733
|
+
return !x.resting || !y.resting || !w.resting;
|
|
20734
|
+
});
|
|
20735
|
+
let at = -2;
|
|
20736
|
+
let shown = false;
|
|
20737
|
+
const place = (snap) => {
|
|
20738
|
+
const p = pills[at];
|
|
20739
|
+
if (!p) {
|
|
20740
|
+
mark.removeAttribute("data-on");
|
|
20741
|
+
pills.forEach((b) => b.removeAttribute("aria-current"));
|
|
20742
|
+
shown = false;
|
|
20743
|
+
return;
|
|
20744
|
+
}
|
|
20745
|
+
pills.forEach((b, i) => i === at ? b.setAttribute("aria-current", "true") : b.removeAttribute("aria-current"));
|
|
20746
|
+
const to = [p.offsetLeft, p.offsetTop, p.offsetWidth];
|
|
20747
|
+
if (snap || !shown || reducedMotion(win)) {
|
|
20748
|
+
x.snap(to[0]);
|
|
20749
|
+
y.snap(to[1]);
|
|
20750
|
+
w.snap(to[2]);
|
|
20751
|
+
draw();
|
|
20752
|
+
} else {
|
|
20753
|
+
x.set(to[0]);
|
|
20754
|
+
y.set(to[1]);
|
|
20755
|
+
w.set(to[2]);
|
|
20756
|
+
loop.wake();
|
|
20757
|
+
}
|
|
20758
|
+
mark.style.height = `${p.offsetHeight}px`;
|
|
20759
|
+
mark.dataset.on = "";
|
|
20760
|
+
shown = true;
|
|
20761
|
+
};
|
|
20762
|
+
const sync = () => {
|
|
20763
|
+
const next = chapterAt(chapters, io.now());
|
|
20764
|
+
if (next === at)
|
|
20765
|
+
return;
|
|
20766
|
+
at = next;
|
|
20767
|
+
place(false);
|
|
20768
|
+
};
|
|
20769
|
+
sync();
|
|
20770
|
+
const ro = "ResizeObserver" in win ? new win.ResizeObserver(() => place(true)) : null;
|
|
20771
|
+
ro?.observe(bar);
|
|
20772
|
+
return {
|
|
20773
|
+
sync,
|
|
20774
|
+
get current() {
|
|
20775
|
+
return chapters[at] ?? null;
|
|
20776
|
+
},
|
|
20777
|
+
dispose() {
|
|
20778
|
+
loop.stop();
|
|
20779
|
+
ro?.disconnect();
|
|
20780
|
+
bar.remove();
|
|
20781
|
+
}
|
|
20782
|
+
};
|
|
20783
|
+
}
|
|
20784
|
+
|
|
19650
20785
|
// src/ui/walkthrough-media.ts
|
|
19651
20786
|
function injectWalkthroughStyles(doc) {
|
|
19652
20787
|
if (doc.querySelector("style[data-knobs-ui-walkthrough]"))
|
|
@@ -19779,8 +20914,25 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19779
20914
|
onEvent?.({ what: "loop", detail: lastLoop = loopKey });
|
|
19780
20915
|
}
|
|
19781
20916
|
});
|
|
20917
|
+
const chapters = m.chapters?.length ? m.chapters : null;
|
|
20918
|
+
if (chapters)
|
|
20919
|
+
c.timeline?.update({
|
|
20920
|
+
markers: chapters.map((ch) => ({ id: ch.id, time: ch.start, label: ch.label }))
|
|
20921
|
+
});
|
|
20922
|
+
const pills = chapters ? chapterPills(host, chapters, {
|
|
20923
|
+
now: () => c.time,
|
|
20924
|
+
seek: (t) => {
|
|
20925
|
+
c.seek(t);
|
|
20926
|
+
c.play();
|
|
20927
|
+
},
|
|
20928
|
+
onEvent
|
|
20929
|
+
}) : null;
|
|
19782
20930
|
const unlisten = c.timeline?.listen({
|
|
19783
|
-
onSeek: (time, source) =>
|
|
20931
|
+
onSeek: (time, source) => {
|
|
20932
|
+
pills?.sync();
|
|
20933
|
+
if (source !== "play")
|
|
20934
|
+
onEvent?.({ what: "seek", time: round2(time) });
|
|
20935
|
+
},
|
|
19784
20936
|
onPlay: (on) => onEvent?.({ what: on ? "play" : "pause", time: round2(c.time) })
|
|
19785
20937
|
});
|
|
19786
20938
|
requestAnimationFrame(() => requestAnimationFrame(() => fit.dataset.settled = ""));
|
|
@@ -19802,12 +20954,14 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
19802
20954
|
duration: round2(c.duration),
|
|
19803
20955
|
playing: c.playing,
|
|
19804
20956
|
mode: c.mode,
|
|
20957
|
+
...pills?.current ? { chapter: pills.current.label } : {},
|
|
19805
20958
|
...loop ? { loop: { start: round2(loop.start), end: round2(loop.end) } } : {},
|
|
19806
20959
|
...zoom ? { zoom: { u: round2(zoom.u), v: round2(zoom.v), w: round2(zoom.w), h: round2(zoom.h) } } : {}
|
|
19807
20960
|
};
|
|
19808
20961
|
},
|
|
19809
20962
|
dispose: () => {
|
|
19810
20963
|
unlisten?.();
|
|
20964
|
+
pills?.dispose();
|
|
19811
20965
|
c.dispose();
|
|
19812
20966
|
fit.remove();
|
|
19813
20967
|
}
|
|
@@ -19831,6 +20985,15 @@ function mountVideo(host, m, onEvent) {
|
|
|
19831
20985
|
v.poster = m.poster;
|
|
19832
20986
|
if (!aspect)
|
|
19833
20987
|
v.addEventListener("loadedmetadata", () => v.videoWidth && frame.style.setProperty("--kw-aspect", String(v.videoWidth / v.videoHeight)), { once: true });
|
|
20988
|
+
if (m.captions) {
|
|
20989
|
+
const track = doc.createElement("track");
|
|
20990
|
+
track.kind = "captions";
|
|
20991
|
+
track.label = "Captions";
|
|
20992
|
+
track.srclang = doc.documentElement.lang || "en";
|
|
20993
|
+
track.default = true;
|
|
20994
|
+
track.src = m.captions;
|
|
20995
|
+
v.append(track);
|
|
20996
|
+
}
|
|
19834
20997
|
v.src = m.src;
|
|
19835
20998
|
v.addEventListener("play", () => onEvent?.({ what: "play", time: round2(v.currentTime) }));
|
|
19836
20999
|
v.addEventListener("pause", () => onEvent?.({ what: "pause", time: round2(v.currentTime) }));
|
|
@@ -19840,6 +21003,18 @@ function mountVideo(host, m, onEvent) {
|
|
|
19840
21003
|
});
|
|
19841
21004
|
frame.append(v);
|
|
19842
21005
|
host.append(frame);
|
|
21006
|
+
const pills = m.chapters?.length ? chapterPills(host, m.chapters, {
|
|
21007
|
+
now: () => v.currentTime,
|
|
21008
|
+
seek: (t) => {
|
|
21009
|
+
v.currentTime = t;
|
|
21010
|
+
v.play().catch(() => {});
|
|
21011
|
+
},
|
|
21012
|
+
onEvent
|
|
21013
|
+
}) : null;
|
|
21014
|
+
const sync = () => pills?.sync();
|
|
21015
|
+
if (pills)
|
|
21016
|
+
for (const type of ["timeupdate", "seeking", "seeked"])
|
|
21017
|
+
v.addEventListener(type, sync);
|
|
19843
21018
|
return {
|
|
19844
21019
|
pause: () => v.pause(),
|
|
19845
21020
|
seekBy: (d) => {
|
|
@@ -19849,9 +21024,11 @@ function mountVideo(host, m, onEvent) {
|
|
|
19849
21024
|
kind: "video",
|
|
19850
21025
|
time: round2(v.currentTime),
|
|
19851
21026
|
...Number.isFinite(v.duration) ? { duration: round2(v.duration) } : {},
|
|
19852
|
-
playing: !v.paused
|
|
21027
|
+
playing: !v.paused,
|
|
21028
|
+
...pills?.current ? { chapter: pills.current.label } : {}
|
|
19853
21029
|
}),
|
|
19854
21030
|
dispose: () => {
|
|
21031
|
+
pills?.dispose();
|
|
19855
21032
|
v.pause();
|
|
19856
21033
|
v.removeAttribute("src");
|
|
19857
21034
|
v.load();
|
|
@@ -20075,6 +21252,12 @@ function mediaSlot(container, options) {
|
|
|
20075
21252
|
case "callout":
|
|
20076
21253
|
case "text":
|
|
20077
21254
|
return done(mountProse(host, m));
|
|
21255
|
+
case "history":
|
|
21256
|
+
return done(mountHistory(host, m, options.steps));
|
|
21257
|
+
case "stack":
|
|
21258
|
+
return done(mountStack(host, m, options.steps));
|
|
21259
|
+
case "table":
|
|
21260
|
+
return done(mountTable(host, m, options.steps));
|
|
20078
21261
|
case "mount": {
|
|
20079
21262
|
const cleanup = m.mount(host);
|
|
20080
21263
|
return done({ dispose: () => cleanup ? cleanup() : host.textContent = "" });
|
|
@@ -20129,14 +21312,14 @@ function mediaSlot(container, options) {
|
|
|
20129
21312
|
var CHECK3 = svg('<path d="m3.5 8.4 3 3 6-6.6"/>');
|
|
20130
21313
|
|
|
20131
21314
|
// src/ui/walkthrough.ts
|
|
20132
|
-
var
|
|
21315
|
+
var el2 = (doc, tag, cls) => {
|
|
20133
21316
|
const n = doc.createElement(tag);
|
|
20134
21317
|
if (cls)
|
|
20135
21318
|
n.className = cls;
|
|
20136
21319
|
return n;
|
|
20137
21320
|
};
|
|
20138
|
-
var
|
|
20139
|
-
const n =
|
|
21321
|
+
var prose2 = (doc, tag, cls, text) => {
|
|
21322
|
+
const n = el2(doc, tag, cls);
|
|
20140
21323
|
n.innerHTML = inlineMarkdown(text);
|
|
20141
21324
|
return n;
|
|
20142
21325
|
};
|
|
@@ -20144,7 +21327,7 @@ var lookOf2 = (o) => ({
|
|
|
20144
21327
|
...o.preset ? { preset: o.preset } : {},
|
|
20145
21328
|
...o.theme ? { theme: o.theme } : {}
|
|
20146
21329
|
});
|
|
20147
|
-
var
|
|
21330
|
+
var grid3 = (v) => Math.round(v * 64) / 64;
|
|
20148
21331
|
var ICON = {
|
|
20149
21332
|
full: svg('<path d="M3 6.5V3h3.5M13 6.5V3H9.5M3 9.5V13h3.5M13 9.5V13H9.5"/>'),
|
|
20150
21333
|
exit: svg('<path d="M6.5 3v3.5H3M9.5 3v3.5H13M6.5 13V9.5H3M9.5 13V9.5H13"/>'),
|
|
@@ -20170,7 +21353,7 @@ function tasteTest(container, options) {
|
|
|
20170
21353
|
suggested: o.suggested === true
|
|
20171
21354
|
});
|
|
20172
21355
|
let picked = options.picked ?? null;
|
|
20173
|
-
const root =
|
|
21356
|
+
const root = el2(doc, "div", "knobs-wt-options");
|
|
20174
21357
|
root.setAttribute("role", "radiogroup");
|
|
20175
21358
|
root.setAttribute("aria-label", "Options");
|
|
20176
21359
|
root.style.setProperty("--kw-n", String(Math.min(list.length, 3)));
|
|
@@ -20178,28 +21361,28 @@ function tasteTest(container, options) {
|
|
|
20178
21361
|
applyLook2(root, options);
|
|
20179
21362
|
const slots = [];
|
|
20180
21363
|
const tiles = list.map((o, i) => {
|
|
20181
|
-
const tile =
|
|
20182
|
-
const pick =
|
|
21364
|
+
const tile = el2(doc, "div", "knobs-wt-option");
|
|
21365
|
+
const pick = el2(doc, "button", "knobs-wt-pick");
|
|
20183
21366
|
pick.type = "button";
|
|
20184
21367
|
pick.setAttribute("role", "radio");
|
|
20185
21368
|
if (i < 9)
|
|
20186
21369
|
pick.setAttribute("aria-keyshortcuts", String(i + 1));
|
|
20187
|
-
const letter =
|
|
21370
|
+
const letter = el2(doc, "span", "knobs-wt-letter");
|
|
20188
21371
|
letter.textContent = o.letter;
|
|
20189
|
-
const name =
|
|
21372
|
+
const name = el2(doc, "span", "knobs-wt-olabel");
|
|
20190
21373
|
name.textContent = o.label;
|
|
20191
21374
|
pick.append(letter, name);
|
|
20192
21375
|
if (o.suggested) {
|
|
20193
|
-
const s =
|
|
21376
|
+
const s = el2(doc, "span", "knobs-wt-suggested");
|
|
20194
21377
|
s.textContent = "Suggested";
|
|
20195
21378
|
pick.append(s);
|
|
20196
21379
|
}
|
|
20197
21380
|
if (i < 9) {
|
|
20198
|
-
const key =
|
|
21381
|
+
const key = el2(doc, "span", "knobs-wt-key");
|
|
20199
21382
|
key.textContent = String(i + 1);
|
|
20200
21383
|
pick.append(key);
|
|
20201
21384
|
}
|
|
20202
|
-
const check =
|
|
21385
|
+
const check = el2(doc, "span", "knobs-wt-check");
|
|
20203
21386
|
check.innerHTML = CHECK3;
|
|
20204
21387
|
pick.append(check);
|
|
20205
21388
|
pick.addEventListener("click", () => {
|
|
@@ -20212,10 +21395,11 @@ function tasteTest(container, options) {
|
|
|
20212
21395
|
slots.push(mediaSlot(tile, {
|
|
20213
21396
|
...look,
|
|
20214
21397
|
media: o.media,
|
|
20215
|
-
...options.lazy === false ? { lazy: false } : {}
|
|
21398
|
+
...options.lazy === false ? { lazy: false } : {},
|
|
21399
|
+
...options.steps ? { steps: options.steps } : {}
|
|
20216
21400
|
}));
|
|
20217
21401
|
if (o.caption)
|
|
20218
|
-
tile.append(
|
|
21402
|
+
tile.append(prose2(doc, "p", "knobs-wt-ocap", o.caption));
|
|
20219
21403
|
root.append(tile);
|
|
20220
21404
|
return { tile, pick };
|
|
20221
21405
|
});
|
|
@@ -20263,7 +21447,7 @@ function walkthrough(container, options) {
|
|
|
20263
21447
|
injectWalkthroughStyles(doc);
|
|
20264
21448
|
const look = lookOf2(options);
|
|
20265
21449
|
const storage = options.storage === undefined ? browserStorage() : options.storage;
|
|
20266
|
-
const root =
|
|
21450
|
+
const root = el2(doc, "div", "knobs-walkthrough");
|
|
20267
21451
|
applyLook2(root, options);
|
|
20268
21452
|
container.append(root);
|
|
20269
21453
|
let page = normalizeWalkthrough(options.doc);
|
|
@@ -20323,8 +21507,8 @@ function walkthrough(container, options) {
|
|
|
20323
21507
|
if (mark) {
|
|
20324
21508
|
pillY.step(dt);
|
|
20325
21509
|
pillH.step(dt);
|
|
20326
|
-
mark.style.transform = `translateY(${
|
|
20327
|
-
mark.style.height = `${
|
|
21510
|
+
mark.style.transform = `translateY(${grid3(pillY.x)}px)`;
|
|
21511
|
+
mark.style.height = `${grid3(pillY.x + pillH.x) - grid3(pillY.x)}px`;
|
|
20328
21512
|
more ||= !pillY.resting || !pillH.resting;
|
|
20329
21513
|
}
|
|
20330
21514
|
if (scroll.to && scroll.el) {
|
|
@@ -20459,6 +21643,10 @@ function walkthrough(container, options) {
|
|
|
20459
21643
|
scrollToCard(v);
|
|
20460
21644
|
v.card.focus({ preventScroll: true });
|
|
20461
21645
|
};
|
|
21646
|
+
const stepLinks = {
|
|
21647
|
+
find: (ref) => findStep(page.steps, ref) ?? null,
|
|
21648
|
+
go: (key) => go(key)
|
|
21649
|
+
};
|
|
20462
21650
|
const slotsOf = (v) => v ? [...v.slots, ...v.taste?.slots ?? []] : [];
|
|
20463
21651
|
const mediaContext = (v) => {
|
|
20464
21652
|
const slots = slotsOf(v);
|
|
@@ -20473,17 +21661,17 @@ function walkthrough(container, options) {
|
|
|
20473
21661
|
return true;
|
|
20474
21662
|
return false;
|
|
20475
21663
|
};
|
|
20476
|
-
const scrim =
|
|
21664
|
+
const scrim = el2(doc, "div", "knobs-wt-scrim");
|
|
20477
21665
|
scrim.addEventListener("click", () => setFull(false));
|
|
20478
21666
|
const fullRect = () => ({ x: 12, y: 12, w: win.innerWidth - 24, h: win.innerHeight - 24 });
|
|
20479
21667
|
const placeFull = (f) => {
|
|
20480
21668
|
const a = f.hold.getBoundingClientRect();
|
|
20481
21669
|
const b = fullRect();
|
|
20482
21670
|
const k = reduced() ? Math.round(f.p.target) : f.p.x;
|
|
20483
|
-
const left =
|
|
20484
|
-
const top =
|
|
20485
|
-
const right =
|
|
20486
|
-
const bottom =
|
|
21671
|
+
const left = grid3(a.left + (b.x - a.left) * k);
|
|
21672
|
+
const top = grid3(a.top + (b.y - a.top) * k);
|
|
21673
|
+
const right = grid3(a.right + (b.x + b.w - a.right) * k);
|
|
21674
|
+
const bottom = grid3(a.bottom + (b.y + b.h - a.bottom) * k);
|
|
20487
21675
|
const s = f.v.card.style;
|
|
20488
21676
|
s.left = `${left}px`;
|
|
20489
21677
|
s.top = `${top}px`;
|
|
@@ -20518,7 +21706,7 @@ function walkthrough(container, options) {
|
|
|
20518
21706
|
}
|
|
20519
21707
|
if (!full) {
|
|
20520
21708
|
const r = v.card.getBoundingClientRect();
|
|
20521
|
-
const hold =
|
|
21709
|
+
const hold = el2(doc, "div", "knobs-wt-hold-card");
|
|
20522
21710
|
hold.style.height = `${r.height}px`;
|
|
20523
21711
|
v.card.before(hold);
|
|
20524
21712
|
v.card.dataset.full = "";
|
|
@@ -20666,7 +21854,7 @@ function walkthrough(container, options) {
|
|
|
20666
21854
|
toast("Couldn't reach the microphone", { tone: "warn", look, doc });
|
|
20667
21855
|
}
|
|
20668
21856
|
};
|
|
20669
|
-
const backPill =
|
|
21857
|
+
const backPill = el2(doc, "button", "knobs-wt-back");
|
|
20670
21858
|
backPill.type = "button";
|
|
20671
21859
|
let backTo = null;
|
|
20672
21860
|
let backTimer = 0;
|
|
@@ -20691,16 +21879,16 @@ function walkthrough(container, options) {
|
|
|
20691
21879
|
backTimer = win.setTimeout(hideBack, BACK_MS);
|
|
20692
21880
|
};
|
|
20693
21881
|
const bar = (() => {
|
|
20694
|
-
const dock =
|
|
20695
|
-
const box =
|
|
20696
|
-
const on =
|
|
20697
|
-
const row =
|
|
20698
|
-
const input =
|
|
21882
|
+
const dock = el2(doc, "div", "knobs-wt-dock");
|
|
21883
|
+
const box = el2(doc, "div", "knobs-wt-notebar");
|
|
21884
|
+
const on = el2(doc, "div", "knobs-wt-on");
|
|
21885
|
+
const row = el2(doc, "div", "knobs-wt-row");
|
|
21886
|
+
const input = el2(doc, "textarea", "knobs-wt-input");
|
|
20699
21887
|
input.rows = 1;
|
|
20700
21888
|
input.setAttribute("aria-label", "Note");
|
|
20701
|
-
const attach =
|
|
21889
|
+
const attach = el2(doc, "div", "knobs-wt-attach");
|
|
20702
21890
|
const btn = (icon, label, run) => {
|
|
20703
|
-
const b =
|
|
21891
|
+
const b = el2(doc, "button", "knobs-wt-tool");
|
|
20704
21892
|
b.type = "button";
|
|
20705
21893
|
b.innerHTML = icon;
|
|
20706
21894
|
b.setAttribute("aria-label", label);
|
|
@@ -20711,7 +21899,7 @@ function walkthrough(container, options) {
|
|
|
20711
21899
|
const fullBtn = btn(ICON.full, "Full window · f", () => setFull(!full));
|
|
20712
21900
|
const penBtn = btn(ICON.pen, "Draw on the media", () => setInk(!ink.on));
|
|
20713
21901
|
const micBtn = btn(ICON.mic, "Record a voice note", () => void toggleVoice());
|
|
20714
|
-
const micTime =
|
|
21902
|
+
const micTime = el2(doc, "span", "knobs-wt-rec");
|
|
20715
21903
|
micBtn.append(micTime);
|
|
20716
21904
|
const sendBtn = btn(ICON.send, "Send · Enter", () => send());
|
|
20717
21905
|
sendBtn.dataset.send = "";
|
|
@@ -20737,9 +21925,9 @@ function walkthrough(container, options) {
|
|
|
20737
21925
|
}
|
|
20738
21926
|
});
|
|
20739
21927
|
const chip = (text, clear) => {
|
|
20740
|
-
const c =
|
|
21928
|
+
const c = el2(doc, "span", "knobs-wt-chip-a");
|
|
20741
21929
|
c.append(text);
|
|
20742
|
-
const x =
|
|
21930
|
+
const x = el2(doc, "button", "knobs-wt-x");
|
|
20743
21931
|
x.type = "button";
|
|
20744
21932
|
x.innerHTML = ICON.close;
|
|
20745
21933
|
x.setAttribute("aria-label", `Remove ${text}`);
|
|
@@ -20852,9 +22040,9 @@ function walkthrough(container, options) {
|
|
|
20852
22040
|
}
|
|
20853
22041
|
};
|
|
20854
22042
|
const renderNote = (v, n, entering = false) => {
|
|
20855
|
-
const row =
|
|
22043
|
+
const row = el2(doc, "div", "knobs-wt-n");
|
|
20856
22044
|
row.dataset.id = n.id;
|
|
20857
|
-
const meta =
|
|
22045
|
+
const meta = el2(doc, "span", "knobs-wt-n-meta");
|
|
20858
22046
|
const m = n.context.media;
|
|
20859
22047
|
meta.textContent = m?.time !== undefined ? formatTime(m.time) : m?.file ? m.file.split("/").pop() : "·";
|
|
20860
22048
|
if (m?.time !== undefined) {
|
|
@@ -20870,11 +22058,11 @@ function walkthrough(container, options) {
|
|
|
20870
22058
|
});
|
|
20871
22059
|
}
|
|
20872
22060
|
row.append(meta);
|
|
20873
|
-
const body =
|
|
22061
|
+
const body = el2(doc, "div", "knobs-wt-n-body");
|
|
20874
22062
|
if (n.text)
|
|
20875
|
-
body.append(
|
|
22063
|
+
body.append(prose2(doc, "p", "knobs-wt-n-text", n.text));
|
|
20876
22064
|
if (n.voice) {
|
|
20877
|
-
const a =
|
|
22065
|
+
const a = el2(doc, "audio", "knobs-wt-n-voice");
|
|
20878
22066
|
a.controls = true;
|
|
20879
22067
|
a.preload = "none";
|
|
20880
22068
|
a.src = n.voice.url;
|
|
@@ -20894,7 +22082,7 @@ function walkthrough(container, options) {
|
|
|
20894
22082
|
body.append(thumb);
|
|
20895
22083
|
}
|
|
20896
22084
|
row.append(body);
|
|
20897
|
-
const x =
|
|
22085
|
+
const x = el2(doc, "button", "knobs-wt-x");
|
|
20898
22086
|
x.type = "button";
|
|
20899
22087
|
x.innerHTML = ICON.close;
|
|
20900
22088
|
x.setAttribute("aria-label", "Delete this note");
|
|
@@ -20948,22 +22136,22 @@ function walkthrough(container, options) {
|
|
|
20948
22136
|
pillPlaced = false;
|
|
20949
22137
|
root.textContent = "";
|
|
20950
22138
|
root.removeAttribute("data-settled");
|
|
20951
|
-
const layout =
|
|
20952
|
-
const main =
|
|
20953
|
-
const head =
|
|
20954
|
-
const h1 =
|
|
22139
|
+
const layout = el2(doc, "div", "knobs-wt-layout");
|
|
22140
|
+
const main = el2(doc, "div", "knobs-wt-main");
|
|
22141
|
+
const head = el2(doc, "header", "knobs-wt-head");
|
|
22142
|
+
const h1 = el2(doc, "h1", "knobs-wt-title");
|
|
20955
22143
|
h1.textContent = page.title;
|
|
20956
22144
|
head.append(h1);
|
|
20957
22145
|
if (page.summary)
|
|
20958
|
-
head.append(
|
|
22146
|
+
head.append(prose2(doc, "p", "knobs-wt-summary", page.summary));
|
|
20959
22147
|
if (page.meta.length) {
|
|
20960
|
-
const meta =
|
|
22148
|
+
const meta = el2(doc, "div", "knobs-wt-meta");
|
|
20961
22149
|
for (const m of page.meta) {
|
|
20962
|
-
const s =
|
|
22150
|
+
const s = el2(doc, "span");
|
|
20963
22151
|
s.append(m.label);
|
|
20964
|
-
const b =
|
|
22152
|
+
const b = el2(doc, "b");
|
|
20965
22153
|
if (m.href) {
|
|
20966
|
-
const a =
|
|
22154
|
+
const a = el2(doc, "a");
|
|
20967
22155
|
a.href = m.href;
|
|
20968
22156
|
a.target = "_blank";
|
|
20969
22157
|
a.rel = "noreferrer";
|
|
@@ -20978,27 +22166,27 @@ function walkthrough(container, options) {
|
|
|
20978
22166
|
}
|
|
20979
22167
|
main.append(head);
|
|
20980
22168
|
if (page.feed.length) {
|
|
20981
|
-
const feed =
|
|
22169
|
+
const feed = el2(doc, "section", "knobs-wt-feed");
|
|
20982
22170
|
feed.setAttribute("aria-label", "Latest");
|
|
20983
|
-
const list =
|
|
22171
|
+
const list = el2(doc, "ol", "knobs-wt-feed-list knobs-scroll-fade");
|
|
20984
22172
|
const now = Date.now();
|
|
20985
22173
|
for (const f of page.feed) {
|
|
20986
|
-
const li =
|
|
22174
|
+
const li = el2(doc, "li", "knobs-wt-fi");
|
|
20987
22175
|
li.dataset.kind = f.kind;
|
|
20988
|
-
const when =
|
|
22176
|
+
const when = el2(doc, "span", "knobs-wt-fi-when");
|
|
20989
22177
|
when.textContent = f.at === null ? "" : ago(f.at, now);
|
|
20990
22178
|
if (f.at !== null)
|
|
20991
22179
|
when.title = new Date(f.at).toLocaleString();
|
|
20992
|
-
const kind =
|
|
22180
|
+
const kind = el2(doc, "span", "knobs-wt-fi-kind");
|
|
20993
22181
|
kind.textContent = f.kind === "qa" ? "Answer" : "Update";
|
|
20994
|
-
const body =
|
|
22182
|
+
const body = el2(doc, "div", "knobs-wt-fi-body");
|
|
20995
22183
|
if (f.q)
|
|
20996
|
-
body.append(
|
|
20997
|
-
body.append(
|
|
22184
|
+
body.append(prose2(doc, "p", "knobs-wt-fi-q", f.q));
|
|
22185
|
+
body.append(prose2(doc, "p", "knobs-wt-fi-text", f.text));
|
|
20998
22186
|
li.append(when, kind, body);
|
|
20999
22187
|
if (f.step) {
|
|
21000
22188
|
const target = f.step;
|
|
21001
|
-
const link =
|
|
22189
|
+
const link = el2(doc, "button", "knobs-wt-fi-step");
|
|
21002
22190
|
link.type = "button";
|
|
21003
22191
|
link.textContent = page.steps.find((s) => s.key === target)?.number ?? "";
|
|
21004
22192
|
link.title = "Go to the step";
|
|
@@ -21013,33 +22201,33 @@ function walkthrough(container, options) {
|
|
|
21013
22201
|
let rail = null;
|
|
21014
22202
|
count = fill = tocEl = mark = null;
|
|
21015
22203
|
if (options.rail !== false) {
|
|
21016
|
-
rail =
|
|
22204
|
+
rail = el2(doc, "aside", "knobs-wt-rail");
|
|
21017
22205
|
rail.setAttribute("aria-label", "Progress");
|
|
21018
|
-
const progress =
|
|
21019
|
-
count =
|
|
21020
|
-
const meter =
|
|
21021
|
-
fill =
|
|
22206
|
+
const progress = el2(doc, "div", "knobs-wt-progress");
|
|
22207
|
+
count = el2(doc, "div", "knobs-wt-count");
|
|
22208
|
+
const meter = el2(doc, "div", "knobs-wt-bar");
|
|
22209
|
+
fill = el2(doc, "div", "knobs-wt-fill");
|
|
21022
22210
|
meter.append(fill);
|
|
21023
22211
|
progress.append(count, meter);
|
|
21024
|
-
tocEl =
|
|
22212
|
+
tocEl = el2(doc, "nav", "knobs-wt-toc");
|
|
21025
22213
|
tocEl.setAttribute("aria-label", "Contents");
|
|
21026
|
-
mark =
|
|
22214
|
+
mark = el2(doc, "div", "knobs-wt-toc-mark");
|
|
21027
22215
|
tocEl.append(mark);
|
|
21028
22216
|
rail.append(progress, tocEl, actions(true));
|
|
21029
22217
|
if (options.keys !== false) {
|
|
21030
|
-
const keys =
|
|
22218
|
+
const keys = el2(doc, "div", "knobs-wt-keys");
|
|
21031
22219
|
keys.innerHTML = "<kbd>j</kbd> <kbd>k</kbd> steps · <kbd>←</kbd> <kbd>→</kbd> seek · <kbd>f</kbd> full · <kbd>n</kbd> note" + (page.steps.some((s) => s.ask === "pick") ? " · <kbd>1</kbd>–<kbd>9</kbd> pick" : "");
|
|
21032
22220
|
rail.append(keys);
|
|
21033
22221
|
}
|
|
21034
22222
|
}
|
|
21035
22223
|
for (const section of page.sections) {
|
|
21036
|
-
const sec =
|
|
22224
|
+
const sec = el2(doc, "section", "knobs-wt-section");
|
|
21037
22225
|
sec.id = `wt-${section.id}`;
|
|
21038
|
-
const sh =
|
|
22226
|
+
const sh = el2(doc, "header", "knobs-wt-shead");
|
|
21039
22227
|
if (section.link || section.status) {
|
|
21040
|
-
const kicker =
|
|
22228
|
+
const kicker = el2(doc, "div", "knobs-wt-kicker");
|
|
21041
22229
|
if (section.link) {
|
|
21042
|
-
const a =
|
|
22230
|
+
const a = el2(doc, "a");
|
|
21043
22231
|
a.href = section.link.href;
|
|
21044
22232
|
a.target = "_blank";
|
|
21045
22233
|
a.rel = "noreferrer";
|
|
@@ -21048,21 +22236,21 @@ function walkthrough(container, options) {
|
|
|
21048
22236
|
}
|
|
21049
22237
|
if (section.status) {
|
|
21050
22238
|
const s = statusLook(section.status);
|
|
21051
|
-
const chip =
|
|
22239
|
+
const chip = el2(doc, "span", "knobs-wt-chip");
|
|
21052
22240
|
chip.dataset.tone = s.tone;
|
|
21053
22241
|
chip.textContent = s.label;
|
|
21054
22242
|
kicker.append(chip);
|
|
21055
22243
|
}
|
|
21056
22244
|
sh.append(kicker);
|
|
21057
22245
|
}
|
|
21058
|
-
const h2 =
|
|
22246
|
+
const h2 = el2(doc, "h2", "knobs-wt-stitle");
|
|
21059
22247
|
h2.textContent = section.title;
|
|
21060
22248
|
sh.append(h2);
|
|
21061
22249
|
if (section.summary)
|
|
21062
|
-
sh.append(
|
|
22250
|
+
sh.append(prose2(doc, "p", "knobs-wt-ssum", section.summary));
|
|
21063
22251
|
sec.append(sh);
|
|
21064
22252
|
if (tocEl) {
|
|
21065
|
-
const t =
|
|
22253
|
+
const t = el2(doc, "div", "knobs-wt-toc-section");
|
|
21066
22254
|
t.textContent = section.link ? `${section.link.label} ${section.title}` : section.title;
|
|
21067
22255
|
t.title = section.title;
|
|
21068
22256
|
tocEl.append(t);
|
|
@@ -21095,9 +22283,9 @@ function walkthrough(container, options) {
|
|
|
21095
22283
|
}));
|
|
21096
22284
|
};
|
|
21097
22285
|
const actions = (rail) => {
|
|
21098
|
-
const row =
|
|
22286
|
+
const row = el2(doc, "div", "knobs-wt-actions");
|
|
21099
22287
|
const btn = (text, run, primary = false) => {
|
|
21100
|
-
const b =
|
|
22288
|
+
const b = el2(doc, "button", "knobs-wt-btn");
|
|
21101
22289
|
b.type = "button";
|
|
21102
22290
|
b.textContent = text;
|
|
21103
22291
|
if (primary)
|
|
@@ -21135,43 +22323,44 @@ function walkthrough(container, options) {
|
|
|
21135
22323
|
}
|
|
21136
22324
|
};
|
|
21137
22325
|
const stepView = (step) => {
|
|
21138
|
-
const card =
|
|
22326
|
+
const card = el2(doc, "article", "knobs-wt-step knobs-surface");
|
|
21139
22327
|
card.id = `wt-${step.key.replace("/", "--")}`;
|
|
21140
22328
|
card.tabIndex = -1;
|
|
21141
22329
|
card.dataset.key = step.key;
|
|
21142
22330
|
card.setAttribute("aria-label", `${step.number} ${step.title}`);
|
|
21143
|
-
const head =
|
|
21144
|
-
const num =
|
|
22331
|
+
const head = el2(doc, "header", "knobs-wt-step-head");
|
|
22332
|
+
const num = el2(doc, "span", "knobs-wt-num");
|
|
21145
22333
|
num.textContent = step.number;
|
|
21146
|
-
const h3 =
|
|
22334
|
+
const h3 = el2(doc, "h3", "knobs-wt-stepTitle");
|
|
21147
22335
|
h3.textContent = step.title;
|
|
21148
22336
|
head.append(num, h3);
|
|
21149
22337
|
card.append(head);
|
|
21150
22338
|
if (step.caption)
|
|
21151
|
-
card.append(
|
|
22339
|
+
card.append(prose2(doc, "p", "knobs-wt-caption", step.caption));
|
|
21152
22340
|
const slots = [];
|
|
21153
22341
|
const onEvent = (e) => record({ ...e, step: step.key });
|
|
21154
22342
|
let media = null;
|
|
21155
22343
|
if (step.media.length) {
|
|
21156
|
-
media =
|
|
22344
|
+
media = el2(doc, "div", "knobs-wt-media");
|
|
21157
22345
|
media.style.setProperty("--kw-n", String(Math.min(step.media.length, 3)));
|
|
21158
22346
|
for (const m of step.media)
|
|
21159
|
-
slots.push(mediaSlot(media, { ...look, media: m, onEvent }));
|
|
22347
|
+
slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks }));
|
|
21160
22348
|
card.append(media);
|
|
21161
22349
|
}
|
|
21162
22350
|
let taste = null;
|
|
21163
22351
|
if (step.question)
|
|
21164
|
-
card.append(
|
|
22352
|
+
card.append(prose2(doc, "p", "knobs-wt-question", step.question));
|
|
21165
22353
|
if (step.ask === "pick") {
|
|
21166
22354
|
taste = tasteTest(card, {
|
|
21167
22355
|
...look,
|
|
21168
22356
|
options: step.options,
|
|
21169
22357
|
picked: state.steps[step.key]?.pick ?? null,
|
|
21170
|
-
onPick: (id) => pick(step.key, id)
|
|
22358
|
+
onPick: (id) => pick(step.key, id),
|
|
22359
|
+
steps: stepLinks
|
|
21171
22360
|
});
|
|
21172
22361
|
media ??= taste.element;
|
|
21173
22362
|
}
|
|
21174
|
-
const notes =
|
|
22363
|
+
const notes = el2(doc, "div", "knobs-wt-notes");
|
|
21175
22364
|
card.append(notes);
|
|
21176
22365
|
card.addEventListener("focusin", () => {
|
|
21177
22366
|
if (scroll.to)
|
|
@@ -21182,10 +22371,10 @@ function walkthrough(container, options) {
|
|
|
21182
22371
|
let toc = null;
|
|
21183
22372
|
let glyph = null;
|
|
21184
22373
|
if (tocEl) {
|
|
21185
|
-
toc =
|
|
22374
|
+
toc = el2(doc, "button", "knobs-wt-toc-step");
|
|
21186
22375
|
toc.type = "button";
|
|
21187
|
-
glyph =
|
|
21188
|
-
const label =
|
|
22376
|
+
glyph = el2(doc, "span", "knobs-wt-toc-glyph");
|
|
22377
|
+
const label = el2(doc, "span", "knobs-wt-toc-label");
|
|
21189
22378
|
label.textContent = step.title;
|
|
21190
22379
|
toc.title = `${step.number} ${step.title}`;
|
|
21191
22380
|
toc.append(glyph, label);
|
|
@@ -21346,9 +22535,11 @@ function walkthrough(container, options) {
|
|
|
21346
22535
|
return api;
|
|
21347
22536
|
}
|
|
21348
22537
|
export {
|
|
22538
|
+
CATCH_UP,
|
|
21349
22539
|
COLLAPSE_PAST,
|
|
21350
22540
|
COMPARE_FFMPEG,
|
|
21351
22541
|
FIT_MAX,
|
|
22542
|
+
ICON_NAMES,
|
|
21352
22543
|
activity,
|
|
21353
22544
|
adjacentMarker,
|
|
21354
22545
|
advance,
|
|
@@ -21365,12 +22556,14 @@ export {
|
|
|
21365
22556
|
commandsFrom,
|
|
21366
22557
|
compare,
|
|
21367
22558
|
compareLayout,
|
|
22559
|
+
compareModes,
|
|
21368
22560
|
comparePaths,
|
|
21369
22561
|
compareStrip,
|
|
21370
22562
|
compareText,
|
|
21371
22563
|
confirm,
|
|
21372
22564
|
contextMenu,
|
|
21373
22565
|
countChanges,
|
|
22566
|
+
currentMarker,
|
|
21374
22567
|
diffActivity,
|
|
21375
22568
|
diffBar,
|
|
21376
22569
|
diffCounts,
|
|
@@ -21395,7 +22588,9 @@ export {
|
|
|
21395
22588
|
frameOf,
|
|
21396
22589
|
frameTime,
|
|
21397
22590
|
fuzzy,
|
|
22591
|
+
gripTop,
|
|
21398
22592
|
hints,
|
|
22593
|
+
icon,
|
|
21399
22594
|
inTriangle,
|
|
21400
22595
|
indexActivity,
|
|
21401
22596
|
indexTree,
|
|
@@ -21414,7 +22609,9 @@ export {
|
|
|
21414
22609
|
mediaKind,
|
|
21415
22610
|
mediaSlot,
|
|
21416
22611
|
menu,
|
|
22612
|
+
modeForKey,
|
|
21417
22613
|
motionBlur,
|
|
22614
|
+
moveHead,
|
|
21418
22615
|
navigate,
|
|
21419
22616
|
navigateActivity,
|
|
21420
22617
|
normalizeWalkthrough,
|
|
@@ -21423,9 +22620,14 @@ export {
|
|
|
21423
22620
|
pageHints,
|
|
21424
22621
|
parseLayout,
|
|
21425
22622
|
parsePatch,
|
|
22623
|
+
pillBoxOf,
|
|
22624
|
+
pillProgress,
|
|
22625
|
+
pillRadius,
|
|
22626
|
+
pillSpring,
|
|
21426
22627
|
place,
|
|
21427
22628
|
plainAccess,
|
|
21428
22629
|
planBento,
|
|
22630
|
+
planPill,
|
|
21429
22631
|
popover,
|
|
21430
22632
|
railSegments,
|
|
21431
22633
|
rank,
|
|
@@ -21437,14 +22639,17 @@ export {
|
|
|
21437
22639
|
rulerTicks,
|
|
21438
22640
|
safeTriangle,
|
|
21439
22641
|
scrubTime,
|
|
22642
|
+
seamLabels,
|
|
21440
22643
|
seekTime,
|
|
21441
22644
|
seeked,
|
|
21442
22645
|
select,
|
|
22646
|
+
selectionPill,
|
|
21443
22647
|
showsSelection,
|
|
21444
22648
|
softClamp,
|
|
21445
22649
|
solve2 as solveSplit,
|
|
21446
22650
|
split,
|
|
21447
22651
|
splitPath,
|
|
22652
|
+
stackLayout,
|
|
21448
22653
|
stackRows,
|
|
21449
22654
|
stepFile,
|
|
21450
22655
|
stepItem,
|