@dsh-plugin/dsh-ui-experience 1.0.0-dev.43492422772 → 1.0.0-dev.43522015745
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 +257 -12
- package/lib/client.js +1154 -120
- package/lib/index.js +68 -5
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +3 -2
package/lib/client.js
CHANGED
|
@@ -12,7 +12,13 @@
|
|
|
12
12
|
spatialSplit: true,
|
|
13
13
|
quantize: false,
|
|
14
14
|
reduced: "system",
|
|
15
|
-
scroll:
|
|
15
|
+
scroll: false,
|
|
16
|
+
stream: true,
|
|
17
|
+
followScroll: true,
|
|
18
|
+
streamRevealMinMs: 250,
|
|
19
|
+
streamRevealMaxMs: 1200,
|
|
20
|
+
streamEnterMs: 220,
|
|
21
|
+
streamRollMs: 260
|
|
16
22
|
};
|
|
17
23
|
|
|
18
24
|
// src/motion-plan.ts
|
|
@@ -403,8 +409,7 @@
|
|
|
403
409
|
function bringsStylesheet(node) {
|
|
404
410
|
if (node === void 0) return false;
|
|
405
411
|
const name = node.nodeName?.toUpperCase();
|
|
406
|
-
|
|
407
|
-
return (node.querySelectorAll?.("style,link")?.length ?? 0) > 0;
|
|
412
|
+
return name === "STYLE" || name === "LINK";
|
|
408
413
|
}
|
|
409
414
|
function observeDocument() {
|
|
410
415
|
const instance = new MutationObserver((records) => {
|
|
@@ -418,7 +423,7 @@
|
|
|
418
423
|
}
|
|
419
424
|
});
|
|
420
425
|
instance.observe(document.head, { childList: true, subtree: true });
|
|
421
|
-
instance.observe(document.body, { childList: true
|
|
426
|
+
instance.observe(document.body, { childList: true });
|
|
422
427
|
return { disconnect: () => {
|
|
423
428
|
instance.disconnect();
|
|
424
429
|
} };
|
|
@@ -472,6 +477,8 @@
|
|
|
472
477
|
var MAX_MS = 460;
|
|
473
478
|
var FREE_PX = 240;
|
|
474
479
|
var GROWTH_MS_PER_PX = 0.2;
|
|
480
|
+
var MOTION_EPSILON = 0.5;
|
|
481
|
+
var FOREIGN_TOLERANCE = 1;
|
|
475
482
|
var SCROLL_KEYS = /* @__PURE__ */ new Set([
|
|
476
483
|
"ArrowUp",
|
|
477
484
|
"ArrowDown",
|
|
@@ -483,7 +490,20 @@
|
|
|
483
490
|
"End",
|
|
484
491
|
" "
|
|
485
492
|
]);
|
|
486
|
-
var
|
|
493
|
+
var EDITABLE = [
|
|
494
|
+
"input:not([type=checkbox]):not([type=radio]):not([type=button]):not([type=submit]):not([type=reset]):not([type=file]):not([type=range])",
|
|
495
|
+
"textarea",
|
|
496
|
+
"select",
|
|
497
|
+
'[contenteditable=""]',
|
|
498
|
+
'[contenteditable="true"]',
|
|
499
|
+
'[contenteditable="plaintext-only"]'
|
|
500
|
+
].join(", ");
|
|
501
|
+
var JUMP_ANIMATE_MIN = 24;
|
|
502
|
+
var WHEEL_SETTLE_PX = 0.5;
|
|
503
|
+
var PIXELS_PER_LINE = 40;
|
|
504
|
+
var PAGE_FRACTION = 0.875;
|
|
505
|
+
var PAGE_OVERLAP = 40;
|
|
506
|
+
var WHEEL_MAX_MS = 900;
|
|
487
507
|
function scrollDistance(legs) {
|
|
488
508
|
let distance = 0;
|
|
489
509
|
for (const leg of legs) {
|
|
@@ -502,15 +522,12 @@
|
|
|
502
522
|
left: Math.max(0, element.scrollWidth - element.clientWidth)
|
|
503
523
|
};
|
|
504
524
|
}
|
|
505
|
-
function scrollable(element) {
|
|
506
|
-
const max = maxima(element);
|
|
507
|
-
return max.top > 0 || max.left > 0;
|
|
508
|
-
}
|
|
509
525
|
function containers(element) {
|
|
510
526
|
const out = [];
|
|
511
527
|
let node = element.parentElement;
|
|
512
528
|
while (node !== null) {
|
|
513
|
-
|
|
529
|
+
const max = maxima(node);
|
|
530
|
+
if (max.top > 0 || max.left > 0) out.push(node);
|
|
514
531
|
node = node.parentElement;
|
|
515
532
|
}
|
|
516
533
|
return out;
|
|
@@ -521,149 +538,499 @@
|
|
|
521
538
|
scrollBy: Element.prototype.scrollBy,
|
|
522
539
|
scrollIntoView: Element.prototype.scrollIntoView
|
|
523
540
|
};
|
|
541
|
+
const topAxis = Object.getOwnPropertyDescriptor(Element.prototype, "scrollTop");
|
|
542
|
+
const leftAxis = Object.getOwnPropertyDescriptor(Element.prototype, "scrollLeft");
|
|
524
543
|
let settings;
|
|
525
544
|
let run;
|
|
545
|
+
let wheel;
|
|
526
546
|
let installed = false;
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
547
|
+
let writing = 0;
|
|
548
|
+
const shadowed = /* @__PURE__ */ new WeakMap();
|
|
549
|
+
let dispatching = false;
|
|
550
|
+
const END_GRACE_MS = 400;
|
|
551
|
+
const graceful = /* @__PURE__ */ new Set();
|
|
552
|
+
function writeAxis(element, axis, value) {
|
|
553
|
+
const accessor = axis === "top" ? topAxis : leftAxis;
|
|
554
|
+
if (accessor !== void 0) {
|
|
555
|
+
writing += 1;
|
|
556
|
+
try {
|
|
557
|
+
accessor.set.call(element, value);
|
|
558
|
+
} finally {
|
|
559
|
+
writing -= 1;
|
|
560
|
+
}
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (axis === "top") element.scrollTop = value;
|
|
564
|
+
else element.scrollLeft = value;
|
|
565
|
+
}
|
|
566
|
+
function clearGrace() {
|
|
567
|
+
for (const element of graceful) shadowed.delete(element);
|
|
568
|
+
graceful.clear();
|
|
569
|
+
}
|
|
570
|
+
function hold(element) {
|
|
571
|
+
shadowed.set(element, { until: Number.POSITIVE_INFINITY, top: 0, left: 0 });
|
|
572
|
+
}
|
|
573
|
+
function release(element, notify) {
|
|
574
|
+
if (!shadowed.has(element)) return;
|
|
575
|
+
shadowed.set(element, {
|
|
576
|
+
until: performance.now() + END_GRACE_MS,
|
|
577
|
+
top: element.scrollTop,
|
|
578
|
+
left: element.scrollLeft
|
|
579
|
+
});
|
|
580
|
+
graceful.add(element);
|
|
581
|
+
if (!notify) return;
|
|
582
|
+
queueMicrotask(() => {
|
|
583
|
+
if (shadowed.get(element)?.until === Number.POSITIVE_INFINITY) return;
|
|
584
|
+
dispatching = true;
|
|
585
|
+
try {
|
|
586
|
+
element.dispatchEvent(new Event("scrollend"));
|
|
587
|
+
} finally {
|
|
588
|
+
dispatching = false;
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
function cancel(notify = true) {
|
|
593
|
+
stopWheel();
|
|
594
|
+
const active = run;
|
|
595
|
+
if (active === void 0) return;
|
|
596
|
+
window.cancelAnimationFrame(active.frame);
|
|
530
597
|
run = void 0;
|
|
598
|
+
for (const leg of active.legs) release(leg.element, notify);
|
|
599
|
+
for (const settle of active.done) settle();
|
|
531
600
|
}
|
|
532
601
|
function step(now) {
|
|
533
602
|
const active = run;
|
|
534
603
|
if (active === void 0) return;
|
|
535
604
|
const elapsed = now - active.started;
|
|
536
605
|
const progress = active.duration <= 0 ? 1 : Math.min(1, elapsed / active.duration);
|
|
537
|
-
const eased = easeCurve(effectCurvePoints(settings
|
|
606
|
+
const eased = easeCurve(effectCurvePoints(settings.mode), progress);
|
|
538
607
|
for (let index = 0; index < active.legs.length; index += 1) {
|
|
539
608
|
const leg = active.legs[index];
|
|
540
609
|
const written = active.written[index];
|
|
541
610
|
if (leg === void 0 || written === void 0) continue;
|
|
542
|
-
if (Math.abs(leg.element.scrollTop - written.top) >
|
|
611
|
+
if (Math.abs(leg.element.scrollTop - written.top) > FOREIGN_TOLERANCE || Math.abs(leg.element.scrollLeft - written.left) > FOREIGN_TOLERANCE) {
|
|
543
612
|
cancel();
|
|
544
613
|
return;
|
|
545
614
|
}
|
|
615
|
+
}
|
|
616
|
+
for (const leg of active.legs) {
|
|
546
617
|
const top = progress >= 1 ? leg.toTop : leg.fromTop + (leg.toTop - leg.fromTop) * eased;
|
|
547
618
|
const left = progress >= 1 ? leg.toLeft : leg.fromLeft + (leg.toLeft - leg.fromLeft) * eased;
|
|
548
|
-
if (leg.element.scrollTop !== top) leg.element
|
|
549
|
-
if (leg.element.scrollLeft !== left) leg.element
|
|
619
|
+
if (leg.element.scrollTop !== top) writeAxis(leg.element, "top", top);
|
|
620
|
+
if (leg.element.scrollLeft !== left) writeAxis(leg.element, "left", left);
|
|
621
|
+
}
|
|
622
|
+
for (let index = 0; index < active.legs.length; index += 1) {
|
|
623
|
+
const leg = active.legs[index];
|
|
624
|
+
const written = active.written[index];
|
|
625
|
+
if (leg === void 0 || written === void 0) continue;
|
|
550
626
|
written.top = leg.element.scrollTop;
|
|
551
627
|
written.left = leg.element.scrollLeft;
|
|
552
628
|
}
|
|
553
629
|
if (progress >= 1) {
|
|
554
630
|
run = void 0;
|
|
631
|
+
for (const leg of active.legs) release(leg.element, true);
|
|
632
|
+
for (const settle of active.done) settle();
|
|
555
633
|
return;
|
|
556
634
|
}
|
|
557
635
|
active.frame = window.requestAnimationFrame(step);
|
|
558
636
|
}
|
|
559
|
-
function start(legs) {
|
|
637
|
+
function start(legs, done) {
|
|
560
638
|
cancel();
|
|
561
639
|
const active = settings;
|
|
562
|
-
|
|
640
|
+
for (const leg of legs) hold(leg.element);
|
|
563
641
|
run = {
|
|
564
|
-
legs,
|
|
642
|
+
legs: [...legs],
|
|
565
643
|
started: performance.now(),
|
|
566
644
|
duration: scrollDurationMs(scrollDistance(legs), active),
|
|
567
645
|
written: legs.map((leg) => ({ top: leg.element.scrollTop, left: leg.element.scrollLeft })),
|
|
568
|
-
frame: window.requestAnimationFrame(step)
|
|
646
|
+
frame: window.requestAnimationFrame(step),
|
|
647
|
+
done: [done]
|
|
569
648
|
};
|
|
570
649
|
}
|
|
571
|
-
function
|
|
572
|
-
|
|
573
|
-
if (
|
|
574
|
-
|
|
650
|
+
function merge(legs, done) {
|
|
651
|
+
const active = run;
|
|
652
|
+
if (active === void 0) return false;
|
|
653
|
+
if (active.legs.length !== legs.length) return false;
|
|
654
|
+
for (let index = 0; index < legs.length; index += 1) {
|
|
655
|
+
if (active.legs[index]?.element !== legs[index]?.element) return false;
|
|
656
|
+
}
|
|
657
|
+
active.legs = [...legs];
|
|
658
|
+
active.started = performance.now();
|
|
659
|
+
active.duration = scrollDurationMs(scrollDistance(legs), settings);
|
|
660
|
+
active.written = legs.map((leg) => ({ top: leg.element.scrollTop, left: leg.element.scrollLeft }));
|
|
661
|
+
active.done.push(done);
|
|
662
|
+
return true;
|
|
575
663
|
}
|
|
576
|
-
function
|
|
664
|
+
function wantsInstant() {
|
|
665
|
+
const active = settings;
|
|
666
|
+
if (active.reduced === "always") return true;
|
|
667
|
+
return active.reduced === "system" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
668
|
+
}
|
|
669
|
+
const glides = /* @__PURE__ */ new Map();
|
|
670
|
+
const lastWrite = /* @__PURE__ */ new WeakMap();
|
|
671
|
+
const GLIDE_MIN_PX = 4;
|
|
672
|
+
const GLIDE_MAX_PX = 600;
|
|
673
|
+
const GLIDE_MIN_MS = 90;
|
|
674
|
+
const GLIDE_MAX_MS = 300;
|
|
675
|
+
const GLIDE_PX_PER_MS = 1.8;
|
|
676
|
+
const GLIDE_FRAME_GAP_MS = 32;
|
|
677
|
+
const GLIDE_MAX_LAG_PX = 40;
|
|
678
|
+
function shouldGlide(element, value, now) {
|
|
679
|
+
const active = settings;
|
|
680
|
+
if (active === void 0 || !active.followScroll) return false;
|
|
681
|
+
if (wantsInstant()) return false;
|
|
577
682
|
const max = maxima(element);
|
|
578
|
-
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
683
|
+
if (max.top <= 0) return false;
|
|
684
|
+
const delta = value - topAxis.get.call(element);
|
|
685
|
+
const size = Math.abs(delta);
|
|
686
|
+
if (size < GLIDE_MIN_PX || size > GLIDE_MAX_PX) return false;
|
|
687
|
+
if (delta > 0 && Math.abs(value - max.top) > 4) return false;
|
|
688
|
+
if (max.top - topAxis.get.call(element) > GLIDE_MAX_LAG_PX) return false;
|
|
689
|
+
const previous = lastWrite.get(element);
|
|
690
|
+
lastWrite.set(element, now);
|
|
691
|
+
if (previous !== void 0 && now - previous < GLIDE_FRAME_GAP_MS) return false;
|
|
692
|
+
return true;
|
|
582
693
|
}
|
|
583
|
-
function
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
694
|
+
function startGlide(element, target) {
|
|
695
|
+
const active = settings;
|
|
696
|
+
const existing = glides.get(element);
|
|
697
|
+
if (existing !== void 0) window.cancelAnimationFrame(existing.frame);
|
|
698
|
+
else hold(element);
|
|
699
|
+
const from = topAxis.get.call(element);
|
|
700
|
+
const raw = Math.abs(target - from) / GLIDE_PX_PER_MS;
|
|
701
|
+
const duration = Math.min(GLIDE_MAX_MS, Math.max(GLIDE_MIN_MS, raw)) * active.durationScale;
|
|
702
|
+
const glide = {
|
|
703
|
+
target,
|
|
704
|
+
from,
|
|
705
|
+
started: performance.now(),
|
|
706
|
+
duration: active.quantize ? quantizeMs(duration) : duration,
|
|
707
|
+
frame: 0,
|
|
708
|
+
written: from
|
|
709
|
+
};
|
|
710
|
+
glides.set(element, glide);
|
|
711
|
+
const mode = active.mode;
|
|
712
|
+
const step2 = (now) => {
|
|
713
|
+
const live = glides.get(element);
|
|
714
|
+
if (live !== glide) return;
|
|
715
|
+
if (Math.abs(topAxis.get.call(element) - glide.written) > 1) {
|
|
716
|
+
finishGlide(element, false);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const progress = Math.min(1, (now - glide.started) / Math.max(1, glide.duration));
|
|
720
|
+
const eased = easeCurve(effectCurvePoints(mode), progress);
|
|
721
|
+
writeAxis(element, "top", glide.from + (glide.target - glide.from) * eased);
|
|
722
|
+
glide.written = topAxis.get.call(element);
|
|
723
|
+
if (progress >= 1) {
|
|
724
|
+
finishGlide(element, true);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
glide.frame = window.requestAnimationFrame(step2);
|
|
591
728
|
};
|
|
729
|
+
glide.frame = window.requestAnimationFrame(step2);
|
|
592
730
|
}
|
|
593
|
-
function
|
|
594
|
-
const
|
|
595
|
-
if (
|
|
596
|
-
|
|
597
|
-
|
|
731
|
+
function finishGlide(element, landed) {
|
|
732
|
+
const glide = glides.get(element);
|
|
733
|
+
if (glide === void 0) return;
|
|
734
|
+
window.cancelAnimationFrame(glide.frame);
|
|
735
|
+
glides.delete(element);
|
|
736
|
+
if (landed) writeAxis(element, "top", glide.target);
|
|
737
|
+
release(element, true);
|
|
738
|
+
}
|
|
739
|
+
function finishGlides() {
|
|
740
|
+
for (const [element] of glides) finishGlide(element, true);
|
|
741
|
+
}
|
|
742
|
+
function stopWheel() {
|
|
743
|
+
const active = wheel;
|
|
744
|
+
if (active === void 0) return;
|
|
745
|
+
window.cancelAnimationFrame(active.frame);
|
|
746
|
+
wheel = void 0;
|
|
747
|
+
release(active.element, true);
|
|
748
|
+
}
|
|
749
|
+
function wheelStep(now) {
|
|
750
|
+
const active = wheel;
|
|
751
|
+
if (active === void 0) return;
|
|
752
|
+
const element = active.element;
|
|
753
|
+
const progress = active.duration <= 0 ? 1 : Math.min(1, (now - active.started) / active.duration);
|
|
754
|
+
const eased = easeCurve(effectCurvePoints(settings.mode), progress);
|
|
755
|
+
if (progress >= 1) {
|
|
756
|
+
writeAxis(element, "top", active.top);
|
|
757
|
+
writeAxis(element, "left", active.left);
|
|
758
|
+
wheel = void 0;
|
|
759
|
+
release(element, true);
|
|
598
760
|
return;
|
|
599
761
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
762
|
+
writeAxis(element, "top", active.fromTop + (active.top - active.fromTop) * eased);
|
|
763
|
+
writeAxis(element, "left", active.fromLeft + (active.left - active.fromLeft) * eased);
|
|
764
|
+
active.frame = window.requestAnimationFrame(wheelStep);
|
|
603
765
|
}
|
|
604
|
-
function
|
|
605
|
-
const
|
|
606
|
-
if (
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
766
|
+
function onWheel(event) {
|
|
767
|
+
const active = settings;
|
|
768
|
+
if (active === void 0) return false;
|
|
769
|
+
if (event.ctrlKey || event.metaKey) return false;
|
|
770
|
+
if (wantsInstant()) return false;
|
|
771
|
+
const target = event.target;
|
|
772
|
+
if (target === null) return false;
|
|
773
|
+
if (target instanceof Element && target.closest(EDITABLE) !== null) return false;
|
|
774
|
+
if (event.deltaX === 0 && event.deltaY === 0) return false;
|
|
775
|
+
const element = scrollportOf(target, {
|
|
776
|
+
top: event.deltaY !== 0,
|
|
777
|
+
left: event.deltaX !== 0
|
|
778
|
+
});
|
|
779
|
+
if (element === null) return false;
|
|
780
|
+
const max = maxima(element);
|
|
781
|
+
const delta = pixelDelta(event, element);
|
|
782
|
+
const base = wheel?.element === element ? wheel : void 0;
|
|
783
|
+
const fromTop = element.scrollTop;
|
|
784
|
+
const fromLeft = element.scrollLeft;
|
|
785
|
+
const top = Math.min(max.top, Math.max(0, (base?.top ?? fromTop) + delta.y));
|
|
786
|
+
const left = Math.min(max.left, Math.max(0, (base?.left ?? fromLeft) + delta.x));
|
|
787
|
+
if (Math.abs(top - fromTop) < WHEEL_SETTLE_PX && Math.abs(left - fromLeft) < WHEEL_SETTLE_PX) return false;
|
|
788
|
+
event.preventDefault();
|
|
789
|
+
const stopped = run;
|
|
790
|
+
if (stopped !== void 0) {
|
|
791
|
+
window.cancelAnimationFrame(stopped.frame);
|
|
792
|
+
run = void 0;
|
|
793
|
+
for (const leg of stopped.legs) release(leg.element, true);
|
|
794
|
+
for (const settle of stopped.done) settle();
|
|
610
795
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
796
|
+
if (base === void 0) hold(element);
|
|
797
|
+
else window.cancelAnimationFrame(base.frame);
|
|
798
|
+
const travelled = Math.max(Math.abs(top - fromTop), Math.abs(left - fromLeft));
|
|
799
|
+
const segment = {
|
|
800
|
+
element,
|
|
801
|
+
top,
|
|
802
|
+
left,
|
|
803
|
+
fromTop,
|
|
804
|
+
fromLeft,
|
|
805
|
+
started: performance.now(),
|
|
806
|
+
duration: Math.min(WHEEL_MAX_MS, scrollDurationMs(travelled, active)),
|
|
807
|
+
frame: 0
|
|
808
|
+
};
|
|
809
|
+
wheel = segment;
|
|
810
|
+
segment.frame = window.requestAnimationFrame(wheelStep);
|
|
811
|
+
return true;
|
|
614
812
|
}
|
|
615
|
-
function
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
;
|
|
619
|
-
|
|
620
|
-
|
|
813
|
+
function scrollportOf(node, axis) {
|
|
814
|
+
let current = node;
|
|
815
|
+
while (current !== null) {
|
|
816
|
+
const max = maxima(current);
|
|
817
|
+
if (axis.top && max.top > 0 || axis.left && max.left > 0) return current;
|
|
818
|
+
current = current.parentElement;
|
|
621
819
|
}
|
|
622
|
-
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
function pageStep(length) {
|
|
823
|
+
return Math.max(1, Math.max(length * PAGE_FRACTION, length - PAGE_OVERLAP));
|
|
824
|
+
}
|
|
825
|
+
function pixelDelta(event, element) {
|
|
826
|
+
if (event.deltaMode === 0) return { x: event.deltaX, y: event.deltaY };
|
|
827
|
+
const unitY = event.deltaMode === 1 ? PIXELS_PER_LINE : pageStep(element.clientHeight);
|
|
828
|
+
const unitX = event.deltaMode === 1 ? PIXELS_PER_LINE : pageStep(element.clientWidth);
|
|
829
|
+
return { x: event.deltaX * unitX, y: event.deltaY * unitY };
|
|
830
|
+
}
|
|
831
|
+
function animates(behavior, distance) {
|
|
832
|
+
return behavior === "instant" && Math.max(distance.top, distance.left) >= JUMP_ANIMATE_MIN;
|
|
833
|
+
}
|
|
834
|
+
function scrollIntoView(...args) {
|
|
835
|
+
const options = args[0];
|
|
836
|
+
const requested = typeof options === "object" && options !== null ? options : void 0;
|
|
837
|
+
const active = settings;
|
|
838
|
+
if (active === void 0 || requested?.behavior !== "smooth") return shipped.scrollIntoView.apply(this, args);
|
|
839
|
+
const jump = { ...requested, behavior: "instant" };
|
|
840
|
+
if (wantsInstant()) return shipped.scrollIntoView.call(this, jump);
|
|
623
841
|
const tracked = containers(this);
|
|
842
|
+
for (const element of tracked) hold(element);
|
|
624
843
|
const before = tracked.map((element) => ({ top: element.scrollTop, left: element.scrollLeft }));
|
|
625
|
-
shipped.scrollIntoView.call(this,
|
|
844
|
+
shipped.scrollIntoView.call(this, jump);
|
|
626
845
|
const legs = [];
|
|
627
846
|
for (let index = 0; index < tracked.length; index += 1) {
|
|
628
847
|
const element = tracked[index];
|
|
629
848
|
const origin = before[index];
|
|
630
849
|
if (element === void 0 || origin === void 0) continue;
|
|
631
850
|
const landed = { top: element.scrollTop, left: element.scrollLeft };
|
|
632
|
-
element
|
|
633
|
-
element
|
|
634
|
-
|
|
851
|
+
writeAxis(element, "top", origin.top);
|
|
852
|
+
writeAxis(element, "left", origin.left);
|
|
853
|
+
const moved = Math.abs(landed.top - origin.top) > MOTION_EPSILON || Math.abs(landed.left - origin.left) > MOTION_EPSILON;
|
|
854
|
+
if (!moved) {
|
|
855
|
+
if (run?.legs.some((leg) => leg.element === element)) continue;
|
|
856
|
+
release(element, false);
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
635
859
|
legs.push({ element, fromTop: origin.top, fromLeft: origin.left, toTop: landed.top, toLeft: landed.left });
|
|
636
860
|
}
|
|
637
|
-
if (legs.length
|
|
861
|
+
if (legs.length === 0) return Promise.resolve();
|
|
862
|
+
return new Promise((resolve) => {
|
|
863
|
+
if (!merge(legs, resolve)) start(legs, resolve);
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
function readOffsets(options, base) {
|
|
867
|
+
return {
|
|
868
|
+
left: options.left === void 0 ? void 0 : base.left + options.left,
|
|
869
|
+
top: options.top === void 0 ? void 0 : base.top + options.top
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
function legFor(element, top, left) {
|
|
873
|
+
const max = maxima(element);
|
|
874
|
+
const toTop = top === void 0 ? element.scrollTop : Math.min(max.top, Math.max(0, top));
|
|
875
|
+
const toLeft = left === void 0 ? element.scrollLeft : Math.min(max.left, Math.max(0, left));
|
|
876
|
+
if (Math.abs(toTop - element.scrollTop) < MOTION_EPSILON && Math.abs(toLeft - element.scrollLeft) < MOTION_EPSILON) return void 0;
|
|
877
|
+
return { element, fromTop: element.scrollTop, fromLeft: element.scrollLeft, toTop, toLeft };
|
|
878
|
+
}
|
|
879
|
+
function patchedOffset(element, args, shippedMethod, relative) {
|
|
880
|
+
if (writing > 0) return shippedMethod.apply(element, args);
|
|
881
|
+
const options = args[0];
|
|
882
|
+
const behavior = typeof options === "object" && options !== null ? options.behavior : void 0;
|
|
883
|
+
const active = settings;
|
|
884
|
+
if (active === void 0 || wantsInstant()) return shippedMethod.apply(element, args);
|
|
885
|
+
const base = relative ? { top: element.scrollTop, left: element.scrollLeft } : { top: 0, left: 0 };
|
|
886
|
+
const request = readOffsets(options, base);
|
|
887
|
+
const leg = legFor(element, request.top, request.left);
|
|
888
|
+
if (leg === void 0) return Promise.resolve();
|
|
889
|
+
if (behavior === "smooth") {
|
|
890
|
+
return new Promise((resolve) => {
|
|
891
|
+
if (!merge([leg], resolve)) start([leg], resolve);
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
if (!animates(behavior, {
|
|
895
|
+
top: Math.abs(leg.toTop - leg.fromTop),
|
|
896
|
+
left: Math.abs(leg.toLeft - leg.fromLeft)
|
|
897
|
+
})) {
|
|
898
|
+
shippedMethod.apply(element, args);
|
|
899
|
+
return Promise.resolve();
|
|
900
|
+
}
|
|
901
|
+
return new Promise((resolve) => {
|
|
902
|
+
start([leg], resolve);
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
function scrollTo(...args) {
|
|
906
|
+
return patchedOffset(this, args, shipped.scrollTo, false);
|
|
907
|
+
}
|
|
908
|
+
function scrollBy(...args) {
|
|
909
|
+
return patchedOffset(this, args, shipped.scrollBy, true);
|
|
910
|
+
}
|
|
911
|
+
function resolveTarget(target) {
|
|
912
|
+
if (target === null) return null;
|
|
913
|
+
return target;
|
|
914
|
+
}
|
|
915
|
+
function onScrollEnd(event) {
|
|
916
|
+
if (dispatching) return;
|
|
917
|
+
const target = resolveTarget(event.target);
|
|
918
|
+
if (target === null) return;
|
|
919
|
+
const entry = shadowed.get(target);
|
|
920
|
+
if (entry === void 0) return;
|
|
921
|
+
if (entry.until === Number.POSITIVE_INFINITY) {
|
|
922
|
+
event.stopImmediatePropagation();
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const unmoved = Math.abs(target.scrollTop - entry.top) <= FOREIGN_TOLERANCE && Math.abs(target.scrollLeft - entry.left) <= FOREIGN_TOLERANCE;
|
|
926
|
+
if (!unmoved || performance.now() > entry.until) {
|
|
927
|
+
shadowed.delete(target);
|
|
928
|
+
graceful.delete(target);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
event.stopImmediatePropagation();
|
|
932
|
+
}
|
|
933
|
+
function onWheelBubble(event) {
|
|
934
|
+
cancelIfOwned(event.target);
|
|
935
|
+
if (event.defaultPrevented) return;
|
|
936
|
+
onWheel(event);
|
|
937
|
+
}
|
|
938
|
+
function cancelIfOwned(target) {
|
|
939
|
+
for (const [element] of glides) {
|
|
940
|
+
if (target === null || element.contains(target)) finishGlide(element, false);
|
|
941
|
+
}
|
|
942
|
+
const active = run;
|
|
943
|
+
if (active === void 0 || target === null) return false;
|
|
944
|
+
if (!active.legs.some((leg) => leg.element.contains(target))) return false;
|
|
945
|
+
cancel();
|
|
946
|
+
return true;
|
|
638
947
|
}
|
|
639
948
|
function onTakeover(event) {
|
|
640
|
-
|
|
641
|
-
if (
|
|
642
|
-
|
|
949
|
+
const key = event.type === "keydown";
|
|
950
|
+
if (key) {
|
|
951
|
+
if (event.key === void 0 || !SCROLL_KEYS.has(event.key)) return;
|
|
952
|
+
const focus = event.target;
|
|
953
|
+
if (focus instanceof Element && focus.closest(EDITABLE) !== null) return;
|
|
954
|
+
}
|
|
955
|
+
if (run === void 0) {
|
|
956
|
+
clearGrace();
|
|
643
957
|
return;
|
|
644
958
|
}
|
|
645
|
-
if (
|
|
959
|
+
if (key) cancel();
|
|
960
|
+
else cancelIfOwned(event.target);
|
|
961
|
+
}
|
|
962
|
+
function ownsPatches() {
|
|
963
|
+
const prototype = Element.prototype;
|
|
964
|
+
return prototype.scrollTo === scrollTo && prototype.scrollBy === scrollBy && prototype.scrollIntoView === scrollIntoView;
|
|
646
965
|
}
|
|
647
966
|
function install() {
|
|
648
967
|
if (installed) return;
|
|
968
|
+
const current = Element.prototype;
|
|
969
|
+
if (current.scrollTo !== shipped.scrollTo || current.scrollIntoView !== shipped.scrollIntoView) {
|
|
970
|
+
console.error("ui-experience: scroll methods were already replaced when the motion layer started; scripted scrolling is left off until the page reloads");
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
649
973
|
installed = true;
|
|
650
974
|
const prototype = Element.prototype;
|
|
651
|
-
prototype.scrollTo =
|
|
652
|
-
prototype.scrollBy =
|
|
653
|
-
prototype.scrollIntoView =
|
|
654
|
-
|
|
655
|
-
|
|
975
|
+
prototype.scrollTo = scrollTo;
|
|
976
|
+
prototype.scrollBy = scrollBy;
|
|
977
|
+
prototype.scrollIntoView = scrollIntoView;
|
|
978
|
+
if (topAxis !== void 0) {
|
|
979
|
+
Object.defineProperty(Element.prototype, "scrollTop", {
|
|
980
|
+
configurable: true,
|
|
981
|
+
enumerable: topAxis.enumerable,
|
|
982
|
+
get() {
|
|
983
|
+
const glide = glides.get(this);
|
|
984
|
+
return glide === void 0 ? topAxis.get.call(this) : glide.target;
|
|
985
|
+
},
|
|
986
|
+
set(value) {
|
|
987
|
+
if (writing > 0 || !shouldGlide(this, value, performance.now())) {
|
|
988
|
+
topAxis.set.call(this, value);
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
startGlide(this, value);
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
window.addEventListener("scrollend", onScrollEnd, true);
|
|
996
|
+
window.addEventListener(
|
|
997
|
+
"wheel",
|
|
998
|
+
onWheelBubble,
|
|
999
|
+
{ capture: false, passive: false }
|
|
1000
|
+
);
|
|
1001
|
+
for (const kind of ["touchstart", "keydown"]) {
|
|
1002
|
+
window.addEventListener(kind, onTakeover, { capture: true, passive: true });
|
|
656
1003
|
}
|
|
657
1004
|
}
|
|
658
1005
|
function uninstall() {
|
|
659
|
-
|
|
1006
|
+
finishGlides();
|
|
1007
|
+
cancel(false);
|
|
1008
|
+
clearGrace();
|
|
660
1009
|
if (!installed) return;
|
|
661
1010
|
installed = false;
|
|
1011
|
+
if (!ownsPatches()) {
|
|
1012
|
+
console.error("ui-experience: the scroll methods were replaced while scripted scrolling was active; leaving the current implementations in place instead of restoring stale ones");
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
662
1015
|
const prototype = Element.prototype;
|
|
663
1016
|
prototype.scrollTo = shipped.scrollTo;
|
|
664
1017
|
prototype.scrollBy = shipped.scrollBy;
|
|
665
1018
|
prototype.scrollIntoView = shipped.scrollIntoView;
|
|
666
|
-
|
|
1019
|
+
if (topAxis !== void 0) {
|
|
1020
|
+
Object.defineProperty(Element.prototype, "scrollTop", {
|
|
1021
|
+
configurable: true,
|
|
1022
|
+
enumerable: topAxis.enumerable,
|
|
1023
|
+
get: topAxis.get,
|
|
1024
|
+
set: topAxis.set
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
window.removeEventListener("scrollend", onScrollEnd, true);
|
|
1028
|
+
window.removeEventListener(
|
|
1029
|
+
"wheel",
|
|
1030
|
+
onWheelBubble,
|
|
1031
|
+
false
|
|
1032
|
+
);
|
|
1033
|
+
for (const kind of ["touchstart", "keydown"]) {
|
|
667
1034
|
window.removeEventListener(kind, onTakeover, true);
|
|
668
1035
|
}
|
|
669
1036
|
}
|
|
@@ -672,7 +1039,552 @@
|
|
|
672
1039
|
uninstall();
|
|
673
1040
|
settings = next;
|
|
674
1041
|
if (!next.scroll || next.mode === "off") return;
|
|
675
|
-
|
|
1042
|
+
install();
|
|
1043
|
+
},
|
|
1044
|
+
restore() {
|
|
1045
|
+
uninstall();
|
|
1046
|
+
settings = void 0;
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// src/stream.ts
|
|
1052
|
+
var STREAM_ATTRIBUTE = "data-dsh-uix-stream";
|
|
1053
|
+
var LAST_ATTRIBUTE = "data-dsh-uix-last";
|
|
1054
|
+
var WIDTH_ATTRIBUTE = "data-dsh-uix-width";
|
|
1055
|
+
var ENTER_ATTRIBUTE = "data-dsh-uix-enter";
|
|
1056
|
+
var ROLL_ATTRIBUTE = "data-dsh-uix-rolling";
|
|
1057
|
+
var ROW = "[data-chat-flow-kind], [data-chat-anchor-key], [data-submission-echo], [data-pending-steering]";
|
|
1058
|
+
var ENTER_FALLBACK_MS = 220;
|
|
1059
|
+
var LIVE = "[data-streaming], [data-process-activity] [data-shimmer]";
|
|
1060
|
+
var MIN_REVEAL_PX = 0.5;
|
|
1061
|
+
var REVEAL_EXPONENT = 0.62;
|
|
1062
|
+
var ONE_LINE_PX = 24;
|
|
1063
|
+
var FADE_BAND = 0.45;
|
|
1064
|
+
var SINGLE_LINE_RATIO = 1.5;
|
|
1065
|
+
var MIN_TILT_DEG = 15;
|
|
1066
|
+
var MAX_TILT_DEG = 45;
|
|
1067
|
+
var STREAM_CSS = `
|
|
1068
|
+
@property --dsh-uix-reveal {
|
|
1069
|
+
syntax: '<length>';
|
|
1070
|
+
inherits: false;
|
|
1071
|
+
initial-value: 0px;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
@property --dsh-uix-edge {
|
|
1075
|
+
syntax: '<length>';
|
|
1076
|
+
inherits: false;
|
|
1077
|
+
initial-value: 0px;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
@property --dsh-uix-tilt {
|
|
1081
|
+
syntax: '<angle>';
|
|
1082
|
+
inherits: false;
|
|
1083
|
+
initial-value: 180deg;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/* The mask is a tilted reveal line with a soft trailing edge, so text arrives out
|
|
1087
|
+
of a gradient rather than at a hard boundary. The tilt is held inside 15deg-45deg
|
|
1088
|
+
off horizontal, which is what makes the edge read as a diagonal wipe; any
|
|
1089
|
+
shallower and it looks like a flat line across the block. */
|
|
1090
|
+
[${STREAM_ATTRIBUTE}="reveal"] {
|
|
1091
|
+
--dsh-uix-reveal: 0px;
|
|
1092
|
+
mask-image: linear-gradient(
|
|
1093
|
+
var(--dsh-uix-tilt),
|
|
1094
|
+
#000 calc(var(--dsh-uix-reveal) - ${FADE_BAND}lh),
|
|
1095
|
+
transparent var(--dsh-uix-reveal)
|
|
1096
|
+
);
|
|
1097
|
+
mask-size: 100% 100%;
|
|
1098
|
+
mask-repeat: no-repeat;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/* One-line labels: a horizontal edge at the end of the text a reader has already
|
|
1102
|
+
seen sweeps out to the new width, so only the arriving characters are revealed.
|
|
1103
|
+
|
|
1104
|
+
The edge is a registered custom property driven by a transition. Keyframing the
|
|
1105
|
+
gradient itself does not work: Chrome snaps between two linear-gradients instead of
|
|
1106
|
+
interpolating them, which reads as no animation at all.
|
|
1107
|
+
|
|
1108
|
+
A TextShimmer root holds a decorative copy of its children for its own sweep, so
|
|
1109
|
+
its mask goes on the readable first child. The reasoning summary masks itself,
|
|
1110
|
+
which also replaces the static fade the application puts on it. */
|
|
1111
|
+
[${STREAM_ATTRIBUTE}="append"][data-shimmer] > :first-child,
|
|
1112
|
+
[${STREAM_ATTRIBUTE}="append"][data-streaming]:not([data-shimmer]) {
|
|
1113
|
+
--dsh-uix-edge: 100000px;
|
|
1114
|
+
mask-image: linear-gradient(
|
|
1115
|
+
to right,
|
|
1116
|
+
#000 calc(var(--dsh-uix-edge) - 1.2ch),
|
|
1117
|
+
transparent var(--dsh-uix-edge)
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/* A TextShimmer root draws a second, inert copy of its children and sweeps a band of
|
|
1122
|
+
light through it \u2014 the application's own idle shimmer. This layer moves a mask over
|
|
1123
|
+
the readable copy instead, so the two would run at once: a band of light crossing a
|
|
1124
|
+
line whose text is also being uncovered, which reads as two animations fighting.
|
|
1125
|
+
|
|
1126
|
+
The decorative copy is always the second child, and only exists while the label is
|
|
1127
|
+
active, so this matches nothing on an idle one. */
|
|
1128
|
+
[${STREAM_ATTRIBUTE}="append"][data-shimmer] > :nth-child(2),
|
|
1129
|
+
[${STREAM_ATTRIBUTE}="append"] [data-shimmer] > :nth-child(2) {
|
|
1130
|
+
display: none;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/* A label arrives as a whole line and rises into place; only later growth is swept,
|
|
1134
|
+
so arrival and extension read differently. */
|
|
1135
|
+
[${STREAM_ATTRIBUTE}="append"][data-shimmer][${ROLL_ATTRIBUTE}] > :first-child,
|
|
1136
|
+
[${STREAM_ATTRIBUTE}="append"][data-streaming]:not([data-shimmer])[${ROLL_ATTRIBUTE}] {
|
|
1137
|
+
animation: dsh-uix-arrive var(--dsh-uix-roll, 260ms) var(--dsh-uix-ease, ease) both;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
@keyframes dsh-uix-arrive {
|
|
1141
|
+
from { transform: translateY(0.85lh); opacity: 0; }
|
|
1142
|
+
to { transform: translateY(0); opacity: 1; }
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/* A row that arrives while the transcript is following its tail rises into place.
|
|
1146
|
+
Only opacity and transform are animated, so the height the follow loop reads back
|
|
1147
|
+
right after its write is the height it asked for. */
|
|
1148
|
+
[${ENTER_ATTRIBUTE}] {
|
|
1149
|
+
animation: dsh-uix-enter var(--dsh-uix-enter, 220ms) var(--dsh-uix-ease, ease) both;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
@keyframes dsh-uix-enter {
|
|
1153
|
+
from { opacity: 0; transform: translateY(-6px); }
|
|
1154
|
+
to { opacity: 1; transform: none; }
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1158
|
+
[${STREAM_ATTRIBUTE}="reveal"] {
|
|
1159
|
+
mask-image: none;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
[${ENTER_ATTRIBUTE}] {
|
|
1163
|
+
animation: none;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
[${STREAM_ATTRIBUTE}="append"][data-shimmer][${ROLL_ATTRIBUTE}] > :first-child,
|
|
1167
|
+
[${STREAM_ATTRIBUTE}="append"][data-streaming]:not([data-shimmer])[${ROLL_ATTRIBUTE}] {
|
|
1168
|
+
animation: none;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
[${STREAM_ATTRIBUTE}="append"][data-shimmer] > :first-child,
|
|
1172
|
+
[${STREAM_ATTRIBUTE}="append"][data-streaming]:not([data-shimmer]) {
|
|
1173
|
+
mask-image: none;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
`;
|
|
1177
|
+
function createStreamMotion() {
|
|
1178
|
+
const reveals = /* @__PURE__ */ new Map();
|
|
1179
|
+
let knownRows = /* @__PURE__ */ new WeakSet();
|
|
1180
|
+
let following = false;
|
|
1181
|
+
let sheet;
|
|
1182
|
+
let observer;
|
|
1183
|
+
let settings;
|
|
1184
|
+
let pending = false;
|
|
1185
|
+
let installed = false;
|
|
1186
|
+
let ease = "";
|
|
1187
|
+
let rollMs = 260;
|
|
1188
|
+
let enterMs = ENTER_FALLBACK_MS;
|
|
1189
|
+
let revealMinMs = 250;
|
|
1190
|
+
let revealMaxMs = 1200;
|
|
1191
|
+
const generation = /* @__PURE__ */ new WeakMap();
|
|
1192
|
+
const sweeps = /* @__PURE__ */ new WeakMap();
|
|
1193
|
+
const rests = /* @__PURE__ */ new WeakMap();
|
|
1194
|
+
const heights = /* @__PURE__ */ new WeakMap();
|
|
1195
|
+
const rolls = /* @__PURE__ */ new WeakMap();
|
|
1196
|
+
const slots = /* @__PURE__ */ new WeakMap();
|
|
1197
|
+
function nextGeneration(element) {
|
|
1198
|
+
const next = (generation.get(element) ?? 0) + 1;
|
|
1199
|
+
generation.set(element, next);
|
|
1200
|
+
return next;
|
|
1201
|
+
}
|
|
1202
|
+
function still() {
|
|
1203
|
+
const active = settings;
|
|
1204
|
+
if (active === void 0) return true;
|
|
1205
|
+
if (active.reduced === "always") return true;
|
|
1206
|
+
return active.reduced === "system" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1207
|
+
}
|
|
1208
|
+
function lineHeightOf(element) {
|
|
1209
|
+
const lineHeight = Number.parseFloat(window.getComputedStyle(element).lineHeight);
|
|
1210
|
+
return Number.isFinite(lineHeight) && lineHeight > 0 ? lineHeight : 24;
|
|
1211
|
+
}
|
|
1212
|
+
function singleLine(element) {
|
|
1213
|
+
return element.scrollHeight <= lineHeightOf(element) * SINGLE_LINE_RATIO;
|
|
1214
|
+
}
|
|
1215
|
+
function modeOf(element) {
|
|
1216
|
+
if (element.hasAttribute("data-shimmer")) return "append";
|
|
1217
|
+
if (window.getComputedStyle(element).whiteSpace === "nowrap") return "append";
|
|
1218
|
+
return singleLine(element) ? "append" : "reveal";
|
|
1219
|
+
}
|
|
1220
|
+
function tiltFor(element, lineHeight) {
|
|
1221
|
+
const width = element.clientWidth;
|
|
1222
|
+
if (width <= 0 || lineHeight <= 0) return { angle: "180deg", sin: 0, cos: 1 };
|
|
1223
|
+
const natural = Math.atan(lineHeight / width) * 180 / Math.PI;
|
|
1224
|
+
const slant = Math.min(MAX_TILT_DEG, Math.max(MIN_TILT_DEG, natural)) * Math.PI / 180;
|
|
1225
|
+
return {
|
|
1226
|
+
angle: `${180 - slant * 180 / Math.PI}deg`,
|
|
1227
|
+
sin: Math.sin(slant),
|
|
1228
|
+
cos: Math.cos(slant)
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
function front(element, tilt, height, lineHeight) {
|
|
1232
|
+
return tilt.sin * Math.max(0, element.clientWidth) + tilt.cos * height + lineHeight * FADE_BAND;
|
|
1233
|
+
}
|
|
1234
|
+
function textOf(element) {
|
|
1235
|
+
return (element.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
1236
|
+
}
|
|
1237
|
+
function labelText(element) {
|
|
1238
|
+
if (!element.hasAttribute("data-shimmer")) return textOf(element);
|
|
1239
|
+
const content = firstChild(element);
|
|
1240
|
+
return content === null ? "" : textOf(content);
|
|
1241
|
+
}
|
|
1242
|
+
function contentWidth(element) {
|
|
1243
|
+
const root = maskTarget(element);
|
|
1244
|
+
if (root === null) return 0;
|
|
1245
|
+
const origin = root.getBoundingClientRect().left;
|
|
1246
|
+
const range = document.createRange();
|
|
1247
|
+
const walker = document.createTreeWalker(root, 4);
|
|
1248
|
+
let right = 0;
|
|
1249
|
+
for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
|
|
1250
|
+
const text = node.textContent ?? "";
|
|
1251
|
+
if (text.trim() === "") continue;
|
|
1252
|
+
range.selectNodeContents(node);
|
|
1253
|
+
right = Math.max(right, range.getBoundingClientRect().right - origin);
|
|
1254
|
+
}
|
|
1255
|
+
return right;
|
|
1256
|
+
}
|
|
1257
|
+
function firstChild(element) {
|
|
1258
|
+
return element.firstElementChild ?? null;
|
|
1259
|
+
}
|
|
1260
|
+
function maskTarget(element) {
|
|
1261
|
+
return element.hasAttribute("data-shimmer") ? firstChild(element) : element;
|
|
1262
|
+
}
|
|
1263
|
+
function slotOf(element) {
|
|
1264
|
+
const parent = element.parentElement;
|
|
1265
|
+
if (parent === null) return null;
|
|
1266
|
+
const children = parent.children;
|
|
1267
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
1268
|
+
if (children[index] === element) return { parent, index };
|
|
1269
|
+
}
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
function readSlot(slot) {
|
|
1273
|
+
if (slot === null) return void 0;
|
|
1274
|
+
return slots.get(slot.parent)?.get(slot.index);
|
|
1275
|
+
}
|
|
1276
|
+
function writeSlot(slot, text, width) {
|
|
1277
|
+
if (slot === null) return;
|
|
1278
|
+
let entries = slots.get(slot.parent);
|
|
1279
|
+
if (entries === void 0) {
|
|
1280
|
+
entries = /* @__PURE__ */ new Map();
|
|
1281
|
+
slots.set(slot.parent, entries);
|
|
1282
|
+
}
|
|
1283
|
+
entries.set(slot.index, { text, width });
|
|
1284
|
+
const count = slot.parent.children.length;
|
|
1285
|
+
for (const index of entries.keys()) {
|
|
1286
|
+
if (index >= count) entries.delete(index);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
function enterLabel(element) {
|
|
1290
|
+
const run2 = (rolls.get(element) ?? 0) + 1;
|
|
1291
|
+
rolls.set(element, run2);
|
|
1292
|
+
element.removeAttribute(ROLL_ATTRIBUTE);
|
|
1293
|
+
void element.scrollHeight;
|
|
1294
|
+
element.setAttribute(ROLL_ATTRIBUTE, "");
|
|
1295
|
+
window.setTimeout(() => {
|
|
1296
|
+
if (rolls.get(element) === run2) element.removeAttribute(ROLL_ATTRIBUTE);
|
|
1297
|
+
}, rollMs);
|
|
1298
|
+
}
|
|
1299
|
+
function append(element, previous, previousWidth) {
|
|
1300
|
+
const now = labelText(element);
|
|
1301
|
+
const target = maskTarget(element);
|
|
1302
|
+
if (target === null) return;
|
|
1303
|
+
const full = contentWidth(element);
|
|
1304
|
+
element.setAttribute(WIDTH_ATTRIBUTE, String(full));
|
|
1305
|
+
if (full <= 1) {
|
|
1306
|
+
rest(element, target);
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
const extending = previous !== "" && now.startsWith(previous) && previousWidth > 0;
|
|
1310
|
+
const start = extending ? previousWidth : 0;
|
|
1311
|
+
const gap = Math.max(MIN_REVEAL_PX, full - start);
|
|
1312
|
+
const duration = revealDuration(gap);
|
|
1313
|
+
const run2 = nextGeneration(element);
|
|
1314
|
+
const continuing = extending && (sweeps.get(element) ?? 0) > performance.now();
|
|
1315
|
+
target.style.removeProperty("mask-image");
|
|
1316
|
+
if (!continuing) {
|
|
1317
|
+
target.style.transition = "none";
|
|
1318
|
+
target.style.setProperty("--dsh-uix-edge", `${start}px`);
|
|
1319
|
+
void element.scrollHeight;
|
|
1320
|
+
}
|
|
1321
|
+
sweeps.set(element, performance.now() + duration);
|
|
1322
|
+
target.style.transition = `--dsh-uix-edge ${duration}ms ${ease}`;
|
|
1323
|
+
target.style.setProperty("--dsh-uix-edge", `${full}px`);
|
|
1324
|
+
window.setTimeout(() => {
|
|
1325
|
+
if (!owns(element, run2)) return;
|
|
1326
|
+
sweeps.delete(element);
|
|
1327
|
+
rest(element, target);
|
|
1328
|
+
}, duration);
|
|
1329
|
+
}
|
|
1330
|
+
function rest(element, target) {
|
|
1331
|
+
target.style.setProperty("mask-image", rests.get(element) ?? "none");
|
|
1332
|
+
target.style.removeProperty("--dsh-uix-edge");
|
|
1333
|
+
target.style.removeProperty("transition");
|
|
1334
|
+
}
|
|
1335
|
+
function owns(element, run2) {
|
|
1336
|
+
return generation.get(element) === run2;
|
|
1337
|
+
}
|
|
1338
|
+
function revealDuration(gap) {
|
|
1339
|
+
const active = settings;
|
|
1340
|
+
const perLine = revealMinMs / Math.pow(ONE_LINE_PX, REVEAL_EXPONENT);
|
|
1341
|
+
const shaped = perLine * Math.pow(Math.max(ONE_LINE_PX, gap), REVEAL_EXPONENT);
|
|
1342
|
+
const scaled = Math.min(revealMaxMs, Math.max(revealMinMs, shaped)) * active.durationScale;
|
|
1343
|
+
return active.quantize ? quantizeMs(scaled) : scaled;
|
|
1344
|
+
}
|
|
1345
|
+
function advance(element, state) {
|
|
1346
|
+
const active = settings;
|
|
1347
|
+
if (active === void 0) return;
|
|
1348
|
+
const height = lineHeightOf(element);
|
|
1349
|
+
const tilt = tiltFor(element, height);
|
|
1350
|
+
element.style.setProperty("--dsh-uix-tilt", tilt.angle);
|
|
1351
|
+
state.target = front(element, tilt, element.scrollHeight, height);
|
|
1352
|
+
if (state.frame !== 0) return;
|
|
1353
|
+
run(element, state);
|
|
1354
|
+
}
|
|
1355
|
+
function run(element, state) {
|
|
1356
|
+
const active = settings;
|
|
1357
|
+
if (active === void 0) return;
|
|
1358
|
+
const gap = state.target - state.current;
|
|
1359
|
+
if (gap < MIN_REVEAL_PX) {
|
|
1360
|
+
state.frame = 0;
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
state.from = state.current;
|
|
1364
|
+
state.started = performance.now();
|
|
1365
|
+
state.duration = revealDuration(gap);
|
|
1366
|
+
const mode = active.mode;
|
|
1367
|
+
const step = (now) => {
|
|
1368
|
+
const live = reveals.get(element);
|
|
1369
|
+
if (live === void 0 || live !== state) return;
|
|
1370
|
+
const progress = Math.min(1, (now - state.started) / Math.max(1, state.duration));
|
|
1371
|
+
const eased = easeCurve(effectCurvePoints(mode), progress);
|
|
1372
|
+
state.current = state.from + (state.target - state.from) * eased;
|
|
1373
|
+
element.style.setProperty("--dsh-uix-reveal", `${state.current}px`);
|
|
1374
|
+
if (progress >= 1) {
|
|
1375
|
+
state.current = state.target;
|
|
1376
|
+
element.style.setProperty("--dsh-uix-reveal", `${state.target}px`);
|
|
1377
|
+
state.frame = 0;
|
|
1378
|
+
if (state.target - state.current >= MIN_REVEAL_PX) run(element, state);
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
state.frame = window.requestAnimationFrame(step);
|
|
1382
|
+
};
|
|
1383
|
+
state.frame = window.requestAnimationFrame(step);
|
|
1384
|
+
}
|
|
1385
|
+
function forget(element, immediately = false) {
|
|
1386
|
+
const state = reveals.get(element);
|
|
1387
|
+
reveals.delete(element);
|
|
1388
|
+
nextGeneration(element);
|
|
1389
|
+
if (state !== void 0 && !immediately) {
|
|
1390
|
+
element.style.setProperty("--dsh-uix-reveal", `${state.target + element.scrollHeight}px`);
|
|
1391
|
+
}
|
|
1392
|
+
element.removeAttribute(STREAM_ATTRIBUTE);
|
|
1393
|
+
element.removeAttribute(LAST_ATTRIBUTE);
|
|
1394
|
+
element.removeAttribute(WIDTH_ATTRIBUTE);
|
|
1395
|
+
element.removeAttribute(ROLL_ATTRIBUTE);
|
|
1396
|
+
rolls.delete(element);
|
|
1397
|
+
const target = maskTarget(element);
|
|
1398
|
+
const clear = () => {
|
|
1399
|
+
element.style.removeProperty("--dsh-uix-reveal");
|
|
1400
|
+
element.style.removeProperty("--dsh-uix-tilt");
|
|
1401
|
+
element.style.removeProperty("transition");
|
|
1402
|
+
target?.style.removeProperty("--dsh-uix-edge");
|
|
1403
|
+
target?.style.removeProperty("transition");
|
|
1404
|
+
target?.style.removeProperty("mask-image");
|
|
1405
|
+
};
|
|
1406
|
+
rests.delete(element);
|
|
1407
|
+
if (immediately || state === void 0) clear();
|
|
1408
|
+
else window.setTimeout(clear, revealMaxMs);
|
|
1409
|
+
}
|
|
1410
|
+
function measure() {
|
|
1411
|
+
pending = false;
|
|
1412
|
+
const active = settings;
|
|
1413
|
+
if (active === void 0 || active.mode === "off" || still()) return;
|
|
1414
|
+
const marked = document.querySelectorAll(`[${STREAM_ATTRIBUTE}]`);
|
|
1415
|
+
for (let index = 0; index < marked.length; index += 1) {
|
|
1416
|
+
const element = marked[index];
|
|
1417
|
+
if (element === void 0) continue;
|
|
1418
|
+
if (element.getAttribute(STREAM_ATTRIBUTE) !== "reveal") continue;
|
|
1419
|
+
const state = reveals.get(element);
|
|
1420
|
+
if (state === void 0) continue;
|
|
1421
|
+
advance(element, state);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
function schedule() {
|
|
1425
|
+
if (pending) return;
|
|
1426
|
+
pending = true;
|
|
1427
|
+
window.requestAnimationFrame(measure);
|
|
1428
|
+
}
|
|
1429
|
+
function covered(element) {
|
|
1430
|
+
let node = element.parentElement;
|
|
1431
|
+
while (node !== null) {
|
|
1432
|
+
if (node.getAttribute(STREAM_ATTRIBUTE) === "reveal") return true;
|
|
1433
|
+
node = node.parentElement;
|
|
1434
|
+
}
|
|
1435
|
+
return false;
|
|
1436
|
+
}
|
|
1437
|
+
function mark() {
|
|
1438
|
+
const active = settings;
|
|
1439
|
+
if (active === void 0 || active.mode === "off" || still()) return;
|
|
1440
|
+
const live = document.querySelectorAll(LIVE);
|
|
1441
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1442
|
+
const containers2 = /* @__PURE__ */ new Set();
|
|
1443
|
+
for (let index = 0; index < live.length; index += 1) {
|
|
1444
|
+
const element = live[index];
|
|
1445
|
+
if (element === void 0) continue;
|
|
1446
|
+
for (let node = element.parentElement; node !== null; node = node.parentElement) {
|
|
1447
|
+
containers2.add(node);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
for (let index = 0; index < live.length; index += 1) {
|
|
1451
|
+
const element = live[index];
|
|
1452
|
+
if (element === void 0) continue;
|
|
1453
|
+
const height = element.scrollHeight;
|
|
1454
|
+
const lineHeight = lineHeightOf(element);
|
|
1455
|
+
const before = heights.get(element);
|
|
1456
|
+
heights.set(element, height);
|
|
1457
|
+
if (covered(element)) continue;
|
|
1458
|
+
const mode = modeOf(element);
|
|
1459
|
+
if (mode === "append" && containers2.has(element)) {
|
|
1460
|
+
if (element.hasAttribute(STREAM_ATTRIBUTE)) forget(element, true);
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
seen.add(element);
|
|
1464
|
+
const marked2 = element.getAttribute(STREAM_ATTRIBUTE);
|
|
1465
|
+
if (marked2 !== mode) {
|
|
1466
|
+
if (marked2 !== null) forget(element, true);
|
|
1467
|
+
if (mode === "reveal") {
|
|
1468
|
+
const tilt = tiltFor(element, lineHeight);
|
|
1469
|
+
const end = front(element, tilt, height, lineHeight);
|
|
1470
|
+
const open = before === void 0 ? end : Math.min(end, front(element, tilt, before, lineHeight));
|
|
1471
|
+
reveals.set(element, { current: open, from: open, target: open, frame: 0, started: 0, duration: 0 });
|
|
1472
|
+
element.style.setProperty("--dsh-uix-tilt", tilt.angle);
|
|
1473
|
+
element.style.setProperty("--dsh-uix-reveal", `${open}px`);
|
|
1474
|
+
} else {
|
|
1475
|
+
const target2 = maskTarget(element);
|
|
1476
|
+
if (target2 !== null) rests.set(element, window.getComputedStyle(target2).maskImage);
|
|
1477
|
+
}
|
|
1478
|
+
element.setAttribute(STREAM_ATTRIBUTE, mode);
|
|
1479
|
+
const target = maskTarget(element);
|
|
1480
|
+
if (mode === "append" && target !== null) rest(element, target);
|
|
1481
|
+
}
|
|
1482
|
+
if (mode === "reveal") continue;
|
|
1483
|
+
const text = labelText(element);
|
|
1484
|
+
const width = contentWidth(element);
|
|
1485
|
+
const slot = slotOf(element);
|
|
1486
|
+
const prior = readSlot(slot);
|
|
1487
|
+
const previous = prior?.text ?? element.getAttribute(LAST_ATTRIBUTE) ?? "";
|
|
1488
|
+
const previousWidth = prior?.width ?? Number.parseFloat(element.getAttribute(WIDTH_ATTRIBUTE) ?? "0");
|
|
1489
|
+
if (text === previous) continue;
|
|
1490
|
+
writeSlot(slot, text, width);
|
|
1491
|
+
element.setAttribute(LAST_ATTRIBUTE, text);
|
|
1492
|
+
element.setAttribute(WIDTH_ATTRIBUTE, String(width));
|
|
1493
|
+
const extending = previous !== "" && text.startsWith(previous) && previousWidth > 0;
|
|
1494
|
+
if (!extending) enterLabel(element);
|
|
1495
|
+
append(element, previous, Number.isFinite(previousWidth) ? previousWidth : 0);
|
|
1496
|
+
}
|
|
1497
|
+
unveil();
|
|
1498
|
+
const marked = document.querySelectorAll(`[${STREAM_ATTRIBUTE}]`);
|
|
1499
|
+
for (let index = 0; index < marked.length; index += 1) {
|
|
1500
|
+
const element = marked[index];
|
|
1501
|
+
if (element === void 0 || seen.has(element)) continue;
|
|
1502
|
+
forget(element);
|
|
1503
|
+
}
|
|
1504
|
+
schedule();
|
|
1505
|
+
}
|
|
1506
|
+
function atTail() {
|
|
1507
|
+
return document.querySelectorAll("[data-chat-following-tail]").length > 0;
|
|
1508
|
+
}
|
|
1509
|
+
function unveil() {
|
|
1510
|
+
const rows = document.querySelectorAll(ROW);
|
|
1511
|
+
if (rows.length === 0) return;
|
|
1512
|
+
const pinned = atTail();
|
|
1513
|
+
const next = /* @__PURE__ */ new WeakSet();
|
|
1514
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
1515
|
+
const row = rows[index];
|
|
1516
|
+
if (row === void 0) continue;
|
|
1517
|
+
next.add(row);
|
|
1518
|
+
if (knownRows.has(row)) continue;
|
|
1519
|
+
if (!pinned || !following) continue;
|
|
1520
|
+
row.setAttribute(ENTER_ATTRIBUTE, "");
|
|
1521
|
+
window.setTimeout(() => {
|
|
1522
|
+
row.removeAttribute(ENTER_ATTRIBUTE);
|
|
1523
|
+
}, enterMs);
|
|
1524
|
+
}
|
|
1525
|
+
knownRows = next;
|
|
1526
|
+
following = pinned;
|
|
1527
|
+
}
|
|
1528
|
+
function install() {
|
|
1529
|
+
if (installed) return;
|
|
1530
|
+
installed = true;
|
|
1531
|
+
sheet = document.createElement("style");
|
|
1532
|
+
sheet.textContent = STREAM_CSS;
|
|
1533
|
+
document.head.appendChild(sheet);
|
|
1534
|
+
observer = new MutationObserver(() => {
|
|
1535
|
+
mark();
|
|
1536
|
+
});
|
|
1537
|
+
observer.observe(document.body, {
|
|
1538
|
+
childList: true,
|
|
1539
|
+
subtree: true,
|
|
1540
|
+
characterData: true,
|
|
1541
|
+
attributes: true,
|
|
1542
|
+
attributeFilter: ["data-streaming", "data-shimmer"]
|
|
1543
|
+
});
|
|
1544
|
+
const existing = document.querySelectorAll(ROW);
|
|
1545
|
+
for (let index = 0; index < existing.length; index += 1) {
|
|
1546
|
+
const row = existing[index];
|
|
1547
|
+
if (row !== void 0) knownRows.add(row);
|
|
1548
|
+
}
|
|
1549
|
+
following = atTail();
|
|
1550
|
+
mark();
|
|
1551
|
+
}
|
|
1552
|
+
function uninstall() {
|
|
1553
|
+
observer?.disconnect();
|
|
1554
|
+
observer = void 0;
|
|
1555
|
+
sheet?.remove();
|
|
1556
|
+
sheet = void 0;
|
|
1557
|
+
installed = false;
|
|
1558
|
+
document.documentElement.style.removeProperty("--dsh-uix-ease");
|
|
1559
|
+
document.documentElement.style.removeProperty("--dsh-uix-roll");
|
|
1560
|
+
document.documentElement.style.removeProperty("--dsh-uix-enter");
|
|
1561
|
+
const entering = document.querySelectorAll(`[${ENTER_ATTRIBUTE}]`);
|
|
1562
|
+
for (let index = 0; index < entering.length; index += 1) {
|
|
1563
|
+
entering[index]?.removeAttribute(ENTER_ATTRIBUTE);
|
|
1564
|
+
}
|
|
1565
|
+
const marked = document.querySelectorAll(`[${STREAM_ATTRIBUTE}]`);
|
|
1566
|
+
for (let index = 0; index < marked.length; index += 1) {
|
|
1567
|
+
const element = marked[index];
|
|
1568
|
+
if (element !== void 0) forget(element, true);
|
|
1569
|
+
}
|
|
1570
|
+
reveals.clear();
|
|
1571
|
+
}
|
|
1572
|
+
return {
|
|
1573
|
+
apply(next) {
|
|
1574
|
+
uninstall();
|
|
1575
|
+
settings = next;
|
|
1576
|
+
if (next.mode === "off" || !next.stream) return;
|
|
1577
|
+
if (next.reduced === "system" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
1578
|
+
ease = `cubic-bezier(${effectCurvePoints(next.mode).join(", ")})`;
|
|
1579
|
+
const scaled = next.streamRollMs * next.durationScale;
|
|
1580
|
+
rollMs = next.quantize ? quantizeMs(scaled) : scaled;
|
|
1581
|
+
document.documentElement.style.setProperty("--dsh-uix-ease", ease);
|
|
1582
|
+
document.documentElement.style.setProperty("--dsh-uix-roll", `${rollMs}ms`);
|
|
1583
|
+
const scaledEnter = next.streamEnterMs * next.durationScale;
|
|
1584
|
+
enterMs = next.quantize ? quantizeMs(scaledEnter) : scaledEnter;
|
|
1585
|
+
revealMinMs = next.streamRevealMinMs;
|
|
1586
|
+
revealMaxMs = next.streamRevealMaxMs;
|
|
1587
|
+
document.documentElement.style.setProperty("--dsh-uix-enter", `${enterMs}ms`);
|
|
676
1588
|
install();
|
|
677
1589
|
},
|
|
678
1590
|
restore: uninstall
|
|
@@ -766,16 +1678,14 @@
|
|
|
766
1678
|
.dsh-uix-segment-tab:disabled{cursor:default;opacity:.4}
|
|
767
1679
|
.dsh-uix-segment-tab:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-2px}
|
|
768
1680
|
|
|
769
|
-
/*
|
|
770
|
-
|
|
771
|
-
.
|
|
772
|
-
.dsh-uix-
|
|
773
|
-
.dsh-uix-
|
|
774
|
-
.dsh-uix-
|
|
775
|
-
.dsh-uix-
|
|
776
|
-
.dsh-uix-
|
|
777
|
-
.dsh-uix-check-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}
|
|
778
|
-
.dsh-uix-check-hint{font-size:12px;line-height:1.5;font-weight:400;color:var(--dsw-alias-label-tertiary)}
|
|
1681
|
+
/* Boolean option row: DeveloperToolsRow.module.css .row / .title / .description \u2014
|
|
1682
|
+
the title column takes the width, the switch sits at the inline end. The copied
|
|
1683
|
+
Switch above is the same control, so an option row and the row above it match. */
|
|
1684
|
+
.dsh-uix-option{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:12px 0}
|
|
1685
|
+
.dsh-uix-option+.dsh-uix-option{border-top:0.5px solid var(--dsw-alias-border-l2)}
|
|
1686
|
+
.dsh-uix-option-text{display:flex;flex-direction:column;gap:4px;min-width:0}
|
|
1687
|
+
.dsh-uix-option-title{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}
|
|
1688
|
+
.dsh-uix-option-desc{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary)}
|
|
779
1689
|
`;
|
|
780
1690
|
|
|
781
1691
|
// src/client.ts
|
|
@@ -830,7 +1740,14 @@
|
|
|
830
1740
|
durationScale: zod.number().optional(),
|
|
831
1741
|
spatialSplit: zod.boolean().optional(),
|
|
832
1742
|
quantize: zod.boolean().optional(),
|
|
833
|
-
reduced: zod.string().optional()
|
|
1743
|
+
reduced: zod.string().optional(),
|
|
1744
|
+
scroll: zod.boolean().optional(),
|
|
1745
|
+
stream: zod.boolean().optional(),
|
|
1746
|
+
followScroll: zod.boolean().optional(),
|
|
1747
|
+
streamRevealMinMs: zod.number().optional(),
|
|
1748
|
+
streamRevealMaxMs: zod.number().optional(),
|
|
1749
|
+
streamEnterMs: zod.number().optional(),
|
|
1750
|
+
streamRollMs: zod.number().optional()
|
|
834
1751
|
}), "SetMotionArgs")
|
|
835
1752
|
]
|
|
836
1753
|
};
|
|
@@ -857,13 +1774,23 @@
|
|
|
857
1774
|
"motion.duration": "\u65F6\u957F\u500D\u7387",
|
|
858
1775
|
"motion.options": "\u9AD8\u7EA7\u9009\u9879",
|
|
859
1776
|
"motion.spatial-split": "\u4F4D\u79FB\u4E0E\u6548\u679C\u4F7F\u7528\u4E0D\u540C\u66F2\u7EBF",
|
|
860
|
-
"motion.spatial-split-hint": "\u4F4D\u79FB\
|
|
1777
|
+
"motion.spatial-split-hint": "\u4F4D\u79FB\u5E26\u8FC7\u51B2\uFF0C\u6548\u679C\u4E0D\u5E26\uFF1B\u5173\u95ED\u540E\u7EDF\u4E00\u7528\u6548\u679C\u66F2\u7EBF\u3002",
|
|
861
1778
|
"motion.quantize": "\u65F6\u957F\u5BF9\u9F50\u5230 Material \u6863\u4F4D",
|
|
862
|
-
"motion.quantize-hint": "\
|
|
1779
|
+
"motion.quantize-hint": "\u65F6\u957F\u5438\u9644\u5230 50 \u81F3 1000ms \u7684 Material \u6863\u4F4D\u3002",
|
|
1780
|
+
"motion.stream": "\u6D41\u5F0F\u6587\u672C\u6E10\u8FDB\u663E\u73B0",
|
|
1781
|
+
"motion.stream-hint": "\u6B63\u6587\u4E0E\u601D\u8003\u6458\u8981\u9010\u6BB5\u663E\u73B0\uFF1B\u53EA\u79FB\u52A8\u906E\u7F69\uFF0C\u4E0D\u622A\u65AD\u6587\u5B57\u3002",
|
|
1782
|
+
"motion.stream-timing": "\u6D41\u5F0F\u52A8\u753B\u65F6\u957F",
|
|
1783
|
+
"motion.follow-scroll": "\u8DDF\u968F\u5230\u5E95\u65F6\u5E73\u6ED1\u6ED1\u884C",
|
|
1784
|
+
"motion.follow-scroll-hint": "\u5173\u95ED\u540E\u5E95\u90E8\u8DDF\u968F\u77AC\u65F6\u8D34\u9F50\uFF0C\u53EA\u6709\u6587\u5B57\u5728\u52A8\uFF1B\u5F00\u542F\u65F6\u89C6\u53E3\u6ED1\u8FC7\u53BB\uFF0C\u89C2\u611F\u66F4\u8FDE\u7EED\u4F46\u59CB\u7EC8\u843D\u540E\u7EA6\u4E00\u884C\u3002",
|
|
1785
|
+
"motion.stream-reveal-min": "\u5355\u884C\u663E\u73B0\u4E0B\u9650\uFF08ms\uFF09",
|
|
1786
|
+
"motion.stream-reveal-max": "\u6574\u5757\u663E\u73B0\u4E0A\u9650\uFF08ms\uFF09",
|
|
1787
|
+
"motion.stream-enter": "\u65B0\u884C\u8FDB\u573A\uFF08ms\uFF09",
|
|
1788
|
+
"motion.stream-roll": "\u6458\u8981\u6362\u884C\u6EDA\u52A8\uFF08ms\uFF09",
|
|
1789
|
+
"motion.stream-timing-hint": "\u5355\u884C\u4E0B\u9650\u5C31\u662F\u7F13\u5B58\u7684\u53EF\u89C1\u7A97\u53E3\uFF1A\u8C03\u5927\u5B83\uFF0C\u8FB9\u754C\u540E\u65B9\u538B\u7740\u7684\u672A\u8BFB\u6587\u5B57\u66F4\u591A\u3001\u52A8\u753B\u66F4\u6162\u3002\u4E0A\u9650\u7528\u4E8E\u6574\u5757\u5185\u5BB9\u4E00\u6B21\u843D\u4E0B\u7684\u60C5\u5F62\u3002",
|
|
863
1790
|
"motion.scroll": "\u6EDA\u52A8\u4F7F\u7528\u540C\u4E00\u66F2\u7EBF",
|
|
864
|
-
"motion.scroll-hint": "\
|
|
1791
|
+
"motion.scroll-hint": "\u5E73\u6ED1\u6EDA\u52A8\u3001\u6EDA\u8F6E\u4E0E\u5B9A\u4F4D\u8DF3\u8F6C\u6539\u7528\u672C\u9875\u66F2\u7EBF\u3002",
|
|
865
1792
|
"motion.reduced": "\u59CB\u7EC8\u51CF\u5F31\u52A8\u6548",
|
|
866
|
-
"motion.reduced-hint": "\
|
|
1793
|
+
"motion.reduced-hint": "\u5FFD\u7565\u7CFB\u7EDF\u504F\u597D\uFF0C\u4E00\u5F8B\u628A\u65F6\u957F\u538B\u5230 1ms\u3002",
|
|
867
1794
|
"motion.system-reduced": "\u7CFB\u7EDF\u5DF2\u8BF7\u6C42\u51CF\u5F31\u52A8\u6548\uFF0C\u672C\u9875\u52A8\u753B\u6B63\u5728\u88AB\u538B\u5236\u3002",
|
|
868
1795
|
"motion.save-failed": "\u52A8\u6548\u8BBE\u7F6E\u4FDD\u5B58\u5931\u8D25",
|
|
869
1796
|
"motion.inert": "\u66F2\u7EBF\u65CF\u4E3A\u300C\u5173\u95ED\u300D\uFF0C\u4EE5\u4E0B\u9009\u9879\u4E0D\u4F1A\u751F\u6548\u3002"
|
|
@@ -889,13 +1816,23 @@
|
|
|
889
1816
|
"motion.duration": "Duration scale",
|
|
890
1817
|
"motion.options": "Advanced options",
|
|
891
1818
|
"motion.spatial-split": "Spatial and effects take different curves",
|
|
892
|
-
"motion.spatial-split-hint": "
|
|
1819
|
+
"motion.spatial-split-hint": "Movement overshoots, effects do not; off uses the effects curve for both.",
|
|
893
1820
|
"motion.quantize": "Snap durations to the Material ladder",
|
|
894
|
-
"motion.quantize-hint": "
|
|
1821
|
+
"motion.quantize-hint": "Snap durations onto the 50-1000 ms Material ladder.",
|
|
1822
|
+
"motion.stream": "Progressive streamed text",
|
|
1823
|
+
"motion.stream-hint": "Reveal transcript and reasoning progressively; a mask moves, no text is cut.",
|
|
1824
|
+
"motion.stream-timing": "Stream timing",
|
|
1825
|
+
"motion.follow-scroll": "Glide when following the tail",
|
|
1826
|
+
"motion.follow-scroll-hint": "Off keeps the tail pinned exactly and only the text moves. On eases the viewport along \u2014 smoother, but about a line behind.",
|
|
1827
|
+
"motion.stream-reveal-min": "Reveal floor, one line (ms)",
|
|
1828
|
+
"motion.stream-reveal-max": "Reveal ceiling, whole block (ms)",
|
|
1829
|
+
"motion.stream-enter": "New row settle (ms)",
|
|
1830
|
+
"motion.stream-roll": "Summary roll (ms)",
|
|
1831
|
+
"motion.stream-timing-hint": "The floor is the visible buffer: raise it and more unread text stays behind the boundary, so the reveal reads slower. The ceiling covers a block that lands all at once.",
|
|
895
1832
|
"motion.scroll": "Scroll on the same curve",
|
|
896
|
-
"motion.scroll-hint": "
|
|
1833
|
+
"motion.scroll-hint": "Drive smooth scrolling, the wheel, and locating jumps on this page's curve.",
|
|
897
1834
|
"motion.reduced": "Always reduce motion",
|
|
898
|
-
"motion.reduced-hint": "Ignore the system
|
|
1835
|
+
"motion.reduced-hint": "Ignore the system preference and force every duration to 1 ms.",
|
|
899
1836
|
"motion.system-reduced": "The system asked for reduced motion; this page is being held still.",
|
|
900
1837
|
"motion.save-failed": "Failed to save the motion settings",
|
|
901
1838
|
"motion.inert": "The curve family is Off, so these options have no effect."
|
|
@@ -936,6 +1873,7 @@
|
|
|
936
1873
|
borderWidth: "0.5px",
|
|
937
1874
|
paths: { colors: DEFAULT_COLORS_PATH, tokens: DEFAULT_TOKENS_PATH },
|
|
938
1875
|
motion: { ...MOTION_OFF },
|
|
1876
|
+
streamUnsupported: false,
|
|
939
1877
|
problem: "",
|
|
940
1878
|
notice: "",
|
|
941
1879
|
saving: false
|
|
@@ -943,6 +1881,7 @@
|
|
|
943
1881
|
const listeners = /* @__PURE__ */ new Set();
|
|
944
1882
|
const motion = createMotion();
|
|
945
1883
|
const scrolling = createScrollMotion();
|
|
1884
|
+
const streaming = createStreamMotion();
|
|
946
1885
|
const publish = (patch) => {
|
|
947
1886
|
view = { ...view, ...patch };
|
|
948
1887
|
for (const listener of listeners) listener();
|
|
@@ -959,8 +1898,9 @@
|
|
|
959
1898
|
if (disposed) return;
|
|
960
1899
|
motion.apply(next);
|
|
961
1900
|
scrolling.apply(next);
|
|
1901
|
+
streaming.apply(next);
|
|
962
1902
|
};
|
|
963
|
-
const motionDiffers = (left, right) => left.mode !== right.mode || left.durationScale !== right.durationScale || left.spatialSplit !== right.spatialSplit || left.quantize !== right.quantize || left.reduced !== right.reduced || left.scroll !== right.scroll;
|
|
1903
|
+
const motionDiffers = (left, right) => left.mode !== right.mode || left.durationScale !== right.durationScale || left.spatialSplit !== right.spatialSplit || left.quantize !== right.quantize || left.reduced !== right.reduced || left.scroll !== right.scroll || left.stream !== right.stream || left.followScroll !== right.followScroll || left.streamRevealMinMs !== right.streamRevealMinMs || left.streamRevealMaxMs !== right.streamRevealMaxMs || left.streamEnterMs !== right.streamEnterMs || left.streamRollMs !== right.streamRollMs;
|
|
964
1904
|
const clearOverride = () => {
|
|
965
1905
|
disposeOverride?.();
|
|
966
1906
|
disposeOverride = void 0;
|
|
@@ -1011,7 +1951,12 @@
|
|
|
1011
1951
|
return envelope.value;
|
|
1012
1952
|
};
|
|
1013
1953
|
const adopt = (answer) => {
|
|
1014
|
-
const
|
|
1954
|
+
const reported = answer.motion;
|
|
1955
|
+
if (reported !== void 0 && reported.stream === void 0 && !view.streamUnsupported) {
|
|
1956
|
+
console.error("ui-experience: this host build predates the stream setting; progressive streamed text stays on and its switch stays hidden until DSH restarts");
|
|
1957
|
+
publish({ streamUnsupported: true });
|
|
1958
|
+
}
|
|
1959
|
+
const motionAnswer = reported === void 0 ? { ...MOTION_OFF } : { ...MOTION_OFF, ...reported, stream: reported.stream ?? true };
|
|
1015
1960
|
if (answer.motion === void 0 && !motionUnsupported) {
|
|
1016
1961
|
motionUnsupported = true;
|
|
1017
1962
|
console.error("ui-experience: this host build predates the motion settings; the M3 motion row stays inert until DSH restarts");
|
|
@@ -1084,6 +2029,10 @@
|
|
|
1084
2029
|
};
|
|
1085
2030
|
const saveMotion = async (change) => {
|
|
1086
2031
|
if (disposed || writing) return;
|
|
2032
|
+
if (view.streamUnsupported && change.stream !== void 0) {
|
|
2033
|
+
console.error("ui-experience: the stream setting cannot be saved on this host build; restart DSH to pick it up");
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
1087
2036
|
writing = true;
|
|
1088
2037
|
publish({ saving: true, problem: "", notice: "" });
|
|
1089
2038
|
try {
|
|
@@ -1185,6 +2134,28 @@
|
|
|
1185
2134
|
),
|
|
1186
2135
|
h("span", null, label)
|
|
1187
2136
|
);
|
|
2137
|
+
const numberRow = (label, hint, value, commit, disabled) => h(
|
|
2138
|
+
"label",
|
|
2139
|
+
{ className: "dsh-uix-field" },
|
|
2140
|
+
h("span", { className: "dsh-uix-label" }, label),
|
|
2141
|
+
h(
|
|
2142
|
+
"span",
|
|
2143
|
+
{ className: "dsh-uix-input-wrap" },
|
|
2144
|
+
h("input", {
|
|
2145
|
+
className: "dsh-uix-input",
|
|
2146
|
+
type: "number",
|
|
2147
|
+
min: 0,
|
|
2148
|
+
step: 10,
|
|
2149
|
+
defaultValue: String(value),
|
|
2150
|
+
disabled,
|
|
2151
|
+
onChange: (event) => {
|
|
2152
|
+
const parsed = event.target.valueAsNumber;
|
|
2153
|
+
if (Number.isFinite(parsed) && parsed >= 0) commit(Math.round(parsed));
|
|
2154
|
+
}
|
|
2155
|
+
})
|
|
2156
|
+
),
|
|
2157
|
+
h("p", { className: "dsh-uix-hint" }, hint)
|
|
2158
|
+
);
|
|
1188
2159
|
const WALK_KEYS = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"];
|
|
1189
2160
|
const segmented = (id, value, segments, select, label, disabled) => {
|
|
1190
2161
|
const selected = Math.max(0, segments.findIndex((segment) => segment.value === value));
|
|
@@ -1234,23 +2205,26 @@
|
|
|
1234
2205
|
}, segment.label))
|
|
1235
2206
|
);
|
|
1236
2207
|
};
|
|
1237
|
-
const
|
|
1238
|
-
"
|
|
1239
|
-
{ className: "dsh-uix-
|
|
1240
|
-
h(
|
|
1241
|
-
|
|
1242
|
-
|
|
2208
|
+
const option = (checked, set, label, hint, disabled) => h(
|
|
2209
|
+
"div",
|
|
2210
|
+
{ className: "dsh-uix-option" },
|
|
2211
|
+
h(
|
|
2212
|
+
"div",
|
|
2213
|
+
{ className: "dsh-uix-option-text" },
|
|
2214
|
+
h("div", { className: "dsh-uix-option-title" }, label),
|
|
2215
|
+
hint === void 0 ? null : h("div", { className: "dsh-uix-option-desc" }, hint)
|
|
2216
|
+
),
|
|
2217
|
+
h("button", {
|
|
2218
|
+
type: "button",
|
|
2219
|
+
role: "switch",
|
|
2220
|
+
"aria-checked": checked,
|
|
2221
|
+
"aria-label": label,
|
|
1243
2222
|
disabled,
|
|
1244
|
-
|
|
1245
|
-
|
|
2223
|
+
className: "dsh-uix-switch",
|
|
2224
|
+
onClick: () => {
|
|
2225
|
+
set(!checked);
|
|
1246
2226
|
}
|
|
1247
|
-
})
|
|
1248
|
-
h(
|
|
1249
|
-
"span",
|
|
1250
|
-
{ className: "dsh-uix-check-text" },
|
|
1251
|
-
h("span", { className: "dsh-uix-check-label" }, label),
|
|
1252
|
-
hint === void 0 ? null : h("span", { className: "dsh-uix-check-hint" }, hint)
|
|
1253
|
-
)
|
|
2227
|
+
}, h("span", { className: "dsh-uix-switch-thumb", "aria-hidden": true }))
|
|
1254
2228
|
);
|
|
1255
2229
|
function PaletteRow(props) {
|
|
1256
2230
|
const tr = props.t ?? t;
|
|
@@ -1389,7 +2363,7 @@
|
|
|
1389
2363
|
const policy = open ? h(
|
|
1390
2364
|
"div",
|
|
1391
2365
|
{ className: "dsh-uix-form" },
|
|
1392
|
-
|
|
2366
|
+
option(
|
|
1393
2367
|
current.spatialSplit,
|
|
1394
2368
|
(next) => {
|
|
1395
2369
|
void saveMotion({ spatialSplit: next });
|
|
@@ -1398,7 +2372,7 @@
|
|
|
1398
2372
|
tr("motion.spatial-split-hint"),
|
|
1399
2373
|
locked
|
|
1400
2374
|
),
|
|
1401
|
-
|
|
2375
|
+
option(
|
|
1402
2376
|
current.quantize,
|
|
1403
2377
|
(next) => {
|
|
1404
2378
|
void saveMotion({ quantize: next });
|
|
@@ -1407,7 +2381,25 @@
|
|
|
1407
2381
|
tr("motion.quantize-hint"),
|
|
1408
2382
|
locked
|
|
1409
2383
|
),
|
|
1410
|
-
|
|
2384
|
+
state.streamUnsupported ? null : option(
|
|
2385
|
+
current.stream,
|
|
2386
|
+
(next) => {
|
|
2387
|
+
void saveMotion({ stream: next });
|
|
2388
|
+
},
|
|
2389
|
+
tr("motion.stream"),
|
|
2390
|
+
tr("motion.stream-hint"),
|
|
2391
|
+
locked
|
|
2392
|
+
),
|
|
2393
|
+
option(
|
|
2394
|
+
current.followScroll,
|
|
2395
|
+
(next) => {
|
|
2396
|
+
void saveMotion({ followScroll: next });
|
|
2397
|
+
},
|
|
2398
|
+
tr("motion.follow-scroll"),
|
|
2399
|
+
tr("motion.follow-scroll-hint"),
|
|
2400
|
+
locked
|
|
2401
|
+
),
|
|
2402
|
+
option(
|
|
1411
2403
|
current.scroll,
|
|
1412
2404
|
(next) => {
|
|
1413
2405
|
void saveMotion({ scroll: next });
|
|
@@ -1416,7 +2408,7 @@
|
|
|
1416
2408
|
tr("motion.scroll-hint"),
|
|
1417
2409
|
locked
|
|
1418
2410
|
),
|
|
1419
|
-
|
|
2411
|
+
option(
|
|
1420
2412
|
current.reduced === "always",
|
|
1421
2413
|
(next) => {
|
|
1422
2414
|
void saveMotion({ reduced: next ? "always" : "system" });
|
|
@@ -1426,6 +2418,46 @@
|
|
|
1426
2418
|
locked
|
|
1427
2419
|
)
|
|
1428
2420
|
) : null;
|
|
2421
|
+
const timing = current.stream && !state.streamUnsupported && open ? h(
|
|
2422
|
+
"div",
|
|
2423
|
+
{ className: "dsh-uix-form" },
|
|
2424
|
+
numberRow(
|
|
2425
|
+
tr("motion.stream-reveal-min"),
|
|
2426
|
+
tr("motion.stream-timing-hint"),
|
|
2427
|
+
current.streamRevealMinMs,
|
|
2428
|
+
(next) => {
|
|
2429
|
+
void saveMotion({ streamRevealMinMs: next });
|
|
2430
|
+
},
|
|
2431
|
+
locked
|
|
2432
|
+
),
|
|
2433
|
+
numberRow(
|
|
2434
|
+
tr("motion.stream-reveal-max"),
|
|
2435
|
+
"",
|
|
2436
|
+
current.streamRevealMaxMs,
|
|
2437
|
+
(next) => {
|
|
2438
|
+
void saveMotion({ streamRevealMaxMs: next });
|
|
2439
|
+
},
|
|
2440
|
+
locked
|
|
2441
|
+
),
|
|
2442
|
+
numberRow(
|
|
2443
|
+
tr("motion.stream-enter"),
|
|
2444
|
+
"",
|
|
2445
|
+
current.streamEnterMs,
|
|
2446
|
+
(next) => {
|
|
2447
|
+
void saveMotion({ streamEnterMs: next });
|
|
2448
|
+
},
|
|
2449
|
+
locked
|
|
2450
|
+
),
|
|
2451
|
+
numberRow(
|
|
2452
|
+
tr("motion.stream-roll"),
|
|
2453
|
+
"",
|
|
2454
|
+
current.streamRollMs,
|
|
2455
|
+
(next) => {
|
|
2456
|
+
void saveMotion({ streamRollMs: next });
|
|
2457
|
+
},
|
|
2458
|
+
locked
|
|
2459
|
+
)
|
|
2460
|
+
) : null;
|
|
1429
2461
|
return h(
|
|
1430
2462
|
"div",
|
|
1431
2463
|
{ className: "dsh-uix-row" },
|
|
@@ -1475,7 +2507,8 @@
|
|
|
1475
2507
|
disclosure(open, () => {
|
|
1476
2508
|
setOpen(!open);
|
|
1477
2509
|
}, tr("motion.options")),
|
|
1478
|
-
policy
|
|
2510
|
+
policy,
|
|
2511
|
+
timing
|
|
1479
2512
|
);
|
|
1480
2513
|
}
|
|
1481
2514
|
ctx.effect(() => ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
@@ -1499,6 +2532,7 @@
|
|
|
1499
2532
|
teardown();
|
|
1500
2533
|
motion.restore();
|
|
1501
2534
|
scrolling.restore();
|
|
2535
|
+
streaming.restore();
|
|
1502
2536
|
};
|
|
1503
2537
|
}, "ui-experience: page-level layers");
|
|
1504
2538
|
void poll();
|