@mmtitanl/tablets-core 0.3.0 → 0.4.0

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/dist/index.js CHANGED
@@ -700,6 +700,14 @@ function ensureBuiltinsRegistered() {
700
700
  }
701
701
  didAutoRegister = true;
702
702
  }
703
+ var ROTATE_MS = 450;
704
+ var EASE = "cubic-bezier(0.4, 0, 0.2, 1)";
705
+ var SVG_FADE_MS = 240;
706
+ var SVG_FADE_DELAY_MS = 100;
707
+ var CONTENT_FADE_OUT_MS = 140;
708
+ var CONTENT_SWAP_MS = 180;
709
+ var CONTENT_FADE_IN_DELAY_MS = 220;
710
+ var CONTENT_FADE_IN_MS = 230;
703
711
  function sendDeviceInfo(iframe, contract, orientation) {
704
712
  if (!iframe?.contentWindow) return;
705
713
  iframe.contentWindow.postMessage(
@@ -737,7 +745,24 @@ function DeviceFrame({
737
745
  const resolvedId = device ?? deviceId;
738
746
  if (!resolvedId) throw new Error("DeviceFrame requires `device` or `deviceId`");
739
747
  const meta = getDeviceMetadata(resolvedId);
740
- const contract = useMemo3(() => getDeviceContract2(resolvedId, orientation), [resolvedId, orientation]);
748
+ const [contentOrientation, setContentOrientation] = useState8(orientation);
749
+ const [contentVisible, setContentVisible] = useState8(true);
750
+ const animTimers = useRef2([]);
751
+ const prevOrientation = useRef2(orientation);
752
+ useEffect5(() => {
753
+ if (prevOrientation.current === orientation) return;
754
+ prevOrientation.current = orientation;
755
+ for (const t of animTimers.current) window.clearTimeout(t);
756
+ setContentVisible(false);
757
+ animTimers.current = [
758
+ window.setTimeout(() => setContentOrientation(orientation), CONTENT_SWAP_MS),
759
+ window.setTimeout(() => setContentVisible(true), CONTENT_FADE_IN_DELAY_MS)
760
+ ];
761
+ }, [orientation]);
762
+ useEffect5(() => () => {
763
+ for (const t of animTimers.current) window.clearTimeout(t);
764
+ }, []);
765
+ const contract = useMemo3(() => getDeviceContract2(resolvedId, contentOrientation), [resolvedId, contentOrientation]);
741
766
  const portW = meta.screen.width;
742
767
  const portH = meta.screen.height;
743
768
  const rotateFrame = orientation === "landscape";
@@ -746,12 +771,36 @@ function DeviceFrame({
746
771
  const sentinelRef = useRef2(null);
747
772
  const frameContainerRef = useRef2(null);
748
773
  const containerSize = useContainerSize(sentinelRef);
749
- const svgEntryEarly = getDeviceSVG(resolvedId);
750
- const portraitFrameEarly = svgEntryEarly?.frame;
751
- const landscapeFrameEarly = svgEntryEarly?.landscapeFrame;
752
- const hasLandscapeSVGEarly = !!svgEntryEarly?.landscapeComponent && !!landscapeFrameEarly;
753
- const fitW = portraitFrameEarly ? rotateFrame ? hasLandscapeSVGEarly ? landscapeFrameEarly.totalWidth : portraitFrameEarly.totalHeight : portraitFrameEarly.totalWidth : rotateFrame ? portH : portW;
754
- const fitH = portraitFrameEarly ? rotateFrame ? hasLandscapeSVGEarly ? landscapeFrameEarly.totalHeight : portraitFrameEarly.totalWidth : portraitFrameEarly.totalHeight : rotateFrame ? portW : portH;
774
+ const svgEntry = getDeviceSVG(resolvedId);
775
+ const SVGComponent = svgEntry?.component ?? null;
776
+ const LandscapeSVGComponent = svgEntry?.landscapeComponent ?? null;
777
+ const registeredLandscapeFrame = svgEntry?.landscapeFrame;
778
+ const hasLandscapeSVG = !!LandscapeSVGComponent && !!registeredLandscapeFrame;
779
+ const portFrame = svgEntry?.frame ?? {
780
+ bezelTop: 0,
781
+ bezelBottom: 0,
782
+ bezelLeft: 0,
783
+ bezelRight: 0,
784
+ totalWidth: portW,
785
+ totalHeight: portH,
786
+ screenWidth: portW,
787
+ screenHeight: portH,
788
+ screenRadius: 0
789
+ };
790
+ const landFrame = hasLandscapeSVG && registeredLandscapeFrame ? registeredLandscapeFrame : {
791
+ bezelTop: portFrame.totalWidth - portFrame.bezelLeft - portFrame.screenWidth,
792
+ bezelBottom: portFrame.bezelLeft,
793
+ bezelLeft: portFrame.bezelTop,
794
+ bezelRight: portFrame.totalHeight - portFrame.bezelTop - portFrame.screenHeight,
795
+ totalWidth: portFrame.totalHeight,
796
+ totalHeight: portFrame.totalWidth,
797
+ screenWidth: portFrame.screenHeight,
798
+ screenHeight: portFrame.screenWidth,
799
+ screenRadius: portFrame.screenRadius
800
+ };
801
+ const activeFrame = rotateFrame ? landFrame : portFrame;
802
+ const fitW = activeFrame.totalWidth;
803
+ const fitH = activeFrame.totalHeight;
755
804
  const fitResult = useMemo3(
756
805
  () => computeFullScale(fitW, fitH, containerSize.width, containerSize.height, {
757
806
  snapToSteps: scaleMode === "steps"
@@ -774,41 +823,32 @@ function DeviceFrame({
774
823
  }, [scale, onScaleChange]);
775
824
  useEffect5(() => {
776
825
  if (!iframeRef?.current) return;
777
- sendDeviceInfo(iframeRef.current, contract, orientation);
778
- const onLoad = () => sendDeviceInfo(iframeRef.current, contract, orientation);
826
+ sendDeviceInfo(iframeRef.current, contract, contentOrientation);
827
+ const onLoad = () => sendDeviceInfo(iframeRef.current, contract, contentOrientation);
779
828
  iframeRef.current.addEventListener("load", onLoad);
780
829
  return () => iframeRef.current?.removeEventListener("load", onLoad);
781
- }, [iframeRef, contract, orientation]);
830
+ }, [iframeRef, contract, contentOrientation]);
782
831
  useEffect5(() => {
783
832
  if (!iframeRef) return;
784
833
  const handler = (event) => {
785
834
  const data = event.data;
786
835
  if (!data || typeof data !== "object") return;
787
836
  if (data.type === "biela:requestDeviceInfo") {
788
- sendDeviceInfo(iframeRef.current, contract, orientation);
837
+ sendDeviceInfo(iframeRef.current, contract, contentOrientation);
789
838
  } else if (data.type === "biela:colorScheme" && data.payload?.scheme) {
790
839
  onColorSchemeChange?.(data.payload.scheme);
791
840
  }
792
841
  };
793
842
  window.addEventListener("message", handler);
794
843
  return () => window.removeEventListener("message", handler);
795
- }, [iframeRef, contract, orientation, onColorSchemeChange]);
796
- const svgEntry = getDeviceSVG(resolvedId);
797
- const SVGComponent = svgEntry?.component ?? null;
798
- const portraitFrame = svgEntry?.frame;
799
- const LandscapeSVGComponent = svgEntry?.landscapeComponent ?? null;
800
- const landscapeFrame = svgEntry?.landscapeFrame;
801
- const hasLandscapeSVG = !!LandscapeSVGComponent && !!landscapeFrame;
844
+ }, [iframeRef, contract, contentOrientation, onColorSchemeChange]);
802
845
  const cssVarsStyle = contract.cssVariables;
803
- const activeFrame = hasLandscapeSVG && rotateFrame ? landscapeFrame : portraitFrame;
804
- const scalerW = hasLandscapeSVG ? Math.max(portraitFrame?.totalWidth ?? dw, landscapeFrame?.totalWidth ?? dh) : activeFrame?.totalWidth ?? (rotateFrame ? portW : dw);
805
- const scalerH = hasLandscapeSVG ? Math.max(portraitFrame?.totalHeight ?? dh, landscapeFrame?.totalHeight ?? dw) : activeFrame?.totalHeight ?? (rotateFrame ? portH : dh);
806
- const contentBezelLeft = activeFrame?.bezelLeft ?? 0;
807
- const contentBezelTop = activeFrame?.bezelTop ?? 0;
808
- const contentScreenW = activeFrame?.screenWidth ?? dw;
809
- const contentScreenH = activeFrame?.screenHeight ?? dh;
810
- const useRotationFallback = rotateFrame && !hasLandscapeSVG;
811
- const scalerTransform = useRotationFallback ? `scale(${scale}) translate(0px, ${scalerW}px) rotate(-90deg)` : `scale(${scale})`;
846
+ const scalerW = portFrame.totalWidth;
847
+ const scalerH = portFrame.totalHeight;
848
+ const scalerTransform = rotateFrame ? `scale(${scale}) translate(0px, ${landFrame.totalHeight}px) rotate(-90deg)` : `scale(${scale})`;
849
+ const contentLandscape = contentOrientation === "landscape";
850
+ const contentBox = contentLandscape ? { left: landFrame.totalHeight - landFrame.bezelTop, top: landFrame.bezelLeft, width: landFrame.screenWidth, height: landFrame.screenHeight } : { left: portFrame.bezelLeft, top: portFrame.bezelTop, width: portFrame.screenWidth, height: portFrame.screenHeight };
851
+ const contentRadius = contentLandscape ? landFrame.screenRadius : portFrame.screenRadius;
812
852
  return /* @__PURE__ */ jsxs4(
813
853
  "div",
814
854
  {
@@ -825,8 +865,10 @@ function DeviceFrame({
825
865
  height: hostHeight,
826
866
  position: "relative",
827
867
  flexShrink: 0,
828
- overflow: "hidden",
829
- transition: "width 400ms cubic-bezier(0.4, 0, 0.2, 1), height 400ms cubic-bezier(0.4, 0, 0.2, 1)"
868
+ // No overflow clipping: at rest the frame fits the host exactly; while
869
+ // rotating, the corners sweep outside and clipping them flat looks bad.
870
+ // The sentinel still clips at the component boundary.
871
+ transition: `width ${ROTATE_MS}ms ${EASE}, height ${ROTATE_MS}ms ${EASE}`
830
872
  },
831
873
  children: /* @__PURE__ */ jsxs4(
832
874
  "div",
@@ -842,7 +884,7 @@ function DeviceFrame({
842
884
  transform: scalerTransform,
843
885
  transformOrigin: "top left",
844
886
  willChange: "transform",
845
- transition: "transform 400ms cubic-bezier(0.4, 0, 0.2, 1)"
887
+ transition: `transform ${ROTATE_MS}ms ${EASE}`
846
888
  },
847
889
  children: [
848
890
  /* @__PURE__ */ jsxs4(
@@ -851,24 +893,27 @@ function DeviceFrame({
851
893
  className: "bielaframe-content",
852
894
  style: {
853
895
  position: "absolute",
854
- left: `${contentBezelLeft / scalerW * 100}%`,
855
- top: `${contentBezelTop / scalerH * 100}%`,
856
- width: `${contentScreenW / scalerW * 100}%`,
857
- height: `${contentScreenH / scalerH * 100}%`,
896
+ left: contentBox.left,
897
+ top: contentBox.top,
898
+ width: contentBox.width,
899
+ height: contentBox.height,
900
+ ...contentLandscape ? { transform: "rotate(90deg)", transformOrigin: "top left" } : null,
858
901
  overflow: "hidden",
859
902
  zIndex: 0,
860
903
  background: colorScheme === "dark" ? "#000" : "#fff",
861
- borderRadius: activeFrame?.screenRadius ?? 0,
904
+ borderRadius: contentRadius,
905
+ opacity: contentVisible ? 1 : 0,
906
+ transition: `opacity ${contentVisible ? CONTENT_FADE_IN_MS : CONTENT_FADE_OUT_MS}ms ${EASE}`,
862
907
  ...cssVarsStyle
863
908
  },
864
909
  children: [
865
910
  /* @__PURE__ */ jsx6(DeviceErrorBoundary, { children }),
866
- showStatusBar && /* @__PURE__ */ jsx6(DynamicStatusBar, { contract, orientation, colorScheme }),
867
- showSafeAreaOverlay && /* @__PURE__ */ jsx6(SafeAreaOverlay, { contract, orientation })
911
+ showStatusBar && /* @__PURE__ */ jsx6(DynamicStatusBar, { contract, orientation: contentOrientation, colorScheme }),
912
+ showSafeAreaOverlay && /* @__PURE__ */ jsx6(SafeAreaOverlay, { contract, orientation: contentOrientation })
868
913
  ]
869
914
  }
870
915
  ),
871
- SVGComponent && portraitFrame && /* @__PURE__ */ jsx6(
916
+ SVGComponent && /* @__PURE__ */ jsx6(
872
917
  "div",
873
918
  {
874
919
  "aria-hidden": true,
@@ -876,12 +921,12 @@ function DeviceFrame({
876
921
  position: "absolute",
877
922
  top: 0,
878
923
  left: 0,
879
- width: portraitFrame.totalWidth,
880
- height: portraitFrame.totalHeight,
924
+ width: portFrame.totalWidth,
925
+ height: portFrame.totalHeight,
881
926
  pointerEvents: "none",
882
927
  zIndex: 1,
883
928
  opacity: hasLandscapeSVG && rotateFrame ? 0 : 1,
884
- transition: "opacity 400ms cubic-bezier(0.4, 0, 0.2, 1)"
929
+ transition: `opacity ${SVG_FADE_MS}ms ${EASE} ${SVG_FADE_DELAY_MS}ms`
885
930
  },
886
931
  children: /* @__PURE__ */ jsx6(
887
932
  SVGComponent,
@@ -892,20 +937,22 @@ function DeviceFrame({
892
937
  )
893
938
  }
894
939
  ),
895
- hasLandscapeSVG && LandscapeSVGComponent && landscapeFrame && /* @__PURE__ */ jsx6(
940
+ hasLandscapeSVG && LandscapeSVGComponent && /* @__PURE__ */ jsx6(
896
941
  "div",
897
942
  {
898
943
  "aria-hidden": true,
899
944
  style: {
900
945
  position: "absolute",
946
+ left: landFrame.totalHeight,
901
947
  top: 0,
902
- left: 0,
903
- width: landscapeFrame.totalWidth,
904
- height: landscapeFrame.totalHeight,
948
+ width: landFrame.totalWidth,
949
+ height: landFrame.totalHeight,
950
+ transform: "rotate(90deg)",
951
+ transformOrigin: "top left",
905
952
  pointerEvents: "none",
906
953
  zIndex: 1,
907
954
  opacity: rotateFrame ? 1 : 0,
908
- transition: "opacity 400ms cubic-bezier(0.4, 0, 0.2, 1)"
955
+ transition: `opacity ${SVG_FADE_MS}ms ${EASE} ${SVG_FADE_DELAY_MS}ms`
909
956
  },
910
957
  children: /* @__PURE__ */ jsx6(
911
958
  LandscapeSVGComponent,