@dsh-plugin/dsh-ui-experience 1.0.0-dev.43493974985 → 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 +246 -17
- package/lib/client.js +1153 -91
- 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
|
} };
|
|
@@ -485,7 +490,20 @@
|
|
|
485
490
|
"End",
|
|
486
491
|
" "
|
|
487
492
|
]);
|
|
488
|
-
var EDITABLE =
|
|
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;
|
|
489
507
|
function scrollDistance(legs) {
|
|
490
508
|
let distance = 0;
|
|
491
509
|
for (const leg of legs) {
|
|
@@ -515,23 +533,77 @@
|
|
|
515
533
|
return out;
|
|
516
534
|
}
|
|
517
535
|
function createScrollMotion() {
|
|
518
|
-
const shipped =
|
|
536
|
+
const shipped = {
|
|
537
|
+
scrollTo: Element.prototype.scrollTo,
|
|
538
|
+
scrollBy: Element.prototype.scrollBy,
|
|
539
|
+
scrollIntoView: Element.prototype.scrollIntoView
|
|
540
|
+
};
|
|
541
|
+
const topAxis = Object.getOwnPropertyDescriptor(Element.prototype, "scrollTop");
|
|
542
|
+
const leftAxis = Object.getOwnPropertyDescriptor(Element.prototype, "scrollLeft");
|
|
519
543
|
let settings;
|
|
520
544
|
let run;
|
|
545
|
+
let wheel;
|
|
521
546
|
let installed = false;
|
|
522
|
-
|
|
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();
|
|
523
594
|
const active = run;
|
|
524
595
|
if (active === void 0) return;
|
|
525
596
|
window.cancelAnimationFrame(active.frame);
|
|
526
597
|
run = void 0;
|
|
527
|
-
active.
|
|
598
|
+
for (const leg of active.legs) release(leg.element, notify);
|
|
599
|
+
for (const settle of active.done) settle();
|
|
528
600
|
}
|
|
529
601
|
function step(now) {
|
|
530
602
|
const active = run;
|
|
531
603
|
if (active === void 0) return;
|
|
532
604
|
const elapsed = now - active.started;
|
|
533
605
|
const progress = active.duration <= 0 ? 1 : Math.min(1, elapsed / active.duration);
|
|
534
|
-
const eased = easeCurve(effectCurvePoints(settings
|
|
606
|
+
const eased = easeCurve(effectCurvePoints(settings.mode), progress);
|
|
535
607
|
for (let index = 0; index < active.legs.length; index += 1) {
|
|
536
608
|
const leg = active.legs[index];
|
|
537
609
|
const written = active.written[index];
|
|
@@ -540,16 +612,24 @@
|
|
|
540
612
|
cancel();
|
|
541
613
|
return;
|
|
542
614
|
}
|
|
615
|
+
}
|
|
616
|
+
for (const leg of active.legs) {
|
|
543
617
|
const top = progress >= 1 ? leg.toTop : leg.fromTop + (leg.toTop - leg.fromTop) * eased;
|
|
544
618
|
const left = progress >= 1 ? leg.toLeft : leg.fromLeft + (leg.toLeft - leg.fromLeft) * eased;
|
|
545
|
-
if (leg.element.scrollTop !== top) leg.element
|
|
546
|
-
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;
|
|
547
626
|
written.top = leg.element.scrollTop;
|
|
548
627
|
written.left = leg.element.scrollLeft;
|
|
549
628
|
}
|
|
550
629
|
if (progress >= 1) {
|
|
551
630
|
run = void 0;
|
|
552
|
-
active.
|
|
631
|
+
for (const leg of active.legs) release(leg.element, true);
|
|
632
|
+
for (const settle of active.done) settle();
|
|
553
633
|
return;
|
|
554
634
|
}
|
|
555
635
|
active.frame = window.requestAnimationFrame(step);
|
|
@@ -557,86 +637,400 @@
|
|
|
557
637
|
function start(legs, done) {
|
|
558
638
|
cancel();
|
|
559
639
|
const active = settings;
|
|
560
|
-
|
|
561
|
-
done();
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
640
|
+
for (const leg of legs) hold(leg.element);
|
|
564
641
|
run = {
|
|
565
|
-
legs,
|
|
642
|
+
legs: [...legs],
|
|
566
643
|
started: performance.now(),
|
|
567
644
|
duration: scrollDurationMs(scrollDistance(legs), active),
|
|
568
645
|
written: legs.map((leg) => ({ top: leg.element.scrollTop, left: leg.element.scrollLeft })),
|
|
569
646
|
frame: window.requestAnimationFrame(step),
|
|
570
|
-
done
|
|
647
|
+
done: [done]
|
|
571
648
|
};
|
|
572
649
|
}
|
|
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;
|
|
663
|
+
}
|
|
573
664
|
function wantsInstant() {
|
|
574
665
|
const active = settings;
|
|
575
|
-
if (active === void 0) return true;
|
|
576
666
|
if (active.reduced === "always") return true;
|
|
577
667
|
return active.reduced === "system" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
578
668
|
}
|
|
579
|
-
|
|
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;
|
|
682
|
+
const max = maxima(element);
|
|
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;
|
|
693
|
+
}
|
|
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);
|
|
728
|
+
};
|
|
729
|
+
glide.frame = window.requestAnimationFrame(step2);
|
|
730
|
+
}
|
|
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);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
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);
|
|
765
|
+
}
|
|
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();
|
|
795
|
+
}
|
|
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;
|
|
812
|
+
}
|
|
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;
|
|
819
|
+
}
|
|
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) {
|
|
580
835
|
const options = args[0];
|
|
581
836
|
const requested = typeof options === "object" && options !== null ? options : void 0;
|
|
582
837
|
const active = settings;
|
|
583
|
-
if (active === void 0 || requested?.behavior !== "smooth")
|
|
584
|
-
shipped.apply(this, args);
|
|
585
|
-
return void 0;
|
|
586
|
-
}
|
|
838
|
+
if (active === void 0 || requested?.behavior !== "smooth") return shipped.scrollIntoView.apply(this, args);
|
|
587
839
|
const jump = { ...requested, behavior: "instant" };
|
|
588
|
-
if (wantsInstant())
|
|
589
|
-
shipped.call(this, jump);
|
|
590
|
-
return void 0;
|
|
591
|
-
}
|
|
840
|
+
if (wantsInstant()) return shipped.scrollIntoView.call(this, jump);
|
|
592
841
|
const tracked = containers(this);
|
|
842
|
+
for (const element of tracked) hold(element);
|
|
593
843
|
const before = tracked.map((element) => ({ top: element.scrollTop, left: element.scrollLeft }));
|
|
594
|
-
shipped.call(this, jump);
|
|
844
|
+
shipped.scrollIntoView.call(this, jump);
|
|
595
845
|
const legs = [];
|
|
596
846
|
for (let index = 0; index < tracked.length; index += 1) {
|
|
597
847
|
const element = tracked[index];
|
|
598
848
|
const origin = before[index];
|
|
599
849
|
if (element === void 0 || origin === void 0) continue;
|
|
600
850
|
const landed = { top: element.scrollTop, left: element.scrollLeft };
|
|
601
|
-
element
|
|
602
|
-
element
|
|
851
|
+
writeAxis(element, "top", origin.top);
|
|
852
|
+
writeAxis(element, "left", origin.left);
|
|
603
853
|
const moved = Math.abs(landed.top - origin.top) > MOTION_EPSILON || Math.abs(landed.left - origin.left) > MOTION_EPSILON;
|
|
604
|
-
if (!moved)
|
|
854
|
+
if (!moved) {
|
|
855
|
+
if (run?.legs.some((leg) => leg.element === element)) continue;
|
|
856
|
+
release(element, false);
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
605
859
|
legs.push({ element, fromTop: origin.top, fromLeft: origin.left, toTop: landed.top, toLeft: landed.left });
|
|
606
860
|
}
|
|
607
|
-
if (legs.length === 0) return
|
|
861
|
+
if (legs.length === 0) return Promise.resolve();
|
|
608
862
|
return new Promise((resolve) => {
|
|
609
|
-
start(legs, resolve);
|
|
863
|
+
if (!merge(legs, resolve)) start(legs, resolve);
|
|
610
864
|
});
|
|
611
865
|
}
|
|
612
|
-
function
|
|
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
|
+
}
|
|
613
942
|
const active = run;
|
|
614
|
-
if (active === void 0) return;
|
|
615
|
-
if (
|
|
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;
|
|
947
|
+
}
|
|
948
|
+
function onTakeover(event) {
|
|
949
|
+
const key = event.type === "keydown";
|
|
950
|
+
if (key) {
|
|
616
951
|
if (event.key === void 0 || !SCROLL_KEYS.has(event.key)) return;
|
|
617
|
-
const
|
|
618
|
-
if (
|
|
619
|
-
|
|
952
|
+
const focus = event.target;
|
|
953
|
+
if (focus instanceof Element && focus.closest(EDITABLE) !== null) return;
|
|
954
|
+
}
|
|
955
|
+
if (run === void 0) {
|
|
956
|
+
clearGrace();
|
|
620
957
|
return;
|
|
621
958
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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;
|
|
625
965
|
}
|
|
626
966
|
function install() {
|
|
627
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
|
+
}
|
|
628
973
|
installed = true;
|
|
629
|
-
|
|
630
|
-
|
|
974
|
+
const prototype = Element.prototype;
|
|
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"]) {
|
|
631
1002
|
window.addEventListener(kind, onTakeover, { capture: true, passive: true });
|
|
632
1003
|
}
|
|
633
1004
|
}
|
|
634
1005
|
function uninstall() {
|
|
635
|
-
|
|
1006
|
+
finishGlides();
|
|
1007
|
+
cancel(false);
|
|
1008
|
+
clearGrace();
|
|
636
1009
|
if (!installed) return;
|
|
637
1010
|
installed = false;
|
|
638
|
-
|
|
639
|
-
|
|
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
|
+
}
|
|
1015
|
+
const prototype = Element.prototype;
|
|
1016
|
+
prototype.scrollTo = shipped.scrollTo;
|
|
1017
|
+
prototype.scrollBy = shipped.scrollBy;
|
|
1018
|
+
prototype.scrollIntoView = shipped.scrollIntoView;
|
|
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"]) {
|
|
640
1034
|
window.removeEventListener(kind, onTakeover, true);
|
|
641
1035
|
}
|
|
642
1036
|
}
|
|
@@ -647,6 +1041,552 @@
|
|
|
647
1041
|
if (!next.scroll || next.mode === "off") return;
|
|
648
1042
|
install();
|
|
649
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`);
|
|
1588
|
+
install();
|
|
1589
|
+
},
|
|
650
1590
|
restore: uninstall
|
|
651
1591
|
};
|
|
652
1592
|
}
|
|
@@ -738,16 +1678,14 @@
|
|
|
738
1678
|
.dsh-uix-segment-tab:disabled{cursor:default;opacity:.4}
|
|
739
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}
|
|
740
1680
|
|
|
741
|
-
/*
|
|
742
|
-
|
|
743
|
-
.
|
|
744
|
-
.dsh-uix-
|
|
745
|
-
.dsh-uix-
|
|
746
|
-
.dsh-uix-
|
|
747
|
-
.dsh-uix-
|
|
748
|
-
.dsh-uix-
|
|
749
|
-
.dsh-uix-check-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}
|
|
750
|
-
.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)}
|
|
751
1689
|
`;
|
|
752
1690
|
|
|
753
1691
|
// src/client.ts
|
|
@@ -802,7 +1740,14 @@
|
|
|
802
1740
|
durationScale: zod.number().optional(),
|
|
803
1741
|
spatialSplit: zod.boolean().optional(),
|
|
804
1742
|
quantize: zod.boolean().optional(),
|
|
805
|
-
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()
|
|
806
1751
|
}), "SetMotionArgs")
|
|
807
1752
|
]
|
|
808
1753
|
};
|
|
@@ -829,13 +1774,23 @@
|
|
|
829
1774
|
"motion.duration": "\u65F6\u957F\u500D\u7387",
|
|
830
1775
|
"motion.options": "\u9AD8\u7EA7\u9009\u9879",
|
|
831
1776
|
"motion.spatial-split": "\u4F4D\u79FB\u4E0E\u6548\u679C\u4F7F\u7528\u4E0D\u540C\u66F2\u7EBF",
|
|
832
|
-
"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",
|
|
833
1778
|
"motion.quantize": "\u65F6\u957F\u5BF9\u9F50\u5230 Material \u6863\u4F4D",
|
|
834
|
-
"motion.quantize-hint": "\
|
|
835
|
-
"motion.
|
|
836
|
-
"motion.
|
|
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",
|
|
1790
|
+
"motion.scroll": "\u6EDA\u52A8\u4F7F\u7528\u540C\u4E00\u66F2\u7EBF",
|
|
1791
|
+
"motion.scroll-hint": "\u5E73\u6ED1\u6EDA\u52A8\u3001\u6EDA\u8F6E\u4E0E\u5B9A\u4F4D\u8DF3\u8F6C\u6539\u7528\u672C\u9875\u66F2\u7EBF\u3002",
|
|
837
1792
|
"motion.reduced": "\u59CB\u7EC8\u51CF\u5F31\u52A8\u6548",
|
|
838
|
-
"motion.reduced-hint": "\
|
|
1793
|
+
"motion.reduced-hint": "\u5FFD\u7565\u7CFB\u7EDF\u504F\u597D\uFF0C\u4E00\u5F8B\u628A\u65F6\u957F\u538B\u5230 1ms\u3002",
|
|
839
1794
|
"motion.system-reduced": "\u7CFB\u7EDF\u5DF2\u8BF7\u6C42\u51CF\u5F31\u52A8\u6548\uFF0C\u672C\u9875\u52A8\u753B\u6B63\u5728\u88AB\u538B\u5236\u3002",
|
|
840
1795
|
"motion.save-failed": "\u52A8\u6548\u8BBE\u7F6E\u4FDD\u5B58\u5931\u8D25",
|
|
841
1796
|
"motion.inert": "\u66F2\u7EBF\u65CF\u4E3A\u300C\u5173\u95ED\u300D\uFF0C\u4EE5\u4E0B\u9009\u9879\u4E0D\u4F1A\u751F\u6548\u3002"
|
|
@@ -861,13 +1816,23 @@
|
|
|
861
1816
|
"motion.duration": "Duration scale",
|
|
862
1817
|
"motion.options": "Advanced options",
|
|
863
1818
|
"motion.spatial-split": "Spatial and effects take different curves",
|
|
864
|
-
"motion.spatial-split-hint": "
|
|
1819
|
+
"motion.spatial-split-hint": "Movement overshoots, effects do not; off uses the effects curve for both.",
|
|
865
1820
|
"motion.quantize": "Snap durations to the Material ladder",
|
|
866
|
-
"motion.quantize-hint": "
|
|
867
|
-
"motion.
|
|
868
|
-
"motion.
|
|
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.",
|
|
1832
|
+
"motion.scroll": "Scroll on the same curve",
|
|
1833
|
+
"motion.scroll-hint": "Drive smooth scrolling, the wheel, and locating jumps on this page's curve.",
|
|
869
1834
|
"motion.reduced": "Always reduce motion",
|
|
870
|
-
"motion.reduced-hint": "Ignore the system
|
|
1835
|
+
"motion.reduced-hint": "Ignore the system preference and force every duration to 1 ms.",
|
|
871
1836
|
"motion.system-reduced": "The system asked for reduced motion; this page is being held still.",
|
|
872
1837
|
"motion.save-failed": "Failed to save the motion settings",
|
|
873
1838
|
"motion.inert": "The curve family is Off, so these options have no effect."
|
|
@@ -908,6 +1873,7 @@
|
|
|
908
1873
|
borderWidth: "0.5px",
|
|
909
1874
|
paths: { colors: DEFAULT_COLORS_PATH, tokens: DEFAULT_TOKENS_PATH },
|
|
910
1875
|
motion: { ...MOTION_OFF },
|
|
1876
|
+
streamUnsupported: false,
|
|
911
1877
|
problem: "",
|
|
912
1878
|
notice: "",
|
|
913
1879
|
saving: false
|
|
@@ -915,6 +1881,7 @@
|
|
|
915
1881
|
const listeners = /* @__PURE__ */ new Set();
|
|
916
1882
|
const motion = createMotion();
|
|
917
1883
|
const scrolling = createScrollMotion();
|
|
1884
|
+
const streaming = createStreamMotion();
|
|
918
1885
|
const publish = (patch) => {
|
|
919
1886
|
view = { ...view, ...patch };
|
|
920
1887
|
for (const listener of listeners) listener();
|
|
@@ -931,8 +1898,9 @@
|
|
|
931
1898
|
if (disposed) return;
|
|
932
1899
|
motion.apply(next);
|
|
933
1900
|
scrolling.apply(next);
|
|
1901
|
+
streaming.apply(next);
|
|
934
1902
|
};
|
|
935
|
-
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;
|
|
936
1904
|
const clearOverride = () => {
|
|
937
1905
|
disposeOverride?.();
|
|
938
1906
|
disposeOverride = void 0;
|
|
@@ -983,7 +1951,12 @@
|
|
|
983
1951
|
return envelope.value;
|
|
984
1952
|
};
|
|
985
1953
|
const adopt = (answer) => {
|
|
986
|
-
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 };
|
|
987
1960
|
if (answer.motion === void 0 && !motionUnsupported) {
|
|
988
1961
|
motionUnsupported = true;
|
|
989
1962
|
console.error("ui-experience: this host build predates the motion settings; the M3 motion row stays inert until DSH restarts");
|
|
@@ -1056,6 +2029,10 @@
|
|
|
1056
2029
|
};
|
|
1057
2030
|
const saveMotion = async (change) => {
|
|
1058
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
|
+
}
|
|
1059
2036
|
writing = true;
|
|
1060
2037
|
publish({ saving: true, problem: "", notice: "" });
|
|
1061
2038
|
try {
|
|
@@ -1157,6 +2134,28 @@
|
|
|
1157
2134
|
),
|
|
1158
2135
|
h("span", null, label)
|
|
1159
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
|
+
);
|
|
1160
2159
|
const WALK_KEYS = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"];
|
|
1161
2160
|
const segmented = (id, value, segments, select, label, disabled) => {
|
|
1162
2161
|
const selected = Math.max(0, segments.findIndex((segment) => segment.value === value));
|
|
@@ -1206,23 +2205,26 @@
|
|
|
1206
2205
|
}, segment.label))
|
|
1207
2206
|
);
|
|
1208
2207
|
};
|
|
1209
|
-
const
|
|
1210
|
-
"
|
|
1211
|
-
{ className: "dsh-uix-
|
|
1212
|
-
h(
|
|
1213
|
-
|
|
1214
|
-
|
|
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,
|
|
1215
2222
|
disabled,
|
|
1216
|
-
|
|
1217
|
-
|
|
2223
|
+
className: "dsh-uix-switch",
|
|
2224
|
+
onClick: () => {
|
|
2225
|
+
set(!checked);
|
|
1218
2226
|
}
|
|
1219
|
-
})
|
|
1220
|
-
h(
|
|
1221
|
-
"span",
|
|
1222
|
-
{ className: "dsh-uix-check-text" },
|
|
1223
|
-
h("span", { className: "dsh-uix-check-label" }, label),
|
|
1224
|
-
hint === void 0 ? null : h("span", { className: "dsh-uix-check-hint" }, hint)
|
|
1225
|
-
)
|
|
2227
|
+
}, h("span", { className: "dsh-uix-switch-thumb", "aria-hidden": true }))
|
|
1226
2228
|
);
|
|
1227
2229
|
function PaletteRow(props) {
|
|
1228
2230
|
const tr = props.t ?? t;
|
|
@@ -1361,7 +2363,7 @@
|
|
|
1361
2363
|
const policy = open ? h(
|
|
1362
2364
|
"div",
|
|
1363
2365
|
{ className: "dsh-uix-form" },
|
|
1364
|
-
|
|
2366
|
+
option(
|
|
1365
2367
|
current.spatialSplit,
|
|
1366
2368
|
(next) => {
|
|
1367
2369
|
void saveMotion({ spatialSplit: next });
|
|
@@ -1370,7 +2372,7 @@
|
|
|
1370
2372
|
tr("motion.spatial-split-hint"),
|
|
1371
2373
|
locked
|
|
1372
2374
|
),
|
|
1373
|
-
|
|
2375
|
+
option(
|
|
1374
2376
|
current.quantize,
|
|
1375
2377
|
(next) => {
|
|
1376
2378
|
void saveMotion({ quantize: next });
|
|
@@ -1379,7 +2381,25 @@
|
|
|
1379
2381
|
tr("motion.quantize-hint"),
|
|
1380
2382
|
locked
|
|
1381
2383
|
),
|
|
1382
|
-
|
|
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(
|
|
1383
2403
|
current.scroll,
|
|
1384
2404
|
(next) => {
|
|
1385
2405
|
void saveMotion({ scroll: next });
|
|
@@ -1388,7 +2408,7 @@
|
|
|
1388
2408
|
tr("motion.scroll-hint"),
|
|
1389
2409
|
locked
|
|
1390
2410
|
),
|
|
1391
|
-
|
|
2411
|
+
option(
|
|
1392
2412
|
current.reduced === "always",
|
|
1393
2413
|
(next) => {
|
|
1394
2414
|
void saveMotion({ reduced: next ? "always" : "system" });
|
|
@@ -1398,6 +2418,46 @@
|
|
|
1398
2418
|
locked
|
|
1399
2419
|
)
|
|
1400
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;
|
|
1401
2461
|
return h(
|
|
1402
2462
|
"div",
|
|
1403
2463
|
{ className: "dsh-uix-row" },
|
|
@@ -1447,7 +2507,8 @@
|
|
|
1447
2507
|
disclosure(open, () => {
|
|
1448
2508
|
setOpen(!open);
|
|
1449
2509
|
}, tr("motion.options")),
|
|
1450
|
-
policy
|
|
2510
|
+
policy,
|
|
2511
|
+
timing
|
|
1451
2512
|
);
|
|
1452
2513
|
}
|
|
1453
2514
|
ctx.effect(() => ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
@@ -1471,6 +2532,7 @@
|
|
|
1471
2532
|
teardown();
|
|
1472
2533
|
motion.restore();
|
|
1473
2534
|
scrolling.restore();
|
|
2535
|
+
streaming.restore();
|
|
1474
2536
|
};
|
|
1475
2537
|
}, "ui-experience: page-level layers");
|
|
1476
2538
|
void poll();
|