@pictor-sdk/ui 0.2.1 → 0.2.2

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.mjs CHANGED
@@ -803,6 +803,7 @@ function LayerGestureSurface({ onSnap }) {
803
803
  const focalY = useSharedValue3(0.5);
804
804
  const pointers = useSharedValue3(0);
805
805
  const active = useSharedValue3(false);
806
+ const livePointers = useSharedValue3(0);
806
807
  const snapCX = useSharedValue3(false);
807
808
  const snapCY = useSharedValue3(false);
808
809
  const snapR = useSharedValue3(false);
@@ -977,7 +978,23 @@ function LayerGestureSurface({ onSnap }) {
977
978
  deleted
978
979
  );
979
980
  };
980
- const pan = Gesture4.Pan().minPointers(1).maxPointers(2).onBegin((e) => {
981
+ const releaseIfEmpty = () => {
982
+ "worklet";
983
+ if (livePointers.value > 0) return;
984
+ endIfIdle();
985
+ };
986
+ const pan = Gesture4.Pan().minPointers(1).maxPointers(2).onTouchesDown((e) => {
987
+ "worklet";
988
+ livePointers.value = e.allTouches.length;
989
+ }).onTouchesUp((e) => {
990
+ "worklet";
991
+ livePointers.value = Math.max(0, e.allTouches.length - e.changedTouches.length);
992
+ releaseIfEmpty();
993
+ }).onTouchesCancelled(() => {
994
+ "worklet";
995
+ livePointers.value = 0;
996
+ releaseIfEmpty();
997
+ }).onBegin((e) => {
981
998
  "worklet";
982
999
  if (active.value) return;
983
1000
  rawPanX.value = 0;
@@ -1024,7 +1041,7 @@ function LayerGestureSurface({ onSnap }) {
1024
1041
  recompute(false);
1025
1042
  }).onFinalize(() => {
1026
1043
  "worklet";
1027
- endIfIdle();
1044
+ releaseIfEmpty();
1028
1045
  });
1029
1046
  const pinch = Gesture4.Pinch().onUpdate((e) => {
1030
1047
  "worklet";
@@ -1033,6 +1050,9 @@ function LayerGestureSurface({ onSnap }) {
1033
1050
  focalY.value = e.focalY / metricsHeight;
1034
1051
  syncPointers(e.numberOfPointers);
1035
1052
  recompute(false);
1053
+ }).onFinalize(() => {
1054
+ "worklet";
1055
+ releaseIfEmpty();
1036
1056
  });
1037
1057
  const rotate = Gesture4.Rotation().enabled(config.rotationEnabled).onUpdate((e) => {
1038
1058
  "worklet";
@@ -1041,6 +1061,9 @@ function LayerGestureSurface({ onSnap }) {
1041
1061
  focalY.value = e.anchorY / metricsHeight;
1042
1062
  syncPointers(e.numberOfPointers);
1043
1063
  recompute(false);
1064
+ }).onFinalize(() => {
1065
+ "worklet";
1066
+ releaseIfEmpty();
1044
1067
  });
1045
1068
  const tap = Gesture4.Tap().onEnd((e) => {
1046
1069
  "worklet";
@@ -3499,17 +3522,21 @@ import {
3499
3522
  TextInput,
3500
3523
  View as View18
3501
3524
  } from "react-native";
3525
+ import { useRenderer as useRenderer4 } from "@pictor-sdk/skia";
3502
3526
 
3503
3527
  // src/composer/keyboardLayout.ts
3504
3528
  var HIDDEN_KEYBOARD = { height: 0 };
3505
3529
  var MAX_DURATION = 400;
3506
3530
  var FALLBACK_DURATION = 180;
3507
- function composerInsets(frame, safeAreaBottom = 0) {
3531
+ function composerInsets(frame, safeAreaBottom = 0, safeAreaTop = 0) {
3508
3532
  const height = Number.isFinite(frame.height) ? Math.max(0, frame.height) : 0;
3509
3533
  const visible = height > 0;
3510
3534
  const raw = frame.duration;
3511
3535
  const duration = raw === void 0 || !Number.isFinite(raw) || raw <= 0 ? FALLBACK_DURATION : Math.min(MAX_DURATION, raw);
3512
3536
  return {
3537
+ // The keyboard never covers the top, so this one is the safe area and
3538
+ // nothing else.
3539
+ top: Number.isFinite(safeAreaTop) ? Math.max(0, safeAreaTop) : 0,
3513
3540
  bottom: visible ? height : Math.max(0, safeAreaBottom),
3514
3541
  keyboardVisible: visible,
3515
3542
  duration
@@ -3529,6 +3556,31 @@ function fitCanvasRect(container, document) {
3529
3556
  top: (container.height - height) / 2
3530
3557
  };
3531
3558
  }
3559
+ function composerInputBox(request) {
3560
+ const canvas = fitCanvasRect(request.container, request.document);
3561
+ const fontSize = Math.max(1, request.fontSize * canvas.width);
3562
+ const lineHeight = Math.max(1, fontSize * request.lineHeight);
3563
+ const scale = request.document.width > 0 ? canvas.width / request.document.width : 0;
3564
+ const measuredHeight = request.measured && request.measured.height > 0 ? request.measured.height * scale : 0;
3565
+ const estimated = lineHeight * Math.max(1, request.lines);
3566
+ const height = Math.min(
3567
+ Math.max(lineHeight, measuredHeight > 0 ? measuredHeight : estimated),
3568
+ Math.max(lineHeight, canvas.height)
3569
+ );
3570
+ const centre = canvas.top + request.centerY * canvas.height;
3571
+ const top = Math.min(
3572
+ Math.max(0, centre - height / 2),
3573
+ Math.max(0, request.container.height - height)
3574
+ );
3575
+ return {
3576
+ left: canvas.left,
3577
+ width: Math.max(1, canvas.width),
3578
+ top,
3579
+ height: Math.max(1, height),
3580
+ fontSize,
3581
+ lineHeight
3582
+ };
3583
+ }
3532
3584
 
3533
3585
  // src/composer/useTextSession.ts
3534
3586
  import {
@@ -3676,26 +3728,45 @@ function TextComposer({ session, insets, onDone, onCancel }) {
3676
3728
  const store = useEditorStore5();
3677
3729
  const { document } = useEditor22();
3678
3730
  const [container, setContainer] = useState10({ width: 0, height: 0 });
3731
+ const renderer = useRenderer4();
3679
3732
  const input = useRef13(null);
3680
3733
  const [selectionRange, setSelectionRange] = useState10(
3681
3734
  void 0
3682
3735
  );
3683
3736
  const data = session.data;
3684
3737
  const span = session.span;
3685
- const layout = useMemo11(() => {
3686
- const canvas = fitCanvasRect(container, document.size);
3687
- const fontSize = (span?.fontSize ?? 0.08) * canvas.width;
3688
- const centreY = canvas.top + (session.layer?.transform.y ?? 0.42) * canvas.height;
3689
- const height = canvas.height;
3690
- return {
3691
- fontSize: Math.max(1, fontSize),
3692
- lineHeight: Math.max(1, fontSize * (span?.lineHeight ?? 1.2)),
3693
- left: canvas.left,
3694
- width: Math.max(1, canvas.width),
3695
- top: Math.max(0, centreY - height / 2),
3696
- height: Math.max(1, height)
3697
- };
3698
- }, [container, document.size, span?.fontSize, span?.lineHeight, session.layer?.transform.y]);
3738
+ const layerId = session.layer?.id;
3739
+ const [measured, setMeasured] = useState10(null);
3740
+ useEffect8(() => {
3741
+ if (!layerId) {
3742
+ setMeasured(null);
3743
+ return;
3744
+ }
3745
+ const refresh = () => setMeasured(renderer.textMeasures.get(layerId) ?? null);
3746
+ const unsubscribe = renderer.textMeasures.subscribe(refresh);
3747
+ refresh();
3748
+ return unsubscribe;
3749
+ }, [renderer, layerId]);
3750
+ const layout = useMemo11(
3751
+ () => composerInputBox({
3752
+ container,
3753
+ document: document.size,
3754
+ fontSize: span?.fontSize ?? 0.08,
3755
+ lineHeight: span?.lineHeight ?? 1.2,
3756
+ centerY: session.layer?.transform.y ?? 0.42,
3757
+ measured,
3758
+ lines: session.value.split("\n").length
3759
+ }),
3760
+ [
3761
+ container,
3762
+ document.size,
3763
+ span?.fontSize,
3764
+ span?.lineHeight,
3765
+ session.layer?.transform.y,
3766
+ session.value,
3767
+ measured
3768
+ ]
3769
+ );
3699
3770
  const fonts = useMemo11(() => fontChoices(store.registry.fonts), [store.registry.fonts]);
3700
3771
  useEffect8(() => {
3701
3772
  const handle = setTimeout(() => input.current?.focus(), 50);
@@ -3834,7 +3905,7 @@ function TextComposer({ session, insets, onDone, onCancel }) {
3834
3905
  position: "absolute",
3835
3906
  left: 0,
3836
3907
  right: 0,
3837
- top: 0,
3908
+ top: insets.top,
3838
3909
  height: 56,
3839
3910
  flexDirection: "row",
3840
3911
  alignItems: "center",
@@ -4025,7 +4096,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
4025
4096
  );
4026
4097
  }
4027
4098
  function TextComposerHost({
4028
- Composer = TextComposer
4099
+ Composer = TextComposer,
4100
+ safeArea
4029
4101
  }) {
4030
4102
  const { setActiveTool } = useEditor22();
4031
4103
  const session = useTextSession();
@@ -4054,7 +4126,10 @@ function TextComposerHost({
4054
4126
  hide.remove();
4055
4127
  };
4056
4128
  }, []);
4057
- const insets = useMemo11(() => composerInsets(frame), [frame]);
4129
+ const insets = useMemo11(
4130
+ () => composerInsets(frame, safeArea?.bottom ?? 0, safeArea?.top ?? 0),
4131
+ [frame, safeArea?.bottom, safeArea?.top]
4132
+ );
4058
4133
  const finish = useCallback6(
4059
4134
  (commit) => {
4060
4135
  if (commit) session.commit();