@pictor-sdk/ui 0.2.0 → 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
@@ -745,10 +745,12 @@ function LayerGestureSurface({ onSnap }) {
745
745
  );
746
746
  const aspect = document.size.height / document.size.width;
747
747
  const [measureVersion, setMeasureVersion] = useState2(0);
748
- useEffect2(
749
- () => renderer.textMeasures.subscribe(() => setMeasureVersion((v) => v + 1)),
750
- [renderer]
751
- );
748
+ useEffect2(() => {
749
+ const refresh = () => setMeasureVersion((v) => v + 1);
750
+ const unsubscribe = renderer.textMeasures.subscribe(refresh);
751
+ refresh();
752
+ return unsubscribe;
753
+ }, [renderer]);
752
754
  const hitCandidates = useMemo5(
753
755
  () => document.layers.map((layer, index) => ({ layer, index })).filter(
754
756
  ({ layer, index }) => !layer.hidden && !layer.locked && isDirectManipulationLayer(layer, index, profile.interaction?.layers)
@@ -801,6 +803,7 @@ function LayerGestureSurface({ onSnap }) {
801
803
  const focalY = useSharedValue3(0.5);
802
804
  const pointers = useSharedValue3(0);
803
805
  const active = useSharedValue3(false);
806
+ const livePointers = useSharedValue3(0);
804
807
  const snapCX = useSharedValue3(false);
805
808
  const snapCY = useSharedValue3(false);
806
809
  const snapR = useSharedValue3(false);
@@ -975,7 +978,23 @@ function LayerGestureSurface({ onSnap }) {
975
978
  deleted
976
979
  );
977
980
  };
978
- 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) => {
979
998
  "worklet";
980
999
  if (active.value) return;
981
1000
  rawPanX.value = 0;
@@ -1022,7 +1041,7 @@ function LayerGestureSurface({ onSnap }) {
1022
1041
  recompute(false);
1023
1042
  }).onFinalize(() => {
1024
1043
  "worklet";
1025
- endIfIdle();
1044
+ releaseIfEmpty();
1026
1045
  });
1027
1046
  const pinch = Gesture4.Pinch().onUpdate((e) => {
1028
1047
  "worklet";
@@ -1031,6 +1050,9 @@ function LayerGestureSurface({ onSnap }) {
1031
1050
  focalY.value = e.focalY / metricsHeight;
1032
1051
  syncPointers(e.numberOfPointers);
1033
1052
  recompute(false);
1053
+ }).onFinalize(() => {
1054
+ "worklet";
1055
+ releaseIfEmpty();
1034
1056
  });
1035
1057
  const rotate = Gesture4.Rotation().enabled(config.rotationEnabled).onUpdate((e) => {
1036
1058
  "worklet";
@@ -1039,6 +1061,9 @@ function LayerGestureSurface({ onSnap }) {
1039
1061
  focalY.value = e.anchorY / metricsHeight;
1040
1062
  syncPointers(e.numberOfPointers);
1041
1063
  recompute(false);
1064
+ }).onFinalize(() => {
1065
+ "worklet";
1066
+ releaseIfEmpty();
1042
1067
  });
1043
1068
  const tap = Gesture4.Tap().onEnd((e) => {
1044
1069
  "worklet";
@@ -3497,17 +3522,21 @@ import {
3497
3522
  TextInput,
3498
3523
  View as View18
3499
3524
  } from "react-native";
3525
+ import { useRenderer as useRenderer4 } from "@pictor-sdk/skia";
3500
3526
 
3501
3527
  // src/composer/keyboardLayout.ts
3502
3528
  var HIDDEN_KEYBOARD = { height: 0 };
3503
3529
  var MAX_DURATION = 400;
3504
3530
  var FALLBACK_DURATION = 180;
3505
- function composerInsets(frame, safeAreaBottom = 0) {
3531
+ function composerInsets(frame, safeAreaBottom = 0, safeAreaTop = 0) {
3506
3532
  const height = Number.isFinite(frame.height) ? Math.max(0, frame.height) : 0;
3507
3533
  const visible = height > 0;
3508
3534
  const raw = frame.duration;
3509
3535
  const duration = raw === void 0 || !Number.isFinite(raw) || raw <= 0 ? FALLBACK_DURATION : Math.min(MAX_DURATION, raw);
3510
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,
3511
3540
  bottom: visible ? height : Math.max(0, safeAreaBottom),
3512
3541
  keyboardVisible: visible,
3513
3542
  duration
@@ -3527,6 +3556,31 @@ function fitCanvasRect(container, document) {
3527
3556
  top: (container.height - height) / 2
3528
3557
  };
3529
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
+ }
3530
3584
 
3531
3585
  // src/composer/useTextSession.ts
3532
3586
  import {
@@ -3674,26 +3728,45 @@ function TextComposer({ session, insets, onDone, onCancel }) {
3674
3728
  const store = useEditorStore5();
3675
3729
  const { document } = useEditor22();
3676
3730
  const [container, setContainer] = useState10({ width: 0, height: 0 });
3731
+ const renderer = useRenderer4();
3677
3732
  const input = useRef13(null);
3678
3733
  const [selectionRange, setSelectionRange] = useState10(
3679
3734
  void 0
3680
3735
  );
3681
3736
  const data = session.data;
3682
3737
  const span = session.span;
3683
- const layout = useMemo11(() => {
3684
- const canvas = fitCanvasRect(container, document.size);
3685
- const fontSize = (span?.fontSize ?? 0.08) * canvas.width;
3686
- const centreY = canvas.top + (session.layer?.transform.y ?? 0.42) * canvas.height;
3687
- const height = canvas.height;
3688
- return {
3689
- fontSize: Math.max(1, fontSize),
3690
- lineHeight: Math.max(1, fontSize * (span?.lineHeight ?? 1.2)),
3691
- left: canvas.left,
3692
- width: Math.max(1, canvas.width),
3693
- top: Math.max(0, centreY - height / 2),
3694
- height: Math.max(1, height)
3695
- };
3696
- }, [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
+ );
3697
3770
  const fonts = useMemo11(() => fontChoices(store.registry.fonts), [store.registry.fonts]);
3698
3771
  useEffect8(() => {
3699
3772
  const handle = setTimeout(() => input.current?.focus(), 50);
@@ -3832,7 +3905,7 @@ function TextComposer({ session, insets, onDone, onCancel }) {
3832
3905
  position: "absolute",
3833
3906
  left: 0,
3834
3907
  right: 0,
3835
- top: 0,
3908
+ top: insets.top,
3836
3909
  height: 56,
3837
3910
  flexDirection: "row",
3838
3911
  alignItems: "center",
@@ -4023,7 +4096,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
4023
4096
  );
4024
4097
  }
4025
4098
  function TextComposerHost({
4026
- Composer = TextComposer
4099
+ Composer = TextComposer,
4100
+ safeArea
4027
4101
  }) {
4028
4102
  const { setActiveTool } = useEditor22();
4029
4103
  const session = useTextSession();
@@ -4052,7 +4126,10 @@ function TextComposerHost({
4052
4126
  hide.remove();
4053
4127
  };
4054
4128
  }, []);
4055
- 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
+ );
4056
4133
  const finish = useCallback6(
4057
4134
  (commit) => {
4058
4135
  if (commit) session.commit();