@kitlangton/knobs 0.4.0-next.2 → 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 +15 -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 +4 -2
- 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/ui-react.js +696 -270
- package/dist/ui-solid.js +696 -270
- package/dist/ui.js +716 -270
- package/package.json +1 -1
package/dist/ui-react.js
CHANGED
|
@@ -571,12 +571,20 @@ var compareCss = `
|
|
|
571
571
|
display:flex;flex-direction:column;gap:6px;width:100%;height:100%;min-height:160px;outline:none;user-select:none;-webkit-user-select:none;
|
|
572
572
|
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
|
|
573
573
|
: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}
|
|
574
|
-
.knobs-cmp-bar{flex:none;display:flex;align-items:center;gap:
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
.knobs-
|
|
579
|
-
.knobs-ui .knobs-cmp-mode
|
|
574
|
+
.knobs-cmp-bar{flex:none;display:flex;align-items:center;gap:var(--_k-gap);height:var(--_k-control-h)}
|
|
575
|
+
/* The modes: one fill, the selection pill on the one picked; a mode isn't brighter for being
|
|
576
|
+
picked: the pill's bright copy is. */
|
|
577
|
+
.knobs-cmp-modes{position:relative;display:inline-flex;height:var(--_k-control-h);border-radius:var(--_k-radius);background:var(--_k-fill)}
|
|
578
|
+
.knobs-cmp-modes[hidden]{display:none}
|
|
579
|
+
.knobs-ui .knobs-cmp-mode{display:inline-flex;align-items:center;height:100%;padding:0 8px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:pointer;white-space:nowrap;
|
|
580
|
+
transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
581
|
+
.knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg)}
|
|
582
|
+
/* Toggles: icon buttons, the accent's wash when on. */
|
|
583
|
+
.knobs-ui .knobs-cmp-toggle{display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:var(--_k-radius);color:var(--_k-fg-3);cursor:pointer;
|
|
584
|
+
transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
|
|
585
|
+
.knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash)}
|
|
586
|
+
.knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
|
|
587
|
+
.knobs-cmp-toggle svg{display:block}
|
|
580
588
|
.knobs-cmp-fill{flex:1}
|
|
581
589
|
.knobs-cmp-toggle[hidden]{display:none}
|
|
582
590
|
/* One surface: on a card (a walkthrough's step, a split's pane, anything .knobs-surface) or marked
|
|
@@ -591,7 +599,8 @@ var compareCss = `
|
|
|
591
599
|
/* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
|
|
592
600
|
.knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
|
|
593
601
|
.knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
|
|
594
|
-
.
|
|
602
|
+
/* Wiping, the grip stands in for the cursor. */
|
|
603
|
+
.knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:none!important}
|
|
595
604
|
.knobs-cmp-sizer{position:absolute;left:0;right:0;top:0;height:0;visibility:hidden;pointer-events:none}
|
|
596
605
|
.knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
|
|
597
606
|
.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}
|
|
@@ -599,18 +608,21 @@ var compareCss = `
|
|
|
599
608
|
/* Glass: what's behind shows through, blurred, on the knobs tokens. */
|
|
600
609
|
.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);
|
|
601
610
|
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)))}
|
|
602
|
-
.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
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
/* The wipe: a hairline down the line with a grip at its middle; a wider strip takes the hand and
|
|
606
|
-
the keys (a slider). It fades with the mode. */
|
|
611
|
+
.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)}
|
|
612
|
+
/* The wipe: a hairline down the line with a raised grip at the pointer's height; a wider strip
|
|
613
|
+
takes the hand and the keys (a slider). It fades with the mode; wiping, line and grip brighten. */
|
|
607
614
|
.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)}
|
|
608
615
|
.knobs-cmp-handle[data-on]{opacity:1;pointer-events:auto;transition:opacity .22s cubic-bezier(.2,.8,.2,1)}
|
|
609
|
-
.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)}
|
|
610
|
-
.knobs-cmp-handle::
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
.knobs-cmp-
|
|
616
|
+
.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)}
|
|
617
|
+
.knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}
|
|
618
|
+
.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);
|
|
619
|
+
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)}
|
|
620
|
+
.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)}
|
|
621
|
+
.knobs-cmp-grip::before{left:6px}
|
|
622
|
+
.knobs-cmp-grip::after{left:9px}
|
|
623
|
+
.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)}
|
|
624
|
+
.knobs-compare[data-dragging] .knobs-cmp-grip::before,.knobs-compare[data-dragging] .knobs-cmp-grip::after{background:var(--_k-fg)}
|
|
625
|
+
.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)}
|
|
614
626
|
/* The split's hairline, down the middle side by side. */
|
|
615
627
|
.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}
|
|
616
628
|
/* Zoomed: how far, and a way back, in the stage's corner; it fades in and out, nothing shifts. */
|
|
@@ -619,12 +631,12 @@ var compareCss = `
|
|
|
619
631
|
opacity:0;scale:.96;pointer-events:none;transition:opacity .16s cubic-bezier(.4,0,1,1),scale .16s cubic-bezier(.4,0,1,1)}
|
|
620
632
|
.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)}
|
|
621
633
|
: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;
|
|
622
|
-
height:20px;padding:0
|
|
634
|
+
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}
|
|
623
635
|
: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)}
|
|
624
636
|
.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}
|
|
625
637
|
.knobs-cmp-note:empty{display:none}
|
|
626
638
|
.knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
|
|
627
|
-
.knobs-cmp-timeline-host{flex:none;
|
|
639
|
+
.knobs-cmp-timeline-host{flex:none;min-height:0}
|
|
628
640
|
/* The frame strip: Before over After per column, a difference bar under them, a dot where it peaks. */
|
|
629
641
|
.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;
|
|
630
642
|
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg)}
|
|
@@ -767,7 +779,7 @@ var init_picker_css = __esm(() => {
|
|
|
767
779
|
.knobs-picker-suggested{flex:none;color:var(--_k-fg-3);font-size:var(--_k-text-meta)}
|
|
768
780
|
.knobs-picker-check{flex:none;display:flex;width:14px;height:14px;color:var(--_k-accent);opacity:0;scale:.6;filter:blur(2px);
|
|
769
781
|
transition:opacity .11s ease-in,scale .11s ease-in,filter .11s ease-in}
|
|
770
|
-
.knobs-picker-check svg{display:block;width:
|
|
782
|
+
.knobs-picker-check svg{display:block;width:12px;height:12px}
|
|
771
783
|
.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}
|
|
772
784
|
.knobs-picker-body{position:relative;overflow:visible}
|
|
773
785
|
.knobs-picker-body[data-moving]{overflow:hidden}
|
|
@@ -871,6 +883,15 @@ var readoutCss = `
|
|
|
871
883
|
@media(prefers-reduced-motion:reduce){.knobs-rd :where(*){transition:none!important}}
|
|
872
884
|
`;
|
|
873
885
|
|
|
886
|
+
// src/ui/selection-pill-css.ts
|
|
887
|
+
var selectionPillCss = `
|
|
888
|
+
.knobs-pick-pill,.knobs-pick-copy{position:absolute;left:0;top:0;width:0;height:0;pointer-events:none;transition:opacity .1s linear}
|
|
889
|
+
.knobs-pick-pill{background:var(--_k-fill-selected);will-change:transform}
|
|
890
|
+
.knobs-pick-copy{overflow:hidden;will-change:transform}
|
|
891
|
+
.knobs-pick-copy-rows{position:absolute;left:0;top:0;width:0;height:0}
|
|
892
|
+
:is(.knobs-pick-pill,.knobs-pick-copy)[data-off]{opacity:0}
|
|
893
|
+
`;
|
|
894
|
+
|
|
874
895
|
// src/tokens/color.ts
|
|
875
896
|
function linearToOklch([r, g, b, a]) {
|
|
876
897
|
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
@@ -2719,11 +2740,9 @@ var css2 = `
|
|
|
2719
2740
|
.knobs-tree:focus .knobs-tree-row[data-active][aria-selected="true"]::before{background:var(--_ku-select-focus)}
|
|
2720
2741
|
.knobs-tree-row[data-drop="into"]::before{opacity:1;background:color-mix(in srgb,var(--_k-accent) 30%,transparent)}
|
|
2721
2742
|
.knobs-tree-row[data-dragged]{opacity:.38}
|
|
2722
|
-
/* glide:
|
|
2723
|
-
.knobs-tree-
|
|
2724
|
-
|
|
2725
|
-
.knobs-tree-cursor[data-on]{opacity:1}
|
|
2726
|
-
.knobs-tree:focus .knobs-tree-cursor{background:var(--_ku-select-focus)}
|
|
2743
|
+
/* glide: the selection pill on the selected row, in the selection's colour (the row's own stays off). */
|
|
2744
|
+
.knobs-tree>.knobs-pick-pill{background:var(--_ku-select)}
|
|
2745
|
+
.knobs-tree:focus>.knobs-pick-pill{background:var(--_ku-select-focus)}
|
|
2727
2746
|
.knobs-tree[data-glide] .knobs-tree-row[aria-selected="true"]::before{opacity:0!important}
|
|
2728
2747
|
.knobs-tree-row>*{position:relative}
|
|
2729
2748
|
.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)}
|
|
@@ -2755,22 +2774,20 @@ var css2 = `
|
|
|
2755
2774
|
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)}
|
|
2756
2775
|
.knobs-tree-empty{padding:10px var(--_k-inset);color:var(--_k-fg-3)}
|
|
2757
2776
|
|
|
2758
|
-
/* Timeline:
|
|
2759
|
-
|
|
2777
|
+
/* Timeline: play beside a track: the ruler's canvas over a lane of markers, ranges as soft bands,
|
|
2778
|
+
and the playhead, a hairline in the accent with a cap. No clock: the time shows under the
|
|
2779
|
+
pointer on the track (the tag), in seconds, while hovering or scrubbing. */
|
|
2760
2780
|
:where(.knobs-timeline,.knobs-timeline *){box-sizing:border-box}
|
|
2761
2781
|
.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;
|
|
2762
2782
|
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
|
|
2763
2783
|
: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}
|
|
2764
|
-
.knobs-tl-transport{flex:none;display:flex;align-items:center
|
|
2784
|
+
.knobs-tl-transport{flex:none;display:flex;align-items:center}
|
|
2765
2785
|
.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);
|
|
2766
2786
|
background:var(--_k-fill-selected);color:var(--_k-fg);cursor:pointer;transition:background .15s,transform .15s var(--_ku-ease)}
|
|
2767
2787
|
.knobs-ui .knobs-tl-play:hover{background:color-mix(in srgb,var(--_k-fg) 16%,transparent)}
|
|
2768
2788
|
.knobs-ui .knobs-tl-play:active{transform:scale(.94)}
|
|
2769
2789
|
.knobs-tl-play svg{width:12px;height:12px}
|
|
2770
2790
|
.knobs-timeline[data-playing] .knobs-tl-play{background:var(--_k-accent);color:var(--_k-bg)}
|
|
2771
|
-
.knobs-tl-time{display:flex;flex-direction:column;line-height:1.2;font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
|
|
2772
|
-
.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}
|
|
2773
|
-
.knobs-tl-sub{font-size:var(--_k-text-meta);color:var(--_k-fg-3);white-space:nowrap}
|
|
2774
2791
|
.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;
|
|
2775
2792
|
transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
2776
2793
|
.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)}
|
|
@@ -2794,7 +2811,8 @@ var css2 = `
|
|
|
2794
2811
|
.knobs-tl-pin{position:absolute;top:13px;left:-5px;width:10px;height:10px;pointer-events:auto;cursor:grab}
|
|
2795
2812
|
.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)}
|
|
2796
2813
|
.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);
|
|
2797
|
-
font-size:var(--_k-text-meta);line-height:17px;white-space:nowrap;
|
|
2814
|
+
font-size:var(--_k-text-meta);line-height:17px;white-space:nowrap;pointer-events:auto;cursor:grab;transition:background .15s,color .15s}
|
|
2815
|
+
.knobs-tl-mtext{display:block;overflow:hidden;white-space:nowrap}
|
|
2798
2816
|
.knobs-tl-marker[data-selected]{--knobs-tl-c:var(--_k-accent)}
|
|
2799
2817
|
.knobs-tl-marker[data-selected] .knobs-tl-mlabel{background:color-mix(in srgb,var(--_k-accent) 24%,var(--_k-fill-selected))}
|
|
2800
2818
|
.knobs-tl-marker[data-selected] .knobs-tl-pin::after{scale:1.2}
|
|
@@ -2815,7 +2833,22 @@ var css2 = `
|
|
|
2815
2833
|
.knobs-timeline[data-scrubbing] .knobs-tl-hover{opacity:0}
|
|
2816
2834
|
@media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
|
|
2817
2835
|
@media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
|
|
2818
|
-
|
|
2836
|
+
.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}
|
|
2837
|
+
.knobs-tl-tag[data-on]{opacity:1}
|
|
2838
|
+
/* The lane (variant: "lane"): one slim track of ticks under a compare or a video; markers are
|
|
2839
|
+
faint chapter ticks, the current one in text colour; nothing to edit. */
|
|
2840
|
+
.knobs-ui.knobs-timeline[data-variant="lane"]{height:auto;min-height:0;padding:0;gap:var(--_k-gap)}
|
|
2841
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-track{height:var(--_k-control-h)}
|
|
2842
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-ruler{height:100%}
|
|
2843
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-lane{top:0}
|
|
2844
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-marker{top:0}
|
|
2845
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-marker::before{top:0;bottom:50%;left:-.75px;width:1.5px;background:var(--_k-fg-3)}
|
|
2846
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-marker[data-current]::before{background:var(--_k-fg)}
|
|
2847
|
+
.knobs-timeline[data-variant="lane"] :is(.knobs-tl-pin,.knobs-tl-mlabel,.knobs-tl-rlabel){display:none}
|
|
2848
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-head::before{top:0}
|
|
2849
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-head::after{content:none}
|
|
2850
|
+
.knobs-timeline[data-variant="lane"] .knobs-tl-tag{top:2px;bottom:auto}
|
|
2851
|
+
`, uiCss = () => aliased(css2 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + selectionPillCss) + rolling_css_default, lookDefaults, tune = (s, m) => {
|
|
2819
2852
|
s.omega = m.omega;
|
|
2820
2853
|
s.zeta = m.zeta;
|
|
2821
2854
|
return s;
|
|
@@ -2973,33 +3006,29 @@ var init_diff_css = __esm(() => {
|
|
|
2973
3006
|
.knobs-diff-fill{flex:1}
|
|
2974
3007
|
.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}
|
|
2975
3008
|
.knobs-diff-nav>span{min-width:3.6em;text-align:center}
|
|
2976
|
-
:where(.knobs-ui) :is(.knobs-diff-btn,.knobs-diff-
|
|
3009
|
+
: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;
|
|
2977
3010
|
margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
2978
|
-
.knobs-ui .knobs-diff-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:
|
|
3011
|
+
.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;
|
|
2979
3012
|
transition:background .15s,color .15s,opacity .15s}
|
|
2980
3013
|
.knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
2981
3014
|
.knobs-ui .knobs-diff-btn[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
|
|
2982
3015
|
.knobs-ui .knobs-diff-btn:disabled{opacity:.35;cursor:default;background:transparent}
|
|
2983
|
-
.knobs-diff-btn svg{width:
|
|
3016
|
+
.knobs-diff-btn svg{width:16px;height:16px}
|
|
2984
3017
|
/* Viewed: a check with its word, as on GitHub; the path dims once it's ticked. */
|
|
2985
3018
|
.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);
|
|
2986
3019
|
font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:background .15s,color .15s}
|
|
2987
3020
|
.knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
2988
3021
|
.knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
|
|
2989
|
-
.knobs-diff-viewed svg{width:
|
|
3022
|
+
.knobs-diff-viewed svg{width:16px;height:16px}
|
|
2990
3023
|
.knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
|
|
2991
3024
|
.knobs-diff-path{transition:opacity .2s}
|
|
2992
3025
|
.knobs-diff[data-viewed]>.knobs-diff-head .knobs-diff-path,.knobs-diff-part[data-viewed] .knobs-diff-path{opacity:.6}
|
|
2993
|
-
/* Split | Unified:
|
|
2994
|
-
|
|
2995
|
-
.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-
|
|
2996
|
-
.knobs-ui .knobs-diff-
|
|
2997
|
-
font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color
|
|
2998
|
-
.knobs-ui .knobs-diff-
|
|
2999
|
-
.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));
|
|
3000
|
-
box-shadow:0 1px 2px rgba(0,0,0,calc(.16 * var(--k-lift,1)));transition:transform var(--_ku-ease-ms) var(--_ku-ease)}
|
|
3001
|
-
.knobs-diff-seg[data-value="unified"]>.knobs-diff-pill{transform:translateX(100%)}
|
|
3002
|
-
.knobs-diff[data-frame="flat"] .knobs-diff-pill{box-shadow:none}
|
|
3026
|
+
/* Split | Unified: the selection pill on the one picked, its bright copy the picked one's text; a
|
|
3027
|
+
choice isn't brighter for being picked. Choices are styled by class, as the copy holds them too. */
|
|
3028
|
+
.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)}
|
|
3029
|
+
.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);
|
|
3030
|
+
font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
3031
|
+
.knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg)}
|
|
3003
3032
|
/* The diff: one layer per file or layout on show, the old fading under the new. */
|
|
3004
3033
|
.knobs-diff-body{position:relative;flex:1;min-height:0}
|
|
3005
3034
|
.knobs-diff-probe{position:absolute;left:0;top:0;width:0;height:var(--kd-line);visibility:hidden;pointer-events:none}
|
|
@@ -3094,7 +3123,7 @@ var init_diff_css = __esm(() => {
|
|
|
3094
3123
|
.knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
|
|
3095
3124
|
.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}
|
|
3096
3125
|
.knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
3097
|
-
.knobs-diff-filter>button svg{width:
|
|
3126
|
+
.knobs-diff-filter>button svg{width:12px;height:12px}
|
|
3098
3127
|
.knobs-diff-list{position:relative;flex:1;min-height:0}
|
|
3099
3128
|
.knobs-diff-files .knobs-diff-counts{font-size:var(--_k-text-meta);align-items:center}
|
|
3100
3129
|
.knobs-diff-files .knobs-diff-counts{gap:5px}
|
|
@@ -3861,6 +3890,283 @@ var init_rgba = __esm(() => {
|
|
|
3861
3890
|
cache = new Map;
|
|
3862
3891
|
});
|
|
3863
3892
|
|
|
3893
|
+
// src/ui/icons.ts
|
|
3894
|
+
function icon(name, options = {}) {
|
|
3895
|
+
const size = options.small ? 12 : 16;
|
|
3896
|
+
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>`;
|
|
3897
|
+
}
|
|
3898
|
+
var PATHS, ICON_NAMES;
|
|
3899
|
+
var init_icons = __esm(() => {
|
|
3900
|
+
PATHS = {
|
|
3901
|
+
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",
|
|
3902
|
+
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",
|
|
3903
|
+
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",
|
|
3904
|
+
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",
|
|
3905
|
+
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",
|
|
3906
|
+
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",
|
|
3907
|
+
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",
|
|
3908
|
+
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",
|
|
3909
|
+
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",
|
|
3910
|
+
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",
|
|
3911
|
+
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",
|
|
3912
|
+
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",
|
|
3913
|
+
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",
|
|
3914
|
+
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",
|
|
3915
|
+
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",
|
|
3916
|
+
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",
|
|
3917
|
+
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",
|
|
3918
|
+
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",
|
|
3919
|
+
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",
|
|
3920
|
+
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",
|
|
3921
|
+
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",
|
|
3922
|
+
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",
|
|
3923
|
+
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",
|
|
3924
|
+
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",
|
|
3925
|
+
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",
|
|
3926
|
+
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",
|
|
3927
|
+
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"
|
|
3928
|
+
};
|
|
3929
|
+
ICON_NAMES = Object.keys(PATHS);
|
|
3930
|
+
});
|
|
3931
|
+
|
|
3932
|
+
// src/ui/selection-pill.ts
|
|
3933
|
+
function pillSpring(distance, base = SPRINGS.default) {
|
|
3934
|
+
return distance > 400 ? { duration: base.duration * 1.4, bounce: base.bounce } : base;
|
|
3935
|
+
}
|
|
3936
|
+
function pillBoxOf(row, container) {
|
|
3937
|
+
return {
|
|
3938
|
+
x: row.left - container.left - container.clientLeft + container.scrollLeft,
|
|
3939
|
+
y: row.top - container.top - container.clientTop + container.scrollTop,
|
|
3940
|
+
w: row.width,
|
|
3941
|
+
h: row.height
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
function pillRadius(from, to, h, k) {
|
|
3945
|
+
const t = Math.abs(to.h - from.h) > 0.5 ? (h - from.h) / (to.h - from.h) : Number.isFinite(k) ? k : 1;
|
|
3946
|
+
return from.r + (to.r - from.r) * Math.min(1, Math.max(0, t));
|
|
3947
|
+
}
|
|
3948
|
+
function pillProgress(from, to, at) {
|
|
3949
|
+
const d = Math.hypot(to.x - from.x, to.y - from.y);
|
|
3950
|
+
if (d < 0.5)
|
|
3951
|
+
return 1;
|
|
3952
|
+
const p = ((at.x - from.x) * (to.x - from.x) + (at.y - from.y) * (to.y - from.y)) / (d * d);
|
|
3953
|
+
return Math.min(1, Math.max(0, p));
|
|
3954
|
+
}
|
|
3955
|
+
function planPill(state, next, flags = {}) {
|
|
3956
|
+
if (!next)
|
|
3957
|
+
return { kind: "hide" };
|
|
3958
|
+
if (!state.shown || !state.at || flags.first || flags.reduced || flags.snap)
|
|
3959
|
+
return { kind: "land", box: next };
|
|
3960
|
+
const t = state.target;
|
|
3961
|
+
if (t && t.x === next.x && t.y === next.y && t.w === next.w && t.h === next.h)
|
|
3962
|
+
return { kind: "stay" };
|
|
3963
|
+
const d = Math.hypot(next.x - state.at.x, next.y - state.at.y);
|
|
3964
|
+
return { kind: "travel", box: next, spring: pillSpring(d) };
|
|
3965
|
+
}
|
|
3966
|
+
function selectionPill(container, options) {
|
|
3967
|
+
const doc = container.ownerDocument;
|
|
3968
|
+
const win = doc.defaultView;
|
|
3969
|
+
injectUiStyles(doc);
|
|
3970
|
+
if (win.getComputedStyle(container).position === "static")
|
|
3971
|
+
container.style.position = "relative";
|
|
3972
|
+
const pill = doc.createElement("i");
|
|
3973
|
+
pill.className = "knobs-pick-pill";
|
|
3974
|
+
pill.setAttribute("aria-hidden", "true");
|
|
3975
|
+
pill.dataset.off = "";
|
|
3976
|
+
const copyOn = options.copy !== false;
|
|
3977
|
+
const copy = doc.createElement("div");
|
|
3978
|
+
copy.className = "knobs-pick-copy";
|
|
3979
|
+
copy.setAttribute("aria-hidden", "true");
|
|
3980
|
+
copy.inert = true;
|
|
3981
|
+
copy.dataset.off = "";
|
|
3982
|
+
const copyRows = doc.createElement("div");
|
|
3983
|
+
copyRows.className = "knobs-pick-copy-rows";
|
|
3984
|
+
copy.append(copyRows);
|
|
3985
|
+
container.prepend(pill);
|
|
3986
|
+
if (copyOn)
|
|
3987
|
+
container.append(copy);
|
|
3988
|
+
const { omega, zeta } = springMotion(SPRINGS.default);
|
|
3989
|
+
const springs = [0, 0, 0, 0].map(() => new Spring(0, omega, zeta, 0.01));
|
|
3990
|
+
const [sx, sy, sw, sh] = springs;
|
|
3991
|
+
let shown = false;
|
|
3992
|
+
let first = true;
|
|
3993
|
+
let target = null;
|
|
3994
|
+
let from = { x: 0, y: 0, w: 0, h: 0 };
|
|
3995
|
+
let fromR = 0;
|
|
3996
|
+
let toR = 0;
|
|
3997
|
+
let rowBoxes = [];
|
|
3998
|
+
const clones = new Map;
|
|
3999
|
+
let disposed = false;
|
|
4000
|
+
const radiusOf = (row) => options.radius?.(row) ?? (Number.parseFloat(win.getComputedStyle(row).borderTopLeftRadius) || 0);
|
|
4001
|
+
const rowsNow = () => options.rows ? [...options.rows()] : [...container.children].filter((n) => n !== pill && n !== copy);
|
|
4002
|
+
const measure = (row) => {
|
|
4003
|
+
const c = container.getBoundingClientRect();
|
|
4004
|
+
const r = row.getBoundingClientRect();
|
|
4005
|
+
return pillBoxOf(r, {
|
|
4006
|
+
left: c.left,
|
|
4007
|
+
top: c.top,
|
|
4008
|
+
scrollLeft: container.scrollLeft,
|
|
4009
|
+
scrollTop: container.scrollTop,
|
|
4010
|
+
clientLeft: container.clientLeft,
|
|
4011
|
+
clientTop: container.clientTop
|
|
4012
|
+
});
|
|
4013
|
+
};
|
|
4014
|
+
const at = () => ({ x: sx.x, y: sy.x, w: sw.x, h: sh.x });
|
|
4015
|
+
const syncCopy = (span) => {
|
|
4016
|
+
if (!copyOn)
|
|
4017
|
+
return;
|
|
4018
|
+
const keep = new Set;
|
|
4019
|
+
for (const { row, box } of rowBoxes) {
|
|
4020
|
+
if (!boxesMeet(box, span))
|
|
4021
|
+
continue;
|
|
4022
|
+
keep.add(row);
|
|
4023
|
+
let n = clones.get(row);
|
|
4024
|
+
if (!n) {
|
|
4025
|
+
n = row.cloneNode(true);
|
|
4026
|
+
n.removeAttribute("id");
|
|
4027
|
+
for (const d of n.querySelectorAll("[id]"))
|
|
4028
|
+
d.removeAttribute("id");
|
|
4029
|
+
clones.set(row, n);
|
|
4030
|
+
copyRows.append(n);
|
|
4031
|
+
}
|
|
4032
|
+
const s = n.style;
|
|
4033
|
+
s.position = "absolute";
|
|
4034
|
+
s.inset = "auto";
|
|
4035
|
+
s.left = `${box.x}px`;
|
|
4036
|
+
s.top = `${box.y}px`;
|
|
4037
|
+
s.width = `${box.w}px`;
|
|
4038
|
+
s.height = `${box.h}px`;
|
|
4039
|
+
s.margin = "0";
|
|
4040
|
+
s.transform = "none";
|
|
4041
|
+
s.clipPath = "";
|
|
4042
|
+
s.opacity = "";
|
|
4043
|
+
s.boxSizing = "border-box";
|
|
4044
|
+
s.color = options.color ?? "var(--_k-fg)";
|
|
4045
|
+
}
|
|
4046
|
+
for (const [row, n] of clones)
|
|
4047
|
+
if (!keep.has(row)) {
|
|
4048
|
+
n.remove();
|
|
4049
|
+
clones.delete(row);
|
|
4050
|
+
}
|
|
4051
|
+
};
|
|
4052
|
+
const paint = () => {
|
|
4053
|
+
const b = at();
|
|
4054
|
+
const x0 = grid(b.x);
|
|
4055
|
+
const y0 = grid(b.y);
|
|
4056
|
+
const w = grid(b.x + b.w) - x0;
|
|
4057
|
+
const h = grid(b.y + b.h) - y0;
|
|
4058
|
+
const r = target ? pillRadius({ h: from.h, r: fromR }, { h: target.h, r: toR }, b.h, pillProgress(from, target, b)) : toR;
|
|
4059
|
+
const radius = `${Math.max(0, Math.min(r, h / 2, w / 2)).toFixed(2)}px`;
|
|
4060
|
+
for (const n of copyOn ? [pill, copy] : [pill]) {
|
|
4061
|
+
n.style.transform = `translate3d(${x0}px,${y0}px,0)`;
|
|
4062
|
+
n.style.width = `${w}px`;
|
|
4063
|
+
n.style.height = `${h}px`;
|
|
4064
|
+
n.style.borderRadius = radius;
|
|
4065
|
+
}
|
|
4066
|
+
if (copyOn)
|
|
4067
|
+
copyRows.style.transform = `translate3d(${-x0}px,${-y0}px,0)`;
|
|
4068
|
+
};
|
|
4069
|
+
const loop = frameLoop(win, (dt) => {
|
|
4070
|
+
for (const s of springs)
|
|
4071
|
+
s.step(dt);
|
|
4072
|
+
paint();
|
|
4073
|
+
return springs.some((s) => !s.resting);
|
|
4074
|
+
});
|
|
4075
|
+
const show = (on) => {
|
|
4076
|
+
shown = on;
|
|
4077
|
+
pill.toggleAttribute("data-off", !on);
|
|
4078
|
+
copy.toggleAttribute("data-off", !on);
|
|
4079
|
+
container.toggleAttribute("data-pick-empty", !on);
|
|
4080
|
+
};
|
|
4081
|
+
const update = (o) => {
|
|
4082
|
+
if (disposed)
|
|
4083
|
+
return;
|
|
4084
|
+
const row = options.selected();
|
|
4085
|
+
const next = row?.isConnected ? measure(row) : null;
|
|
4086
|
+
const plan = planPill({ shown, at: shown ? at() : null, target }, next, {
|
|
4087
|
+
first,
|
|
4088
|
+
reduced: reducedMotion(win),
|
|
4089
|
+
...o?.snap ? { snap: true } : {}
|
|
4090
|
+
});
|
|
4091
|
+
first = false;
|
|
4092
|
+
for (const n of clones.values())
|
|
4093
|
+
n.remove();
|
|
4094
|
+
clones.clear();
|
|
4095
|
+
rowBoxes = copyOn ? rowsNow().filter((r) => r.isConnected).map((r) => ({ row: r, box: measure(r) })) : [];
|
|
4096
|
+
if (plan.kind === "hide") {
|
|
4097
|
+
if (shown)
|
|
4098
|
+
show(false);
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
if (plan.kind === "stay") {
|
|
4102
|
+
syncCopy(boxUnion(at(), target));
|
|
4103
|
+
return;
|
|
4104
|
+
}
|
|
4105
|
+
const r = radiusOf(row);
|
|
4106
|
+
if (plan.kind === "land") {
|
|
4107
|
+
sx.snap(plan.box.x);
|
|
4108
|
+
sy.snap(plan.box.y);
|
|
4109
|
+
sw.snap(plan.box.w);
|
|
4110
|
+
sh.snap(plan.box.h);
|
|
4111
|
+
from = plan.box;
|
|
4112
|
+
fromR = r;
|
|
4113
|
+
toR = r;
|
|
4114
|
+
target = plan.box;
|
|
4115
|
+
syncCopy(plan.box);
|
|
4116
|
+
paint();
|
|
4117
|
+
loop.stop();
|
|
4118
|
+
if (!shown)
|
|
4119
|
+
show(true);
|
|
4120
|
+
return;
|
|
4121
|
+
}
|
|
4122
|
+
const m = springMotion(plan.spring);
|
|
4123
|
+
for (const s of springs) {
|
|
4124
|
+
s.omega = m.omega;
|
|
4125
|
+
s.zeta = m.zeta;
|
|
4126
|
+
}
|
|
4127
|
+
const now = at();
|
|
4128
|
+
fromR = target ? pillRadius({ h: from.h, r: fromR }, { h: target.h, r: toR }, now.h, pillProgress(from, target, now)) : r;
|
|
4129
|
+
from = now;
|
|
4130
|
+
toR = r;
|
|
4131
|
+
target = plan.box;
|
|
4132
|
+
sx.set(plan.box.x);
|
|
4133
|
+
sy.set(plan.box.y);
|
|
4134
|
+
sw.set(plan.box.w);
|
|
4135
|
+
sh.set(plan.box.h);
|
|
4136
|
+
syncCopy(boxUnion(now, plan.box));
|
|
4137
|
+
loop.wake();
|
|
4138
|
+
};
|
|
4139
|
+
const ro = "ResizeObserver" in win ? new win.ResizeObserver(() => update({ snap: true })) : null;
|
|
4140
|
+
ro?.observe(container);
|
|
4141
|
+
update();
|
|
4142
|
+
return {
|
|
4143
|
+
element: pill,
|
|
4144
|
+
update,
|
|
4145
|
+
dispose() {
|
|
4146
|
+
disposed = true;
|
|
4147
|
+
loop.stop();
|
|
4148
|
+
ro?.disconnect();
|
|
4149
|
+
pill.remove();
|
|
4150
|
+
copy.remove();
|
|
4151
|
+
container.removeAttribute("data-pick-empty");
|
|
4152
|
+
}
|
|
4153
|
+
};
|
|
4154
|
+
}
|
|
4155
|
+
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) => {
|
|
4156
|
+
const x = Math.min(a.x, b.x);
|
|
4157
|
+
const y = Math.min(a.y, b.y);
|
|
4158
|
+
return {
|
|
4159
|
+
x,
|
|
4160
|
+
y,
|
|
4161
|
+
w: Math.max(a.x + a.w, b.x + b.w) - x,
|
|
4162
|
+
h: Math.max(a.y + a.h, b.y + b.h) - y
|
|
4163
|
+
};
|
|
4164
|
+
}, grid = (v) => Math.round(v * 64) / 64;
|
|
4165
|
+
var init_selection_pill = __esm(() => {
|
|
4166
|
+
init_motion();
|
|
4167
|
+
init_look();
|
|
4168
|
+
});
|
|
4169
|
+
|
|
3864
4170
|
// src/panel/hints.ts
|
|
3865
4171
|
function extractWords(text) {
|
|
3866
4172
|
return text.toLowerCase().match(/[a-z]+/g) ?? [];
|
|
@@ -4689,34 +4995,27 @@ function tree(container, options) {
|
|
|
4689
4995
|
const empty = doc.createElement("div");
|
|
4690
4996
|
empty.className = "knobs-tree-empty";
|
|
4691
4997
|
empty.hidden = true;
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4998
|
+
root.append(space, line, empty);
|
|
4999
|
+
let pill = null;
|
|
5000
|
+
let picked = null;
|
|
5001
|
+
let travelUntil = 0;
|
|
5002
|
+
const pickedRow = () => {
|
|
5003
|
+
const id = sel.ids.length === 1 ? sel.ids[0] : null;
|
|
5004
|
+
const v = id === null ? undefined : views.get(id);
|
|
5005
|
+
return v && !v.leaving && rowIndex.has(v.id) ? v : null;
|
|
4699
5006
|
};
|
|
4700
5007
|
const placeCursor = (animate) => {
|
|
4701
|
-
if (!
|
|
5008
|
+
if (!pill)
|
|
4702
5009
|
return;
|
|
4703
5010
|
const id = sel.ids.length === 1 ? sel.ids[0] : null;
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
}
|
|
4711
|
-
|
|
4712
|
-
cursorY.set(i * h);
|
|
4713
|
-
else
|
|
4714
|
-
cursorY.snap(i * h);
|
|
4715
|
-
cursorOn = true;
|
|
4716
|
-
cursor.setAttribute("data-on", "");
|
|
4717
|
-
drawCursor();
|
|
4718
|
-
if (!cursorY.resting)
|
|
4719
|
-
loop.wake();
|
|
5011
|
+
root.toggleAttribute("data-glide", id !== null && rowIndex.has(id));
|
|
5012
|
+
const fresh = id !== picked;
|
|
5013
|
+
picked = id;
|
|
5014
|
+
if (animate && fresh) {
|
|
5015
|
+
travelUntil = performance.now() + 450;
|
|
5016
|
+
pill.update();
|
|
5017
|
+
} else if (performance.now() >= travelUntil)
|
|
5018
|
+
pill.update({ snap: true });
|
|
4720
5019
|
};
|
|
4721
5020
|
container.append(root);
|
|
4722
5021
|
const scrollbar = overlayScrollbar(root, space);
|
|
@@ -4745,6 +5044,12 @@ function tree(container, options) {
|
|
|
4745
5044
|
};
|
|
4746
5045
|
const views = new Map;
|
|
4747
5046
|
const pool = [];
|
|
5047
|
+
if (options.glide)
|
|
5048
|
+
pill = selectionPill(root, {
|
|
5049
|
+
rows: () => [...views.values()].filter((v) => !v.leaving).map((v) => v.el),
|
|
5050
|
+
selected: () => pickedRow()?.el ?? null,
|
|
5051
|
+
radius: (row) => Number.parseFloat(win.getComputedStyle(row, "::before").borderTopLeftRadius) || 0
|
|
5052
|
+
});
|
|
4748
5053
|
const makeView = (id) => {
|
|
4749
5054
|
const v = pool.pop() ?? createView();
|
|
4750
5055
|
v.id = id;
|
|
@@ -4925,12 +5230,6 @@ function tree(container, options) {
|
|
|
4925
5230
|
function frame(dt) {
|
|
4926
5231
|
let moving = false;
|
|
4927
5232
|
let bottom = rows.length * h;
|
|
4928
|
-
if (options.glide && !cursorY.resting) {
|
|
4929
|
-
cursorY.step(dt);
|
|
4930
|
-
drawCursor();
|
|
4931
|
-
if (!cursorY.resting)
|
|
4932
|
-
moving = true;
|
|
4933
|
-
}
|
|
4934
5233
|
for (const v of views.values()) {
|
|
4935
5234
|
v.y.step(dt);
|
|
4936
5235
|
if (!v.y.resting)
|
|
@@ -4950,6 +5249,8 @@ function tree(container, options) {
|
|
|
4950
5249
|
}
|
|
4951
5250
|
setSpace(moving ? bottom : rows.length * h);
|
|
4952
5251
|
trim();
|
|
5252
|
+
if (pill && moving && performance.now() >= travelUntil)
|
|
5253
|
+
pill.update({ snap: true });
|
|
4953
5254
|
return moving;
|
|
4954
5255
|
}
|
|
4955
5256
|
const trim = () => {
|
|
@@ -5578,6 +5879,7 @@ function tree(container, options) {
|
|
|
5578
5879
|
autoScroll.stop();
|
|
5579
5880
|
ro.disconnect();
|
|
5580
5881
|
scrollbar.dispose();
|
|
5882
|
+
pill?.dispose();
|
|
5581
5883
|
dragging?.ghost.remove();
|
|
5582
5884
|
root.remove();
|
|
5583
5885
|
}
|
|
@@ -5596,6 +5898,7 @@ var init_tree = __esm(() => {
|
|
|
5596
5898
|
init_motion();
|
|
5597
5899
|
init_page_hints();
|
|
5598
5900
|
init_look();
|
|
5901
|
+
init_selection_pill();
|
|
5599
5902
|
init_tree_model();
|
|
5600
5903
|
MOVE = springMotion(SPRINGS.default);
|
|
5601
5904
|
});
|
|
@@ -5732,7 +6035,7 @@ function codeDiff(container, options) {
|
|
|
5732
6035
|
};
|
|
5733
6036
|
const nav = make("span", "knobs-diff-nav");
|
|
5734
6037
|
const prev = button("Previous file", PREV);
|
|
5735
|
-
const next = button("Next file",
|
|
6038
|
+
const next = button("Next file", NEXT);
|
|
5736
6039
|
const place = make("span", "");
|
|
5737
6040
|
nav.append(prev, place, next);
|
|
5738
6041
|
const wrapButton = button("Wrap lines", WRAP);
|
|
@@ -5753,9 +6056,8 @@ function codeDiff(container, options) {
|
|
|
5753
6056
|
const seg = make("div", "knobs-diff-seg");
|
|
5754
6057
|
seg.setAttribute("role", "radiogroup");
|
|
5755
6058
|
seg.setAttribute("aria-label", "Layout");
|
|
5756
|
-
const pill = make("i", "knobs-diff-pill");
|
|
5757
6059
|
const choice = (value, label) => {
|
|
5758
|
-
const b = make("button", "");
|
|
6060
|
+
const b = make("button", "knobs-diff-choice");
|
|
5759
6061
|
b.type = "button";
|
|
5760
6062
|
b.setAttribute("role", "radio");
|
|
5761
6063
|
b.dataset.value = value;
|
|
@@ -5764,7 +6066,7 @@ function codeDiff(container, options) {
|
|
|
5764
6066
|
};
|
|
5765
6067
|
const splitButton = choice("split", "Split");
|
|
5766
6068
|
const unifiedButton = choice("unified", "Unified");
|
|
5767
|
-
seg.append(
|
|
6069
|
+
seg.append(splitButton, unifiedButton);
|
|
5768
6070
|
if (options.lead)
|
|
5769
6071
|
head.append(options.lead);
|
|
5770
6072
|
head.append(path, stat, make("span", "knobs-diff-fill"), nav, wrapButton, seg, viewedButton);
|
|
@@ -5775,6 +6077,11 @@ function codeDiff(container, options) {
|
|
|
5775
6077
|
body.append(note, probe);
|
|
5776
6078
|
root.append(head, body);
|
|
5777
6079
|
container.append(root);
|
|
6080
|
+
const segPill = selectionPill(seg, {
|
|
6081
|
+
rows: () => [splitButton, unifiedButton],
|
|
6082
|
+
selected: () => layout === "split" ? splitButton : unifiedButton
|
|
6083
|
+
});
|
|
6084
|
+
let pilled = layout;
|
|
5778
6085
|
const fileNow = () => files.find((f) => f.name === current) ?? null;
|
|
5779
6086
|
const paintHead = () => {
|
|
5780
6087
|
const file = fileNow();
|
|
@@ -5807,6 +6114,10 @@ function codeDiff(container, options) {
|
|
|
5807
6114
|
next.disabled = at >= files.length - 1;
|
|
5808
6115
|
wrapButton.setAttribute("aria-pressed", String(wrap));
|
|
5809
6116
|
seg.dataset.value = layout;
|
|
6117
|
+
if (pilled !== layout) {
|
|
6118
|
+
pilled = layout;
|
|
6119
|
+
segPill.update();
|
|
6120
|
+
}
|
|
5810
6121
|
splitButton.setAttribute("aria-checked", String(layout === "split"));
|
|
5811
6122
|
unifiedButton.setAttribute("aria-checked", String(layout === "unified"));
|
|
5812
6123
|
splitButton.tabIndex = layout === "split" ? 0 : -1;
|
|
@@ -6641,6 +6952,7 @@ function codeDiff(container, options) {
|
|
|
6641
6952
|
dispose() {
|
|
6642
6953
|
disposed = true;
|
|
6643
6954
|
loop.stop();
|
|
6955
|
+
segPill.dispose();
|
|
6644
6956
|
sizes.disconnect();
|
|
6645
6957
|
attaching.disconnect();
|
|
6646
6958
|
resized.disconnect();
|
|
@@ -6996,7 +7308,7 @@ function diffReview(container, options) {
|
|
|
6996
7308
|
}
|
|
6997
7309
|
};
|
|
6998
7310
|
}
|
|
6999
|
-
var lib, load = () => lib ??= import("@pierre/diffs"), SYNTAX, IN, OUT, FADE4, HEIGHT, DRAWER, BLUR = 3, WRAP, PREV, UNVIEWED, VIEWED, SEARCH, CLEAR2, SIDEBAR, SEPARATOR = 32;
|
|
7311
|
+
var lib, load = () => lib ??= import("@pierre/diffs"), SYNTAX, IN, OUT, FADE4, HEIGHT, DRAWER, BLUR = 3, WRAP, PREV, NEXT, UNVIEWED, VIEWED, SEARCH, CLEAR2, SIDEBAR, SEPARATOR = 32;
|
|
7000
7312
|
var init_diff = __esm(() => {
|
|
7001
7313
|
init_glide();
|
|
7002
7314
|
init_motion();
|
|
@@ -7004,6 +7316,8 @@ var init_diff = __esm(() => {
|
|
|
7004
7316
|
init_diff_model();
|
|
7005
7317
|
init_look();
|
|
7006
7318
|
init_rgba();
|
|
7319
|
+
init_icons();
|
|
7320
|
+
init_selection_pill();
|
|
7007
7321
|
init_tree();
|
|
7008
7322
|
SYNTAX = { dark: "pierre-dark", light: "pierre-light" };
|
|
7009
7323
|
IN = springMotion(SPRINGS.default);
|
|
@@ -7011,13 +7325,14 @@ var init_diff = __esm(() => {
|
|
|
7011
7325
|
FADE4 = springMotion(FADE);
|
|
7012
7326
|
HEIGHT = springMotion(SPRINGS.calm);
|
|
7013
7327
|
DRAWER = springMotion(SPRINGS.calm);
|
|
7014
|
-
WRAP =
|
|
7015
|
-
PREV =
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7328
|
+
WRAP = icon("arrowUDownLeft");
|
|
7329
|
+
PREV = icon("caretLeft");
|
|
7330
|
+
NEXT = icon("caretRight");
|
|
7331
|
+
UNVIEWED = icon("circle");
|
|
7332
|
+
VIEWED = icon("checkCircle");
|
|
7333
|
+
SEARCH = icon("magnifyingGlass", { small: true });
|
|
7334
|
+
CLEAR2 = icon("x", { small: true });
|
|
7335
|
+
SIDEBAR = icon("sidebar");
|
|
7021
7336
|
});
|
|
7022
7337
|
|
|
7023
7338
|
// src/ui/layer.ts
|
|
@@ -8000,10 +8315,25 @@ function advance(t, dt, o) {
|
|
|
8000
8315
|
}
|
|
8001
8316
|
return { t: hi, ended: true, wrapped: false };
|
|
8002
8317
|
}
|
|
8003
|
-
|
|
8318
|
+
function moveHead(head, t, motion, catching, minGap = 0) {
|
|
8319
|
+
if (motion === "jump" && Math.abs(t - head.x) > minGap)
|
|
8320
|
+
return { x: head.x, target: t, catching: true };
|
|
8321
|
+
if (catching)
|
|
8322
|
+
return { x: head.x + (t - head.target), target: t, catching: true };
|
|
8323
|
+
return { x: t, target: t, catching: false };
|
|
8324
|
+
}
|
|
8325
|
+
function currentMarker(markers, t, fps) {
|
|
8326
|
+
let best = null;
|
|
8327
|
+
for (const m of markers)
|
|
8328
|
+
if (m.time <= t + 0.5 / fps && (!best || m.time >= best.time))
|
|
8329
|
+
best = m;
|
|
8330
|
+
return best;
|
|
8331
|
+
}
|
|
8332
|
+
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;
|
|
8004
8333
|
var init_timeline_model = __esm(() => {
|
|
8005
8334
|
SECOND_STEPS = [0.1, 0.2, 0.25, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600];
|
|
8006
8335
|
FRAME_STEPS = [1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 9000, 18000];
|
|
8336
|
+
CATCH_UP = { duration: 0.15, bounce: 0 };
|
|
8007
8337
|
});
|
|
8008
8338
|
|
|
8009
8339
|
// src/ui/timeline.ts
|
|
@@ -8016,6 +8346,7 @@ function timeline(container, options) {
|
|
|
8016
8346
|
let speed = options.speed ?? 1;
|
|
8017
8347
|
let repeat = options.repeat ?? true;
|
|
8018
8348
|
const arrows = options.arrows ?? "frames";
|
|
8349
|
+
const lane = options.variant === "lane";
|
|
8019
8350
|
let time = Math.min(duration, Math.max(0, options.time ?? 0));
|
|
8020
8351
|
let playing = false;
|
|
8021
8352
|
let markers = [...options.markers ?? []];
|
|
@@ -8049,27 +8380,27 @@ function timeline(container, options) {
|
|
|
8049
8380
|
root.setAttribute("aria-label", options.label ?? "Timeline");
|
|
8050
8381
|
root.setAttribute("aria-keyshortcuts", "Space ArrowLeft ArrowRight , . Home End [ ] I O M");
|
|
8051
8382
|
applyLook2(root, options);
|
|
8383
|
+
if (lane)
|
|
8384
|
+
root.dataset.variant = "lane";
|
|
8052
8385
|
const transport = el("div", "knobs-tl-transport");
|
|
8053
8386
|
const playBtn = el("button", "knobs-tl-play");
|
|
8054
8387
|
playBtn.type = "button";
|
|
8055
8388
|
playBtn.tabIndex = -1;
|
|
8056
|
-
|
|
8057
|
-
const now = el("span", "knobs-tl-now");
|
|
8058
|
-
const sub = el("span", "knobs-tl-sub");
|
|
8059
|
-
readout.append(now, sub);
|
|
8060
|
-
transport.append(playBtn, readout);
|
|
8389
|
+
transport.append(playBtn);
|
|
8061
8390
|
const track = el("div", "knobs-tl-track");
|
|
8062
8391
|
const canvas = el("canvas", "knobs-tl-ruler");
|
|
8063
8392
|
canvas.setAttribute("aria-hidden", "true");
|
|
8064
|
-
const
|
|
8393
|
+
const markerLane = el("div", "knobs-tl-lane");
|
|
8065
8394
|
const hover = el("div", "knobs-tl-hover");
|
|
8395
|
+
const tag = el("span", "knobs-tl-tag");
|
|
8396
|
+
tag.setAttribute("aria-hidden", "true");
|
|
8066
8397
|
const head = el("div", "knobs-tl-head");
|
|
8067
8398
|
head.id = `${id}-head`;
|
|
8068
8399
|
head.setAttribute("role", "slider");
|
|
8069
8400
|
head.setAttribute("aria-label", "Playhead");
|
|
8070
8401
|
head.setAttribute("aria-valuemin", "0");
|
|
8071
8402
|
head.setAttribute("aria-valuemax", String(duration));
|
|
8072
|
-
track.append(canvas,
|
|
8403
|
+
track.append(canvas, markerLane, hover, head, tag);
|
|
8073
8404
|
root.append(transport, track);
|
|
8074
8405
|
container.append(root);
|
|
8075
8406
|
let width = 1;
|
|
@@ -8116,13 +8447,14 @@ function timeline(container, options) {
|
|
|
8116
8447
|
let drawn = "";
|
|
8117
8448
|
const drawRuler = () => {
|
|
8118
8449
|
const v = view();
|
|
8119
|
-
const key = `${v.start.toFixed(6)}:${v.span.toFixed(6)}:${width}:${units}:${fps}`;
|
|
8450
|
+
const key = `${v.start.toFixed(6)}:${v.span.toFixed(6)}:${width}:${height}:${units}:${fps}`;
|
|
8120
8451
|
if (key === drawn)
|
|
8121
8452
|
return;
|
|
8122
8453
|
drawn = key;
|
|
8123
8454
|
const dpr = win.devicePixelRatio || 1;
|
|
8124
8455
|
const w = Math.max(1, Math.round(width * dpr));
|
|
8125
|
-
const
|
|
8456
|
+
const rh = lane ? height : RULER;
|
|
8457
|
+
const h = Math.round(rh * dpr);
|
|
8126
8458
|
if (canvas.width !== w || canvas.height !== h) {
|
|
8127
8459
|
canvas.width = w;
|
|
8128
8460
|
canvas.height = h;
|
|
@@ -8131,8 +8463,19 @@ function timeline(container, options) {
|
|
|
8131
8463
|
if (!ctx)
|
|
8132
8464
|
return;
|
|
8133
8465
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
8134
|
-
ctx.clearRect(0, 0, width,
|
|
8466
|
+
ctx.clearRect(0, 0, width, rh);
|
|
8135
8467
|
const ticks = rulerTicks(v, width, { fps, units });
|
|
8468
|
+
if (lane) {
|
|
8469
|
+
ctx.fillStyle = colors.tick;
|
|
8470
|
+
for (const t of ticks.minor)
|
|
8471
|
+
if (t >= -0.000000001 && t <= duration + 0.000000001)
|
|
8472
|
+
ctx.fillRect(xOf(t) - 0.5, rh - 4, 1, 4);
|
|
8473
|
+
ctx.fillStyle = colors.major;
|
|
8474
|
+
for (const m of ticks.major)
|
|
8475
|
+
if (m.t >= -0.000000001 && m.t <= duration + 0.000000001)
|
|
8476
|
+
ctx.fillRect(xOf(m.t) - 0.5, rh - 7, 1, 7);
|
|
8477
|
+
return;
|
|
8478
|
+
}
|
|
8136
8479
|
const px = (t) => Math.round(xOf(t)) + 0.5;
|
|
8137
8480
|
ctx.fillStyle = colors.line;
|
|
8138
8481
|
ctx.fillRect(0, RULER - 1, width, 1);
|
|
@@ -8172,18 +8515,21 @@ function timeline(container, options) {
|
|
|
8172
8515
|
n.dataset.id = m.id;
|
|
8173
8516
|
const pin = el("span", "knobs-tl-pin");
|
|
8174
8517
|
const label = el("span", "knobs-tl-mlabel");
|
|
8518
|
+
label.append(el("span", "knobs-tl-mtext knobs-scroll-fade-x"));
|
|
8175
8519
|
n.append(pin, label);
|
|
8176
|
-
|
|
8520
|
+
markerLane.append(n);
|
|
8177
8521
|
markerEls.set(m.id, n);
|
|
8178
8522
|
if (built)
|
|
8179
8523
|
enter(n);
|
|
8180
8524
|
}
|
|
8181
8525
|
const label = n.querySelector(".knobs-tl-mlabel");
|
|
8182
|
-
|
|
8183
|
-
|
|
8526
|
+
const text = label.firstElementChild;
|
|
8527
|
+
if (text.textContent !== (m.label ?? ""))
|
|
8528
|
+
text.textContent = m.label ?? "";
|
|
8184
8529
|
label.hidden = !m.label;
|
|
8185
8530
|
n.style.setProperty("--knobs-tl-color", m.color ?? "");
|
|
8186
8531
|
n.toggleAttribute("data-selected", selected?.kind === "marker" && selected.id === m.id);
|
|
8532
|
+
n.toggleAttribute("data-current", m.id === currentId);
|
|
8187
8533
|
n.title = `${m.label ? `${m.label} · ` : ""}${clock(m.time)}`;
|
|
8188
8534
|
}
|
|
8189
8535
|
placeMarkers();
|
|
@@ -8211,7 +8557,7 @@ function timeline(container, options) {
|
|
|
8211
8557
|
b.dataset.edge = "end";
|
|
8212
8558
|
const label = el("span", "knobs-tl-rlabel");
|
|
8213
8559
|
n.append(label, a, b);
|
|
8214
|
-
track.insertBefore(n,
|
|
8560
|
+
track.insertBefore(n, markerLane);
|
|
8215
8561
|
rangeEls.set(r.id, n);
|
|
8216
8562
|
if (built)
|
|
8217
8563
|
enter(n);
|
|
@@ -8260,16 +8606,17 @@ function timeline(container, options) {
|
|
|
8260
8606
|
};
|
|
8261
8607
|
const clock = (t) => units === "frames" ? `f${toFrame(t, fps)}` : `${timeLabel(t, "seconds", fps, 0.01)}`;
|
|
8262
8608
|
let shownHead = time;
|
|
8609
|
+
let currentId = null;
|
|
8263
8610
|
const renderHead = () => {
|
|
8264
8611
|
head.style.transform = `translateX(${xOf(shownHead).toFixed(2)}px)`;
|
|
8265
|
-
const main = units === "frames" ? `f${toFrame(time, fps)}` : `${time.toFixed(2)}s`;
|
|
8266
|
-
const minor = units === "frames" ? `${time.toFixed(2)}s` : `f${toFrame(time, fps)}`;
|
|
8267
|
-
if (now.textContent !== main)
|
|
8268
|
-
now.textContent = main;
|
|
8269
|
-
if (sub.textContent !== minor)
|
|
8270
|
-
sub.textContent = minor;
|
|
8271
8612
|
head.setAttribute("aria-valuenow", time.toFixed(3));
|
|
8272
8613
|
head.setAttribute("aria-valuetext", `${time.toFixed(2)} seconds, frame ${toFrame(time, fps)}`);
|
|
8614
|
+
const cur = currentMarker(markers, time, fps)?.id ?? null;
|
|
8615
|
+
if (cur !== currentId) {
|
|
8616
|
+
markerEls.get(currentId ?? "")?.removeAttribute("data-current");
|
|
8617
|
+
currentId = cur;
|
|
8618
|
+
markerEls.get(cur ?? "")?.setAttribute("data-current", "");
|
|
8619
|
+
}
|
|
8273
8620
|
};
|
|
8274
8621
|
let viewSent = "";
|
|
8275
8622
|
const render = () => {
|
|
@@ -8295,18 +8642,27 @@ function timeline(container, options) {
|
|
|
8295
8642
|
};
|
|
8296
8643
|
const caret = new Spring(time, GLIDE.omega, GLIDE.zeta, 0.00001);
|
|
8297
8644
|
let gliding = false;
|
|
8298
|
-
const
|
|
8299
|
-
const
|
|
8300
|
-
|
|
8645
|
+
const headTo = (shown, motion) => {
|
|
8646
|
+
const from = gliding ? { x: caret.x, target: caret.target } : { x: shownHead, target: shownHead };
|
|
8647
|
+
const jump = motion === "jump" && !reducedMotion(win);
|
|
8648
|
+
const next = moveHead(from, shown, jump ? "jump" : "follow", gliding, 2 / pxPerSec());
|
|
8649
|
+
if (next.catching) {
|
|
8301
8650
|
if (!gliding)
|
|
8302
|
-
caret.snap(
|
|
8303
|
-
caret.
|
|
8651
|
+
caret.snap(from.x);
|
|
8652
|
+
caret.x = next.x;
|
|
8653
|
+
caret.target = next.target;
|
|
8304
8654
|
gliding = true;
|
|
8655
|
+
shownHead = caret.x;
|
|
8305
8656
|
loop.wake();
|
|
8306
8657
|
} else {
|
|
8307
8658
|
gliding = false;
|
|
8308
|
-
|
|
8659
|
+
caret.snap(next.x);
|
|
8660
|
+
shownHead = next.x;
|
|
8309
8661
|
}
|
|
8662
|
+
};
|
|
8663
|
+
const setTime = (t, source, shown = t, glide = false) => {
|
|
8664
|
+
const next = Math.min(duration, Math.max(0, t));
|
|
8665
|
+
headTo(Math.min(duration, Math.max(0, shown)), glide ? "jump" : "follow");
|
|
8310
8666
|
const moved = next !== time;
|
|
8311
8667
|
time = next;
|
|
8312
8668
|
renderHead();
|
|
@@ -8347,8 +8703,7 @@ function timeline(container, options) {
|
|
|
8347
8703
|
options.onCross?.(m);
|
|
8348
8704
|
}
|
|
8349
8705
|
time = step.t;
|
|
8350
|
-
|
|
8351
|
-
gliding = false;
|
|
8706
|
+
headTo(time, step.wrapped ? "jump" : "follow");
|
|
8352
8707
|
emitSeek(time, "play");
|
|
8353
8708
|
keepInView();
|
|
8354
8709
|
if (step.ended) {
|
|
@@ -8361,7 +8716,7 @@ function timeline(container, options) {
|
|
|
8361
8716
|
shownHead = caret.step(dt);
|
|
8362
8717
|
if (caret.resting) {
|
|
8363
8718
|
gliding = false;
|
|
8364
|
-
shownHead =
|
|
8719
|
+
shownHead = caret.target;
|
|
8365
8720
|
}
|
|
8366
8721
|
}
|
|
8367
8722
|
vStart.step(dt);
|
|
@@ -8374,9 +8729,9 @@ function timeline(container, options) {
|
|
|
8374
8729
|
return;
|
|
8375
8730
|
const r = loopRange();
|
|
8376
8731
|
if (r && (time < r.start || time >= r.end - 0.5 / fps))
|
|
8377
|
-
setTime(r.start, "code");
|
|
8732
|
+
setTime(r.start, "code", r.start, true);
|
|
8378
8733
|
else if (!r && time >= duration - 0.5 / fps)
|
|
8379
|
-
setTime(0, "code");
|
|
8734
|
+
setTime(0, "code", 0, true);
|
|
8380
8735
|
playing = true;
|
|
8381
8736
|
syncPlay();
|
|
8382
8737
|
emitPlay(true);
|
|
@@ -8505,24 +8860,35 @@ function timeline(container, options) {
|
|
|
8505
8860
|
};
|
|
8506
8861
|
const localX = (e) => e.clientX - track.getBoundingClientRect().left;
|
|
8507
8862
|
let drag = null;
|
|
8508
|
-
const scrubTo = (x) => {
|
|
8863
|
+
const scrubTo = (x, press = false) => {
|
|
8509
8864
|
const shownX = softClamp(x, xOf(0), xOf(duration), 10);
|
|
8510
8865
|
const s = scrubTime(tOf(shownX), {
|
|
8511
8866
|
duration,
|
|
8512
8867
|
fps,
|
|
8513
8868
|
pxPerSec: pxPerSec(),
|
|
8514
|
-
stops: snapMarkers ? markers.map((m) => m.time) : [],
|
|
8869
|
+
stops: snapMarkers && !lane ? markers.map((m) => m.time) : [],
|
|
8515
8870
|
frames: snapFrames
|
|
8516
8871
|
});
|
|
8517
|
-
setTime(s.t, "scrub", s.shown);
|
|
8872
|
+
setTime(s.t, "scrub", s.shown, press);
|
|
8518
8873
|
root.toggleAttribute("data-stuck", s.stop !== null);
|
|
8519
8874
|
};
|
|
8875
|
+
const showTag = (x) => {
|
|
8876
|
+
const at = Math.min(duration, Math.max(0, tOf(x)));
|
|
8877
|
+
const text = `${at.toFixed(2)} s`;
|
|
8878
|
+
if (tag.textContent !== text)
|
|
8879
|
+
tag.textContent = text;
|
|
8880
|
+
const cx = Math.min(width, Math.max(0, x));
|
|
8881
|
+
const flip = cx > width - tag.offsetWidth - 12;
|
|
8882
|
+
tag.style.transform = `translateX(${cx.toFixed(1)}px) translateX(${flip ? "calc(-100% - 4px)" : "4px"})`;
|
|
8883
|
+
tag.dataset.on = "";
|
|
8884
|
+
};
|
|
8885
|
+
const hideTag = () => delete tag.dataset.on;
|
|
8520
8886
|
track.addEventListener("pointerdown", (e) => {
|
|
8521
8887
|
if (e.button !== 0 || editing)
|
|
8522
8888
|
return;
|
|
8523
8889
|
root.focus({ preventScroll: true });
|
|
8524
8890
|
const t = e.target;
|
|
8525
|
-
const markerEl = t.closest(".knobs-tl-marker");
|
|
8891
|
+
const markerEl = lane ? null : t.closest(".knobs-tl-marker");
|
|
8526
8892
|
const edgeEl = t.closest(".knobs-tl-edge");
|
|
8527
8893
|
const rangeEl = t.closest(".knobs-tl-range");
|
|
8528
8894
|
track.setPointerCapture(e.pointerId);
|
|
@@ -8567,13 +8933,16 @@ function timeline(container, options) {
|
|
|
8567
8933
|
root.dataset.scrubbing = "";
|
|
8568
8934
|
for (const l of listeners)
|
|
8569
8935
|
l.onScrub?.(true);
|
|
8570
|
-
scrubTo(localX(e));
|
|
8936
|
+
scrubTo(localX(e), true);
|
|
8937
|
+
showTag(localX(e));
|
|
8571
8938
|
}
|
|
8572
8939
|
e.preventDefault();
|
|
8573
8940
|
});
|
|
8574
8941
|
track.addEventListener("pointermove", (e) => {
|
|
8575
8942
|
const x = localX(e);
|
|
8576
8943
|
hover.style.transform = `translateX(${x.toFixed(1)}px)`;
|
|
8944
|
+
if (!drag || drag.kind === "scrub")
|
|
8945
|
+
showTag(x);
|
|
8577
8946
|
if (!drag)
|
|
8578
8947
|
return;
|
|
8579
8948
|
if (drag.kind === "scrub")
|
|
@@ -8619,8 +8988,10 @@ function timeline(container, options) {
|
|
|
8619
8988
|
delete root.dataset.dragging;
|
|
8620
8989
|
root.removeAttribute("data-stuck");
|
|
8621
8990
|
if (d.kind === "scrub") {
|
|
8622
|
-
|
|
8991
|
+
headTo(time, "follow");
|
|
8623
8992
|
renderHead();
|
|
8993
|
+
if (!track.matches(":hover"))
|
|
8994
|
+
hideTag();
|
|
8624
8995
|
for (const l of listeners)
|
|
8625
8996
|
l.onScrub?.(false);
|
|
8626
8997
|
if (d.wasPlaying)
|
|
@@ -8633,7 +9004,7 @@ function timeline(container, options) {
|
|
|
8633
9004
|
} else {
|
|
8634
9005
|
const m = markers.find((x) => x.id === d.id);
|
|
8635
9006
|
if (m)
|
|
8636
|
-
setTime(m.time, "marker");
|
|
9007
|
+
setTime(m.time, "marker", m.time, true);
|
|
8637
9008
|
}
|
|
8638
9009
|
} else if (d.kind === "edge" || d.moved)
|
|
8639
9010
|
emitRanges();
|
|
@@ -8642,10 +9013,14 @@ function timeline(container, options) {
|
|
|
8642
9013
|
track.addEventListener("pointercancel", endDrag);
|
|
8643
9014
|
track.addEventListener("lostpointercapture", endDrag);
|
|
8644
9015
|
track.addEventListener("pointerleave", () => {
|
|
8645
|
-
if (
|
|
8646
|
-
|
|
9016
|
+
if (drag)
|
|
9017
|
+
return;
|
|
9018
|
+
hover.style.transform = "translateX(-100px)";
|
|
9019
|
+
hideTag();
|
|
8647
9020
|
});
|
|
8648
9021
|
track.addEventListener("dblclick", (e) => {
|
|
9022
|
+
if (lane)
|
|
9023
|
+
return;
|
|
8649
9024
|
const markerEl = e.target.closest(".knobs-tl-marker");
|
|
8650
9025
|
if (markerEl)
|
|
8651
9026
|
return rename(markerEl.dataset.id);
|
|
@@ -8656,6 +9031,8 @@ function timeline(container, options) {
|
|
|
8656
9031
|
rename(m.id);
|
|
8657
9032
|
});
|
|
8658
9033
|
track.addEventListener("wheel", (e) => {
|
|
9034
|
+
if (lane)
|
|
9035
|
+
return;
|
|
8659
9036
|
e.preventDefault();
|
|
8660
9037
|
const scale = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? height : 1;
|
|
8661
9038
|
if (e.ctrlKey || e.metaKey) {
|
|
@@ -8719,7 +9096,7 @@ function timeline(container, options) {
|
|
|
8719
9096
|
if (!m)
|
|
8720
9097
|
return;
|
|
8721
9098
|
select({ kind: "marker", id: m.id });
|
|
8722
|
-
return setTime(m.time, "marker");
|
|
9099
|
+
return setTime(m.time, "marker", m.time, true);
|
|
8723
9100
|
}
|
|
8724
9101
|
case "i":
|
|
8725
9102
|
case "I":
|
|
@@ -8731,11 +9108,13 @@ function timeline(container, options) {
|
|
|
8731
9108
|
return setEdge("end", time);
|
|
8732
9109
|
case "m":
|
|
8733
9110
|
case "M":
|
|
9111
|
+
if (lane)
|
|
9112
|
+
return;
|
|
8734
9113
|
claim();
|
|
8735
9114
|
addMarker(time);
|
|
8736
9115
|
return;
|
|
8737
9116
|
case "Enter":
|
|
8738
|
-
if (selected?.kind === "marker") {
|
|
9117
|
+
if (selected?.kind === "marker" && !lane) {
|
|
8739
9118
|
claim();
|
|
8740
9119
|
rename(selected.id);
|
|
8741
9120
|
}
|
|
@@ -8753,19 +9132,25 @@ function timeline(container, options) {
|
|
|
8753
9132
|
return;
|
|
8754
9133
|
case "=":
|
|
8755
9134
|
case "+":
|
|
9135
|
+
if (lane)
|
|
9136
|
+
return;
|
|
8756
9137
|
claim();
|
|
8757
9138
|
return zoom(1.5);
|
|
8758
9139
|
case "-":
|
|
9140
|
+
if (lane)
|
|
9141
|
+
return;
|
|
8759
9142
|
claim();
|
|
8760
9143
|
return zoom(1 / 1.5);
|
|
8761
9144
|
case "0":
|
|
9145
|
+
if (lane)
|
|
9146
|
+
return;
|
|
8762
9147
|
claim();
|
|
8763
9148
|
return fit();
|
|
8764
9149
|
}
|
|
8765
9150
|
});
|
|
8766
9151
|
const unregisterHints = options.hints === false ? undefined : hints.register(root, () => [
|
|
8767
9152
|
{ el: playBtn, label: playing ? "Pause" : "Play", activate: toggle },
|
|
8768
|
-
...markers.flatMap((m) => {
|
|
9153
|
+
...(lane ? [] : markers).flatMap((m) => {
|
|
8769
9154
|
const n = markerEls.get(m.id);
|
|
8770
9155
|
if (!n || n.dataset.leaving !== undefined)
|
|
8771
9156
|
return [];
|
|
@@ -8777,7 +9162,7 @@ function timeline(container, options) {
|
|
|
8777
9162
|
...label && !label.hidden ? { labelEl: label } : {},
|
|
8778
9163
|
activate: () => {
|
|
8779
9164
|
select({ kind: "marker", id: m.id });
|
|
8780
|
-
setTime(m.time, "marker");
|
|
9165
|
+
setTime(m.time, "marker", m.time, true);
|
|
8781
9166
|
root.focus({ preventScroll: true });
|
|
8782
9167
|
}
|
|
8783
9168
|
}
|
|
@@ -8789,7 +9174,7 @@ function timeline(container, options) {
|
|
|
8789
9174
|
activate: () => root.focus({ preventScroll: true })
|
|
8790
9175
|
}
|
|
8791
9176
|
], { listen: false, rank: 1 });
|
|
8792
|
-
const menu = contextMenu(track, (e) => {
|
|
9177
|
+
const menu = lane ? null : contextMenu(track, (e) => {
|
|
8793
9178
|
const t = e?.target ?? null;
|
|
8794
9179
|
const markerEl = t?.closest(".knobs-tl-marker");
|
|
8795
9180
|
const rangeEl = t?.closest(".knobs-tl-range");
|
|
@@ -8804,7 +9189,7 @@ function timeline(container, options) {
|
|
|
8804
9189
|
run: () => {
|
|
8805
9190
|
const m = markers.find((x) => x.id === mid);
|
|
8806
9191
|
if (m)
|
|
8807
|
-
setTime(m.time, "marker");
|
|
9192
|
+
setTime(m.time, "marker", m.time, true);
|
|
8808
9193
|
}
|
|
8809
9194
|
},
|
|
8810
9195
|
{ label: "Rename…", shortcut: "Enter", run: () => rename(mid) },
|
|
@@ -8968,7 +9353,7 @@ function timeline(container, options) {
|
|
|
8968
9353
|
listeners.clear();
|
|
8969
9354
|
loop.stop();
|
|
8970
9355
|
ro.disconnect();
|
|
8971
|
-
menu
|
|
9356
|
+
menu?.dispose();
|
|
8972
9357
|
root.remove();
|
|
8973
9358
|
}
|
|
8974
9359
|
};
|
|
@@ -8985,10 +9370,21 @@ var init_timeline = __esm(() => {
|
|
|
8985
9370
|
init_timeline_model();
|
|
8986
9371
|
INSTANT2 = springMotion(SPRINGS.instant);
|
|
8987
9372
|
CALM = springMotion(SPRINGS.calm);
|
|
8988
|
-
GLIDE = springMotion(
|
|
9373
|
+
GLIDE = springMotion(CATCH_UP);
|
|
8989
9374
|
});
|
|
8990
9375
|
|
|
8991
9376
|
// src/ui/compare-model.ts
|
|
9377
|
+
function compareModes(modes, mode) {
|
|
9378
|
+
const list = [...new Set((modes ?? ["side", "wipe"]).filter((m) => ALL_MODES.includes(m)))];
|
|
9379
|
+
const offered = list.length ? list : ["side", "wipe"];
|
|
9380
|
+
return { modes: offered, mode: mode && offered.includes(mode) ? mode : offered[0] };
|
|
9381
|
+
}
|
|
9382
|
+
function modeForKey(key, modes) {
|
|
9383
|
+
const k = key.toLowerCase();
|
|
9384
|
+
if (/^[1-9]$/.test(k))
|
|
9385
|
+
return modes[Number(k) - 1] ?? null;
|
|
9386
|
+
return modes.find((m) => MODE_KEYS[m] === k) ?? null;
|
|
9387
|
+
}
|
|
8992
9388
|
function mediaKind(src, kind) {
|
|
8993
9389
|
if (kind)
|
|
8994
9390
|
return kind;
|
|
@@ -9067,6 +9463,18 @@ function wipeGrab(x, rect, wipe, slop = 12) {
|
|
|
9067
9463
|
const onLine = Math.abs(x - line) <= slop;
|
|
9068
9464
|
return { onLine, offset: onLine ? x - line : 0 };
|
|
9069
9465
|
}
|
|
9466
|
+
function seamLabels(x, left, right, wb, wa, gap = 8) {
|
|
9467
|
+
const room = (space, w) => Math.min(1, Math.max(0, (space - w - 2 * gap) / gap));
|
|
9468
|
+
return {
|
|
9469
|
+
before: { x: x - gap - wb, opacity: room(x - left, wb) },
|
|
9470
|
+
after: { x: x + gap, opacity: room(right - x, wa) }
|
|
9471
|
+
};
|
|
9472
|
+
}
|
|
9473
|
+
function gripTop(y, rect, grip, margin = 8) {
|
|
9474
|
+
const lo = rect.y + margin;
|
|
9475
|
+
const hi = rect.y + rect.h - margin - grip;
|
|
9476
|
+
return hi < lo ? rect.y + (rect.h - grip) / 2 : Math.min(hi, Math.max(lo, y - grip / 2));
|
|
9477
|
+
}
|
|
9070
9478
|
function stripColumns(view, width, o) {
|
|
9071
9479
|
const { fps } = o;
|
|
9072
9480
|
const total = Math.max(1, Math.round(o.duration * fps));
|
|
@@ -9117,7 +9525,7 @@ function stripPeaks(diffs, o = {}) {
|
|
|
9117
9525
|
});
|
|
9118
9526
|
return out;
|
|
9119
9527
|
}
|
|
9120
|
-
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), 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({
|
|
9528
|
+
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), 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({
|
|
9121
9529
|
u: r.u - dx / Math.max(1, base.w) * r.w,
|
|
9122
9530
|
v: r.v - dy / Math.max(1, base.h) * r.h,
|
|
9123
9531
|
w: r.w
|
|
@@ -9136,6 +9544,8 @@ var VIDEO, frameOf = (t, fps) => Math.max(0, Math.floor(t * fps + 0.0001)), last
|
|
|
9136
9544
|
h: a.h + (b.h - a.h) * k
|
|
9137
9545
|
}), 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));
|
|
9138
9546
|
var init_compare_model = __esm(() => {
|
|
9547
|
+
ALL_MODES = ["side", "wipe", "difference"];
|
|
9548
|
+
MODE_KEYS = { side: "s", wipe: "w", difference: "d" };
|
|
9139
9549
|
VIDEO = /\.(mp4|m4v|mov|webm|ogv|mkv)$/i;
|
|
9140
9550
|
SEEK_IDLE = { seeking: false, pending: null };
|
|
9141
9551
|
ZOOM_FIT = { u: 0, v: 0, w: 1, h: 1 };
|
|
@@ -9935,8 +10345,8 @@ async function createCompareGpu(canvas, onLost) {
|
|
|
9935
10345
|
r(0, f.before);
|
|
9936
10346
|
r(4, f.after);
|
|
9937
10347
|
data.set([f.width, f.height, f.dpr, 0], 8);
|
|
9938
|
-
data.set([f.wipeX, f.soft, 0,
|
|
9939
|
-
data.set([f.from, f.to, f.mix,
|
|
10348
|
+
data.set([f.wipeX, f.soft, 0, 0], 12);
|
|
10349
|
+
data.set([f.from, f.to, f.mix, 0], 16);
|
|
9940
10350
|
data.set([f.threshold, f.gain, ready[0], ready[1]], 20);
|
|
9941
10351
|
data.set([f.accent[0], f.accent[1], f.accent[2], 1], 24);
|
|
9942
10352
|
const l = f.loupe;
|
|
@@ -9978,8 +10388,8 @@ var MODE_INDEX, WGSL = `
|
|
|
9978
10388
|
struct U {
|
|
9979
10389
|
ra: vec4f, rb: vec4f,
|
|
9980
10390
|
view: vec4f, // w, h, dpr, _
|
|
9981
|
-
wipe: vec4f, // x, soft, _,
|
|
9982
|
-
modes: vec4f, // from, to, mix,
|
|
10391
|
+
wipe: vec4f, // x, soft, _, _
|
|
10392
|
+
modes: vec4f, // from, to, mix, _
|
|
9983
10393
|
diff: vec4f, // threshold, gain, readyA, readyB
|
|
9984
10394
|
accent: vec4f,
|
|
9985
10395
|
loupe: vec4f, // x, y, r, zoom (0: none)
|
|
@@ -10030,21 +10440,14 @@ fn takeB(p: vec2f, r: vec4f, c0: vec4f, near: bool) -> vec4f {
|
|
|
10030
10440
|
|
|
10031
10441
|
fn pm(c: vec4f) -> vec4f { return vec4f(c.rgb * c.a, c.a); }
|
|
10032
10442
|
|
|
10033
|
-
fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f
|
|
10034
|
-
let flick = u.modes.w > 0.5;
|
|
10443
|
+
fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f) -> vec4f {
|
|
10035
10444
|
switch mode {
|
|
10036
|
-
case 0u: {
|
|
10037
|
-
let bb = select(b, vec4f(a2.rgb, b.a), flick);
|
|
10038
|
-
return vec4f(a.rgb * a.a + bb.rgb * bb.a * (1.0 - a.a), a.a + bb.a * (1.0 - a.a));
|
|
10039
|
-
}
|
|
10040
10445
|
case 1u: {
|
|
10041
|
-
if (flick) { return pm(a); }
|
|
10042
10446
|
let s = max(u.wipe.y, 0.5);
|
|
10043
10447
|
let m = smoothstep(u.wipe.x - s, u.wipe.x + s, p.x);
|
|
10044
10448
|
return pm(vec4f(mix(a.rgb, b.rgb, m), mix(a.a, b.a, m)));
|
|
10045
10449
|
}
|
|
10046
10450
|
case 2u: {
|
|
10047
|
-
if (flick) { return pm(a); }
|
|
10048
10451
|
let d = abs(a.rgb - b.rgb);
|
|
10049
10452
|
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);
|
|
10050
10453
|
let c = mix(vec3f(luma(b.rgb) * 0.35), u.accent.rgb, h);
|
|
@@ -10052,8 +10455,8 @@ fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f, a2: vec4f) -> vec4f {
|
|
|
10052
10455
|
}
|
|
10053
10456
|
default: {}
|
|
10054
10457
|
}
|
|
10055
|
-
//
|
|
10056
|
-
return
|
|
10458
|
+
// Side by side: each side in its own place, Before over After where they meet.
|
|
10459
|
+
return vec4f(a.rgb * a.a + b.rgb * b.a * (1.0 - a.a), a.a + b.a * (1.0 - a.a));
|
|
10057
10460
|
}
|
|
10058
10461
|
|
|
10059
10462
|
@fragment fn fs(@builtin(position) q: vec4f) -> @location(0) vec4f {
|
|
@@ -10071,16 +10474,14 @@ fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f, a2: vec4f) -> vec4f {
|
|
|
10071
10474
|
}
|
|
10072
10475
|
let a = takeA(p, u.ra, u.ca, near);
|
|
10073
10476
|
let b = takeB(p, u.rb, u.cb, near);
|
|
10074
|
-
|
|
10075
|
-
let a2 = takeA(p, u.rb, u.cb, near);
|
|
10076
|
-
var o = mix(composite(u32(u.modes.x), p, a, b, a2), composite(u32(u.modes.y), p, a, b, a2), u.modes.z);
|
|
10477
|
+
var o = mix(composite(u32(u.modes.x), p, a, b), composite(u32(u.modes.y), p, a, b), u.modes.z);
|
|
10077
10478
|
if (near) { o = o + vec4f(0.05, 0.05, 0.06, 1.0) * (1.0 - o.a); }
|
|
10078
10479
|
o = mix(o, vec4f(0.92), ring * 0.9);
|
|
10079
10480
|
return o;
|
|
10080
10481
|
}
|
|
10081
10482
|
`, TEXTURE_BINDING = 4, COPY_DST = 2, RENDER_ATTACHMENT = 16, shared = null, lostListeners;
|
|
10082
10483
|
var init_compare_gpu = __esm(() => {
|
|
10083
|
-
MODE_INDEX = { side: 0, wipe: 1, difference: 2
|
|
10484
|
+
MODE_INDEX = { side: 0, wipe: 1, difference: 2 };
|
|
10084
10485
|
lostListeners = new Set;
|
|
10085
10486
|
});
|
|
10086
10487
|
|
|
@@ -10466,7 +10867,9 @@ function compare(container, options) {
|
|
|
10466
10867
|
...options.theme ? { theme: options.theme } : {},
|
|
10467
10868
|
...options.appearance ? { appearance: options.appearance } : {}
|
|
10468
10869
|
};
|
|
10469
|
-
|
|
10870
|
+
const offered = compareModes(options.modes, options.mode);
|
|
10871
|
+
const modeList = offered.modes;
|
|
10872
|
+
let mode = offered.mode;
|
|
10470
10873
|
let wipe = Math.min(1, Math.max(0, options.wipe ?? 0.5));
|
|
10471
10874
|
let loupeOn = options.loupe ?? false;
|
|
10472
10875
|
let threshold = options.threshold ?? 4 / 255;
|
|
@@ -10484,7 +10887,7 @@ function compare(container, options) {
|
|
|
10484
10887
|
root.setAttribute("role", "group");
|
|
10485
10888
|
root.setAttribute("aria-roledescription", "compare");
|
|
10486
10889
|
root.setAttribute("aria-label", options.label ?? "Compare");
|
|
10487
|
-
root.setAttribute("aria-keyshortcuts",
|
|
10890
|
+
root.setAttribute("aria-keyshortcuts", `Space ArrowLeft ArrowRight Shift+ArrowLeft Shift+ArrowRight , . ${modeList.map((m, i) => `${i + 1} ${modeKey(m)}`).join(" ")} Z L 0 = -`);
|
|
10488
10891
|
applyLook2(root, options);
|
|
10489
10892
|
root.dataset.mode = mode;
|
|
10490
10893
|
if (auto)
|
|
@@ -10493,35 +10896,37 @@ function compare(container, options) {
|
|
|
10493
10896
|
const modes = el("div", "knobs-cmp-modes");
|
|
10494
10897
|
modes.setAttribute("role", "radiogroup");
|
|
10495
10898
|
modes.setAttribute("aria-label", "Mode");
|
|
10496
|
-
|
|
10899
|
+
modes.hidden = modeList.length < 2;
|
|
10900
|
+
const modeBtns = modeList.map((m, i) => {
|
|
10497
10901
|
const b = el("button", "knobs-cmp-mode");
|
|
10498
10902
|
b.type = "button";
|
|
10499
10903
|
b.tabIndex = -1;
|
|
10500
|
-
b.textContent = m.
|
|
10501
|
-
b.title = `${m.
|
|
10904
|
+
b.textContent = MODE_LABEL[m].short;
|
|
10905
|
+
b.title = `${MODE_LABEL[m].long} · ${i + 1} or ${modeKey(m)}`;
|
|
10502
10906
|
b.setAttribute("role", "radio");
|
|
10503
|
-
b.setAttribute("aria-keyshortcuts",
|
|
10907
|
+
b.setAttribute("aria-keyshortcuts", `${i + 1} ${modeKey(m)}`);
|
|
10504
10908
|
b.addEventListener("click", () => {
|
|
10505
|
-
setMode(m
|
|
10909
|
+
setMode(m);
|
|
10506
10910
|
root.focus({ preventScroll: true });
|
|
10507
10911
|
});
|
|
10508
10912
|
modes.append(b);
|
|
10509
10913
|
return b;
|
|
10510
10914
|
});
|
|
10511
|
-
const
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10915
|
+
const toggleBtn = (name, label, title) => {
|
|
10916
|
+
const b = el("button", "knobs-cmp-toggle");
|
|
10917
|
+
b.type = "button";
|
|
10918
|
+
b.tabIndex = -1;
|
|
10919
|
+
b.innerHTML = icon(name);
|
|
10920
|
+
b.setAttribute("aria-label", label);
|
|
10921
|
+
b.title = title;
|
|
10922
|
+
return b;
|
|
10923
|
+
};
|
|
10924
|
+
const loupeBtn = toggleBtn("magnifyingGlass", "Loupe", "Loupe · hold Z");
|
|
10516
10925
|
loupeBtn.addEventListener("click", () => {
|
|
10517
10926
|
setLoupe(!loupeOn);
|
|
10518
10927
|
root.focus({ preventScroll: true });
|
|
10519
10928
|
});
|
|
10520
|
-
const loopBtn =
|
|
10521
|
-
loopBtn.type = "button";
|
|
10522
|
-
loopBtn.tabIndex = -1;
|
|
10523
|
-
loopBtn.textContent = "Loop";
|
|
10524
|
-
loopBtn.title = "Loop · L (drag its ends on the timeline)";
|
|
10929
|
+
const loopBtn = toggleBtn("repeat", "Loop", "Loop · L (drag its ends on the timeline)");
|
|
10525
10930
|
loopBtn.setAttribute("aria-keyshortcuts", "L");
|
|
10526
10931
|
loopBtn.addEventListener("click", () => {
|
|
10527
10932
|
api.setLoop(!loopNow());
|
|
@@ -10538,7 +10943,8 @@ function compare(container, options) {
|
|
|
10538
10943
|
const zoomReset = el("button", "knobs-cmp-zoom-reset");
|
|
10539
10944
|
zoomReset.type = "button";
|
|
10540
10945
|
zoomReset.tabIndex = -1;
|
|
10541
|
-
zoomReset.
|
|
10946
|
+
zoomReset.innerHTML = icon("arrowCounterClockwise", { small: true });
|
|
10947
|
+
zoomReset.setAttribute("aria-label", "Reset zoom");
|
|
10542
10948
|
zoomReset.title = "Fit · 0 or double-click";
|
|
10543
10949
|
zoomReset.addEventListener("click", () => {
|
|
10544
10950
|
api.setZoom(null);
|
|
@@ -10553,6 +10959,9 @@ function compare(container, options) {
|
|
|
10553
10959
|
handle.setAttribute("aria-label", "Wipe");
|
|
10554
10960
|
handle.setAttribute("aria-valuemin", "0");
|
|
10555
10961
|
handle.setAttribute("aria-valuemax", "100");
|
|
10962
|
+
const grip = el("span", "knobs-cmp-grip");
|
|
10963
|
+
grip.setAttribute("aria-hidden", "true");
|
|
10964
|
+
handle.append(grip);
|
|
10556
10965
|
const note = el("div", "knobs-cmp-note");
|
|
10557
10966
|
stage.append(canvas, split, labels[0], labels[1], handle, zoomPill, note);
|
|
10558
10967
|
root.append(bar, stage);
|
|
@@ -10569,6 +10978,7 @@ function compare(container, options) {
|
|
|
10569
10978
|
root.append(host);
|
|
10570
10979
|
tl = timeline(host, {
|
|
10571
10980
|
...look,
|
|
10981
|
+
variant: "lane",
|
|
10572
10982
|
duration: 1,
|
|
10573
10983
|
fps: fps(),
|
|
10574
10984
|
arrows: { step: 0.5, shift: 2 },
|
|
@@ -10587,9 +10997,9 @@ function compare(container, options) {
|
|
|
10587
10997
|
const fade = new Spring(1, FADE6.omega, FADE6.zeta, 0.001);
|
|
10588
10998
|
let fromMode = mode;
|
|
10589
10999
|
let toMode = mode;
|
|
10590
|
-
|
|
10591
|
-
let
|
|
10592
|
-
let
|
|
11000
|
+
const wipeLine = new Spring(wipe, WIPE_CATCH.omega, WIPE_CATCH.zeta, 0.0001);
|
|
11001
|
+
let wipeCatching = false;
|
|
11002
|
+
let gripY = null;
|
|
10593
11003
|
let loupeHeld = false;
|
|
10594
11004
|
let pointer = null;
|
|
10595
11005
|
let drag = null;
|
|
@@ -10907,25 +11317,16 @@ function compare(container, options) {
|
|
|
10907
11317
|
gpu = g;
|
|
10908
11318
|
setRenderer("gpu");
|
|
10909
11319
|
};
|
|
10910
|
-
const syncFlicker = () => {
|
|
10911
|
-
win.clearInterval(flickerTimer);
|
|
10912
|
-
flickerTimer = 0;
|
|
10913
|
-
phase = false;
|
|
10914
|
-
if (mode === "flicker")
|
|
10915
|
-
flickerTimer = win.setInterval(() => {
|
|
10916
|
-
phase = !phase;
|
|
10917
|
-
requestDraw();
|
|
10918
|
-
}, FLICKER_MS);
|
|
10919
|
-
};
|
|
10920
11320
|
const syncModeUi = () => {
|
|
10921
11321
|
root.dataset.mode = mode;
|
|
10922
|
-
|
|
10923
|
-
modeBtns[i].setAttribute("aria-checked", String(m
|
|
11322
|
+
modeList.forEach((m, i) => {
|
|
11323
|
+
modeBtns[i].setAttribute("aria-checked", String(m === mode));
|
|
10924
11324
|
});
|
|
10925
11325
|
handle.tabIndex = mode === "wipe" ? 0 : -1;
|
|
11326
|
+
modePill?.update();
|
|
10926
11327
|
};
|
|
10927
11328
|
const setMode = (next, emit = true) => {
|
|
10928
|
-
if (next === mode)
|
|
11329
|
+
if (next === mode || !modeList.includes(next))
|
|
10929
11330
|
return;
|
|
10930
11331
|
mode = next;
|
|
10931
11332
|
const reduced = reducedMotion(win);
|
|
@@ -10941,19 +11342,29 @@ function compare(container, options) {
|
|
|
10941
11342
|
else
|
|
10942
11343
|
lay.set(next === "side" ? 0 : 1);
|
|
10943
11344
|
syncModeUi();
|
|
10944
|
-
syncFlicker();
|
|
10945
11345
|
if (emit)
|
|
10946
11346
|
options.onMode?.(next);
|
|
10947
11347
|
requestDraw();
|
|
10948
11348
|
};
|
|
10949
|
-
const setWipe = (next, emit = true) => {
|
|
11349
|
+
const setWipe = (next, emit = true, jump = false) => {
|
|
10950
11350
|
const w = Math.min(1, Math.max(0, next));
|
|
10951
|
-
if (w === wipe)
|
|
11351
|
+
if (w === wipe && !jump)
|
|
10952
11352
|
return;
|
|
10953
11353
|
wipe = w;
|
|
11354
|
+
const minGap = 2 / Math.max(1, wipeRect(layoutNow().L.overlay).w);
|
|
11355
|
+
const head = moveHead({ x: wipeLine.x, target: wipeLine.target }, w, jump && !reducedMotion(win) ? "jump" : "follow", wipeCatching, minGap);
|
|
11356
|
+
wipeCatching = head.catching;
|
|
11357
|
+
if (head.catching) {
|
|
11358
|
+
wipeLine.x = head.x;
|
|
11359
|
+
wipeLine.target = head.target;
|
|
11360
|
+
} else
|
|
11361
|
+
wipeLine.snap(head.x);
|
|
10954
11362
|
if (emit)
|
|
10955
11363
|
options.onWipe?.(w);
|
|
10956
|
-
|
|
11364
|
+
if (head.catching)
|
|
11365
|
+
requestDraw();
|
|
11366
|
+
else
|
|
11367
|
+
drawNow();
|
|
10957
11368
|
};
|
|
10958
11369
|
const setLoupe = (on) => {
|
|
10959
11370
|
loupeOn = on;
|
|
@@ -11106,28 +11517,44 @@ function compare(container, options) {
|
|
|
11106
11517
|
viewSent = key;
|
|
11107
11518
|
options.onView({ zoom: clampZoom(z), loop: l });
|
|
11108
11519
|
};
|
|
11520
|
+
const labelsIn = (m, r, wr, wx) => {
|
|
11521
|
+
if (m === "wipe") {
|
|
11522
|
+
const at = seamLabels(wx, wr.x, wr.x + wr.w, labelW[0], labelW[1], INSET);
|
|
11523
|
+
return [at.before, at.after];
|
|
11524
|
+
}
|
|
11525
|
+
const ax = r.after.x + INSET + (r.after.w - labelW[1] - INSET * 2) * lay.x;
|
|
11526
|
+
return [
|
|
11527
|
+
{ x: r.before.x + INSET, opacity: 1 },
|
|
11528
|
+
{ x: ax, opacity: 1 }
|
|
11529
|
+
];
|
|
11530
|
+
};
|
|
11531
|
+
const placeGrip = (wr) => {
|
|
11532
|
+
const gy = gripTop(gripY ?? wr.y + wr.h / 2, wr, GRIP.h) - wr.y;
|
|
11533
|
+
grip.style.transform = `translate3d(0,${gy.toFixed(2)}px,0)`;
|
|
11534
|
+
};
|
|
11109
11535
|
const draw = () => {
|
|
11110
11536
|
dirty = false;
|
|
11111
11537
|
const r = layoutNow();
|
|
11112
11538
|
const wr = wipeRect(r.L.overlay);
|
|
11113
|
-
const wx = wipeX(wr,
|
|
11539
|
+
const wx = wipeX(wr, wipeLine.x);
|
|
11114
11540
|
const k = lay.x;
|
|
11115
|
-
const
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
const
|
|
11119
|
-
const
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
|
|
11541
|
+
const a = labelsIn(fromMode, r, wr, wx);
|
|
11542
|
+
const b = labelsIn(toMode, r, wr, wx);
|
|
11543
|
+
const f = Math.min(1, Math.max(0, fade.x));
|
|
11544
|
+
const ys = [r.before.y + INSET, r.after.y + INSET];
|
|
11545
|
+
for (const i of [0, 1]) {
|
|
11546
|
+
const x = a[i].x + (b[i].x - a[i].x) * f;
|
|
11547
|
+
labels[i].style.transform = `translate(${x.toFixed(2)}px,${ys[i].toFixed(2)}px)`;
|
|
11548
|
+
labels[i].style.opacity = (a[i].opacity + (b[i].opacity - a[i].opacity) * f).toFixed(3);
|
|
11549
|
+
}
|
|
11124
11550
|
const top = Math.min(r.L.side.before.y, r.L.side.after.y);
|
|
11125
11551
|
split.style.transform = `translate(${r.L.side.split.toFixed(2)}px,${top.toFixed(2)}px)`;
|
|
11126
11552
|
split.style.height = `${Math.max(0, r.L.side.after.h).toFixed(2)}px`;
|
|
11127
11553
|
split.style.opacity = String(Math.max(0, 1 - k * 2));
|
|
11128
|
-
handle.toggleAttribute("data-on", mode === "wipe"
|
|
11554
|
+
handle.toggleAttribute("data-on", mode === "wipe");
|
|
11129
11555
|
handle.style.transform = `translate(${wx.toFixed(2)}px,${wr.y.toFixed(2)}px)`;
|
|
11130
11556
|
handle.style.height = `${Math.max(0, wr.h).toFixed(2)}px`;
|
|
11557
|
+
placeGrip(wr);
|
|
11131
11558
|
handle.setAttribute("aria-valuenow", String(Math.round(wipe * 100)));
|
|
11132
11559
|
handle.setAttribute("aria-valuetext", `${Math.round(wipe * 100)}% Before`);
|
|
11133
11560
|
const pct = `${Math.round(100 / zw.x)}%`;
|
|
@@ -11150,7 +11577,7 @@ function compare(container, options) {
|
|
|
11150
11577
|
const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w, cutAt));
|
|
11151
11578
|
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)` : "";
|
|
11152
11579
|
};
|
|
11153
|
-
const cut = mode === "wipe"
|
|
11580
|
+
const cut = mode === "wipe" ? wx : Number.POSITIVE_INFINITY;
|
|
11154
11581
|
for (const s of sides) {
|
|
11155
11582
|
const rect = s.index === 0 ? r.zBefore : r.zAfter;
|
|
11156
11583
|
const clip = s.index === 0 ? r.before : r.after;
|
|
@@ -11159,10 +11586,8 @@ function compare(container, options) {
|
|
|
11159
11586
|
place(s.preview, rect, clip, at);
|
|
11160
11587
|
if (s.index !== 0)
|
|
11161
11588
|
continue;
|
|
11162
|
-
for (const n of [s.el, s.preview])
|
|
11163
|
-
n.style.mixBlendMode = mode === "difference"
|
|
11164
|
-
n.style.opacity = mode === "flicker" && !showsBefore ? "0" : "";
|
|
11165
|
-
}
|
|
11589
|
+
for (const n of [s.el, s.preview])
|
|
11590
|
+
n.style.mixBlendMode = mode === "difference" ? "difference" : "";
|
|
11166
11591
|
}
|
|
11167
11592
|
return;
|
|
11168
11593
|
}
|
|
@@ -11183,8 +11608,6 @@ function compare(container, options) {
|
|
|
11183
11608
|
from: MODE_INDEX[fromMode],
|
|
11184
11609
|
to: MODE_INDEX[toMode],
|
|
11185
11610
|
mix: Math.min(1, Math.max(0, fade.x)),
|
|
11186
|
-
flick: flickHeld,
|
|
11187
|
-
phase,
|
|
11188
11611
|
threshold,
|
|
11189
11612
|
gain: 1,
|
|
11190
11613
|
accent,
|
|
@@ -11200,6 +11623,11 @@ function compare(container, options) {
|
|
|
11200
11623
|
const loop = frameLoop(win, (dt) => {
|
|
11201
11624
|
lay.step(dt);
|
|
11202
11625
|
fade.step(dt);
|
|
11626
|
+
if (wipeCatching) {
|
|
11627
|
+
wipeLine.step(dt);
|
|
11628
|
+
if (wipeLine.resting)
|
|
11629
|
+
wipeCatching = false;
|
|
11630
|
+
}
|
|
11203
11631
|
zu.step(dt);
|
|
11204
11632
|
zv.step(dt);
|
|
11205
11633
|
zw.step(dt);
|
|
@@ -11233,7 +11661,7 @@ function compare(container, options) {
|
|
|
11233
11661
|
pushFrame(s);
|
|
11234
11662
|
}
|
|
11235
11663
|
}
|
|
11236
|
-
const moving = !lay.resting || !fade.resting || !zoomResting();
|
|
11664
|
+
const moving = !lay.resting || !fade.resting || !zoomResting() || wipeCatching;
|
|
11237
11665
|
if (moving)
|
|
11238
11666
|
dirty = true;
|
|
11239
11667
|
if (auto && !lay.resting)
|
|
@@ -11376,16 +11804,19 @@ function compare(container, options) {
|
|
|
11376
11804
|
e.preventDefault();
|
|
11377
11805
|
return startPinch();
|
|
11378
11806
|
}
|
|
11379
|
-
if (mode === "wipe"
|
|
11807
|
+
if (mode === "wipe") {
|
|
11380
11808
|
const wr = wipeRect(layoutNow().L.overlay);
|
|
11381
11809
|
const inside = p.x >= wr.x - SLOP && p.x <= wr.x + wr.w + SLOP && p.y >= wr.y && p.y <= wr.y + wr.h;
|
|
11382
|
-
const grab = wipeGrab(p.x, wr,
|
|
11810
|
+
const grab = wipeGrab(p.x, wr, wipeLine.x, SLOP);
|
|
11383
11811
|
if (inside && (grab.onLine || !zoomed())) {
|
|
11384
11812
|
drag = { kind: "wipe", offset: grab.offset };
|
|
11385
11813
|
root.dataset.dragging = "";
|
|
11814
|
+
gripY = p.y;
|
|
11386
11815
|
e.preventDefault();
|
|
11387
11816
|
if (!grab.onLine)
|
|
11388
|
-
setWipe(wipeAt(p.x, wr));
|
|
11817
|
+
setWipe(wipeAt(p.x, wr), true, true);
|
|
11818
|
+
else
|
|
11819
|
+
drawNow();
|
|
11389
11820
|
return;
|
|
11390
11821
|
}
|
|
11391
11822
|
}
|
|
@@ -11411,6 +11842,11 @@ function compare(container, options) {
|
|
|
11411
11842
|
pinch = { d, x: mid.x, y: mid.y };
|
|
11412
11843
|
return handZoom(zoomAbout(panned, k, q.pu, q.pv));
|
|
11413
11844
|
}
|
|
11845
|
+
if (mode === "wipe" && drag?.kind !== "pan") {
|
|
11846
|
+
gripY = p.y;
|
|
11847
|
+
if (!drag)
|
|
11848
|
+
placeGrip(wipeRect(layoutNow().L.overlay));
|
|
11849
|
+
}
|
|
11414
11850
|
if (drag?.kind === "wipe")
|
|
11415
11851
|
setWipe(wipeAt(p.x, wipeRect(layoutNow().L.overlay), drag.offset));
|
|
11416
11852
|
else if (drag?.kind === "pan") {
|
|
@@ -11483,12 +11919,6 @@ function compare(container, options) {
|
|
|
11483
11919
|
const z = { u: zu.target, v: zv.target, w: zw.target, h: zw.target };
|
|
11484
11920
|
glideZoom(zoomAbout(z, factor, 0.5, 0.5));
|
|
11485
11921
|
};
|
|
11486
|
-
const holdFlick = (on) => {
|
|
11487
|
-
if (flickHeld === on)
|
|
11488
|
-
return;
|
|
11489
|
-
flickHeld = on;
|
|
11490
|
-
drawNow();
|
|
11491
|
-
};
|
|
11492
11922
|
const holdLoupe = (on) => {
|
|
11493
11923
|
if (loupeHeld === on)
|
|
11494
11924
|
return;
|
|
@@ -11534,25 +11964,17 @@ function compare(container, options) {
|
|
|
11534
11964
|
case "s":
|
|
11535
11965
|
case "w":
|
|
11536
11966
|
case "d":
|
|
11537
|
-
if (e.altKey || e.shiftKey || key === "s" && claimsHintKey(doc, key))
|
|
11538
|
-
return;
|
|
11539
|
-
claim();
|
|
11540
|
-
return setMode(KEY_MODE[key]);
|
|
11541
11967
|
case "1":
|
|
11542
11968
|
case "2":
|
|
11543
|
-
case "3":
|
|
11544
|
-
|
|
11545
|
-
if (e.altKey || e.shiftKey)
|
|
11969
|
+
case "3": {
|
|
11970
|
+
if (e.altKey || e.shiftKey || key === "s" && claimsHintKey(doc, key))
|
|
11546
11971
|
return;
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
case "x":
|
|
11550
|
-
if (e.altKey)
|
|
11972
|
+
const m = modeForKey(key, modeList);
|
|
11973
|
+
if (!m)
|
|
11551
11974
|
return;
|
|
11552
11975
|
claim();
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
return;
|
|
11976
|
+
return setMode(m);
|
|
11977
|
+
}
|
|
11556
11978
|
case "z":
|
|
11557
11979
|
if (e.altKey)
|
|
11558
11980
|
return;
|
|
@@ -11608,8 +12030,11 @@ function compare(container, options) {
|
|
|
11608
12030
|
labels[1].textContent = labelOf(1);
|
|
11609
12031
|
loupeBtn.setAttribute("aria-pressed", String(loupeOn));
|
|
11610
12032
|
loopBtn.setAttribute("aria-pressed", String(loopNow() !== null));
|
|
12033
|
+
const modePill = modeList.length > 1 ? selectionPill(modes, {
|
|
12034
|
+
rows: () => modeBtns,
|
|
12035
|
+
selected: () => modeBtns[modeList.indexOf(mode)] ?? null
|
|
12036
|
+
}) : null;
|
|
11611
12037
|
syncModeUi();
|
|
11612
|
-
syncFlicker();
|
|
11613
12038
|
const unlisten = tl?.listen({
|
|
11614
12039
|
onSeek: (t, source) => {
|
|
11615
12040
|
if (source === "play") {
|
|
@@ -11667,24 +12092,20 @@ function compare(container, options) {
|
|
|
11667
12092
|
});
|
|
11668
12093
|
root.addEventListener("keydown", onKey);
|
|
11669
12094
|
root.addEventListener("keyup", (e) => {
|
|
11670
|
-
|
|
11671
|
-
if (key === "x")
|
|
11672
|
-
holdFlick(false);
|
|
11673
|
-
else if (key === "z")
|
|
12095
|
+
if (e.key.toLowerCase() === "z")
|
|
11674
12096
|
holdLoupe(false);
|
|
11675
12097
|
});
|
|
11676
12098
|
root.addEventListener("focusout", (e) => {
|
|
11677
12099
|
if (root.contains(e.relatedTarget))
|
|
11678
12100
|
return;
|
|
11679
|
-
holdFlick(false);
|
|
11680
12101
|
holdLoupe(false);
|
|
11681
12102
|
});
|
|
11682
12103
|
const unregisterHints = options.hints === false ? undefined : hints.register(root, () => [
|
|
11683
|
-
...
|
|
12104
|
+
...modeList.length > 1 ? modeList.map((m, i) => ({
|
|
11684
12105
|
el: modeBtns[i],
|
|
11685
|
-
label: m.
|
|
11686
|
-
activate: () => setMode(m
|
|
11687
|
-
})),
|
|
12106
|
+
label: MODE_LABEL[m].long,
|
|
12107
|
+
activate: () => setMode(m)
|
|
12108
|
+
})) : [],
|
|
11688
12109
|
{ el: loopBtn, label: "Loop", activate: () => api.setLoop(!loopNow()) },
|
|
11689
12110
|
...loupeBtn.hidden ? [] : [{ el: loupeBtn, label: "Loupe", activate: () => setLoupe(!loupeOn) }],
|
|
11690
12111
|
...zoomed() ? [{ el: zoomReset, label: "Reset zoom", activate: () => api.setZoom(null) }] : [],
|
|
@@ -11709,6 +12130,7 @@ function compare(container, options) {
|
|
|
11709
12130
|
get mode() {
|
|
11710
12131
|
return mode;
|
|
11711
12132
|
},
|
|
12133
|
+
modes: modeList,
|
|
11712
12134
|
get wipe() {
|
|
11713
12135
|
return wipe;
|
|
11714
12136
|
},
|
|
@@ -11764,7 +12186,7 @@ function compare(container, options) {
|
|
|
11764
12186
|
tl?.update({ fps: next.fps });
|
|
11765
12187
|
requestDraw();
|
|
11766
12188
|
},
|
|
11767
|
-
seek: (t) => tl ? tl.seek(t) : ownSeek(t),
|
|
12189
|
+
seek: (t, o) => tl ? tl.seek(t, o?.glide ? { glide: true } : undefined) : ownSeek(t),
|
|
11768
12190
|
seekBy(d) {
|
|
11769
12191
|
const total = duration || tl?.duration || 0;
|
|
11770
12192
|
const t = Math.min(total, Math.max(0, now() + d));
|
|
@@ -11805,7 +12227,7 @@ function compare(container, options) {
|
|
|
11805
12227
|
dispose() {
|
|
11806
12228
|
disposed = true;
|
|
11807
12229
|
loop.stop();
|
|
11808
|
-
|
|
12230
|
+
modePill?.dispose();
|
|
11809
12231
|
win.clearTimeout(releaseTimer);
|
|
11810
12232
|
ro.disconnect();
|
|
11811
12233
|
nearIo?.disconnect();
|
|
@@ -11835,7 +12257,7 @@ function compare(container, options) {
|
|
|
11835
12257
|
wake();
|
|
11836
12258
|
return api;
|
|
11837
12259
|
}
|
|
11838
|
-
var LAYOUT2, FADE6, FADE_REDUCED, ZOOM,
|
|
12260
|
+
var LAYOUT2, FADE6, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
|
|
11839
12261
|
var init_compare = __esm(() => {
|
|
11840
12262
|
init_motion();
|
|
11841
12263
|
init_page_hints();
|
|
@@ -11843,21 +12265,24 @@ var init_compare = __esm(() => {
|
|
|
11843
12265
|
init_compare_gpu();
|
|
11844
12266
|
init_compare_model();
|
|
11845
12267
|
init_compare_strip();
|
|
12268
|
+
init_icons();
|
|
11846
12269
|
init_look();
|
|
11847
12270
|
init_rgba();
|
|
12271
|
+
init_selection_pill();
|
|
11848
12272
|
init_timeline();
|
|
12273
|
+
init_timeline_model();
|
|
11849
12274
|
LAYOUT2 = springMotion({ duration: 0.35, bounce: 0 });
|
|
11850
12275
|
FADE6 = springMotion(SPRINGS.snappy);
|
|
11851
12276
|
FADE_REDUCED = springMotion(FADE);
|
|
11852
12277
|
ZOOM = springMotion({ duration: 0.35, bounce: 0 });
|
|
12278
|
+
WIPE_CATCH = springMotion(CATCH_UP);
|
|
12279
|
+
GRIP = { w: 16, h: 28 };
|
|
11853
12280
|
LOUPE = { r: 88, zoom: 4 };
|
|
11854
|
-
|
|
11855
|
-
{
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
];
|
|
11860
|
-
KEY_MODE = { s: "side", w: "wipe", d: "difference" };
|
|
12281
|
+
MODE_LABEL = {
|
|
12282
|
+
side: { short: "Side", long: "Side by side" },
|
|
12283
|
+
wipe: { short: "Wipe", long: "Wipe" },
|
|
12284
|
+
difference: { short: "Difference", long: "Difference" }
|
|
12285
|
+
};
|
|
11861
12286
|
});
|
|
11862
12287
|
|
|
11863
12288
|
// src/ui/react.tsx
|
|
@@ -14757,6 +15182,7 @@ function timelineChanges(now, changed) {
|
|
|
14757
15182
|
// src/ui/picker.ts
|
|
14758
15183
|
init_motion();
|
|
14759
15184
|
init_look();
|
|
15185
|
+
init_icons();
|
|
14760
15186
|
|
|
14761
15187
|
// src/ui/picker-model.ts
|
|
14762
15188
|
function digitPick(ids, key) {
|
|
@@ -14811,7 +15237,7 @@ var CLOSE3 = springMotion(CLOSE);
|
|
|
14811
15237
|
var CARD = springMotion(SPRINGS.default);
|
|
14812
15238
|
var FADE5 = springMotion(FADE);
|
|
14813
15239
|
var DRIFT = 6;
|
|
14814
|
-
var CHECK =
|
|
15240
|
+
var CHECK = icon("check", { small: true });
|
|
14815
15241
|
function optionPicker(container, options) {
|
|
14816
15242
|
const doc = container.ownerDocument;
|
|
14817
15243
|
const win = doc.defaultView;
|
|
@@ -17667,7 +18093,7 @@ function mountTable(host, m, steps) {
|
|
|
17667
18093
|
context: () => ({ kind: "table" })
|
|
17668
18094
|
};
|
|
17669
18095
|
}
|
|
17670
|
-
var
|
|
18096
|
+
var grid2 = (v) => Math.round(v * 64) / 64;
|
|
17671
18097
|
function chapterPills(host, chapters, io) {
|
|
17672
18098
|
const doc = host.ownerDocument;
|
|
17673
18099
|
const win = doc.defaultView;
|
|
@@ -17695,9 +18121,9 @@ function chapterPills(host, chapters, io) {
|
|
|
17695
18121
|
const y = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
|
|
17696
18122
|
const w = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
|
|
17697
18123
|
const draw = () => {
|
|
17698
|
-
const left =
|
|
17699
|
-
mark.style.transform = `translate(${left}px,${
|
|
17700
|
-
mark.style.width = `${
|
|
18124
|
+
const left = grid2(x.x);
|
|
18125
|
+
mark.style.transform = `translate(${left}px,${grid2(y.x)}px)`;
|
|
18126
|
+
mark.style.width = `${grid2(x.x + w.x) - left}px`;
|
|
17701
18127
|
};
|
|
17702
18128
|
const loop = frameLoop(win, (dt) => {
|
|
17703
18129
|
x.step(dt);
|
|
@@ -18301,7 +18727,7 @@ var lookOf2 = (o) => ({
|
|
|
18301
18727
|
...o.preset ? { preset: o.preset } : {},
|
|
18302
18728
|
...o.theme ? { theme: o.theme } : {}
|
|
18303
18729
|
});
|
|
18304
|
-
var
|
|
18730
|
+
var grid3 = (v) => Math.round(v * 64) / 64;
|
|
18305
18731
|
var ICON = {
|
|
18306
18732
|
full: svg('<path d="M3 6.5V3h3.5M13 6.5V3H9.5M3 9.5V13h3.5M13 9.5V13H9.5"/>'),
|
|
18307
18733
|
exit: svg('<path d="M6.5 3v3.5H3M9.5 3v3.5H13M6.5 13V9.5H3M9.5 13V9.5H13"/>'),
|
|
@@ -18481,8 +18907,8 @@ function walkthrough(container, options) {
|
|
|
18481
18907
|
if (mark) {
|
|
18482
18908
|
pillY.step(dt);
|
|
18483
18909
|
pillH.step(dt);
|
|
18484
|
-
mark.style.transform = `translateY(${
|
|
18485
|
-
mark.style.height = `${
|
|
18910
|
+
mark.style.transform = `translateY(${grid3(pillY.x)}px)`;
|
|
18911
|
+
mark.style.height = `${grid3(pillY.x + pillH.x) - grid3(pillY.x)}px`;
|
|
18486
18912
|
more ||= !pillY.resting || !pillH.resting;
|
|
18487
18913
|
}
|
|
18488
18914
|
if (scroll.to && scroll.el) {
|
|
@@ -18642,10 +19068,10 @@ function walkthrough(container, options) {
|
|
|
18642
19068
|
const a = f.hold.getBoundingClientRect();
|
|
18643
19069
|
const b = fullRect();
|
|
18644
19070
|
const k = reduced() ? Math.round(f.p.target) : f.p.x;
|
|
18645
|
-
const left =
|
|
18646
|
-
const top =
|
|
18647
|
-
const right =
|
|
18648
|
-
const bottom =
|
|
19071
|
+
const left = grid3(a.left + (b.x - a.left) * k);
|
|
19072
|
+
const top = grid3(a.top + (b.y - a.top) * k);
|
|
19073
|
+
const right = grid3(a.right + (b.x + b.w - a.right) * k);
|
|
19074
|
+
const bottom = grid3(a.bottom + (b.y + b.h - a.bottom) * k);
|
|
18649
19075
|
const s = f.v.card.style;
|
|
18650
19076
|
s.left = `${left}px`;
|
|
18651
19077
|
s.top = `${top}px`;
|