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