@pictor-sdk/ui 0.2.2 → 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.d.mts +46 -7
- package/dist/index.d.ts +46 -7
- package/dist/index.js +180 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +179 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/components/Slider.tsx +3 -2
- package/src/composer/TextComposer.tsx +180 -9
- package/src/composer/keyboardLayout.ts +94 -1
- package/src/composer/tapGesture.ts +31 -0
- package/src/gestures/LayerGestureSurface.tsx +40 -4
- package/src/gestures/selectionTap.ts +22 -0
- package/src/index.ts +1 -1
- package/src/panels/layoutMath.ts +23 -0
package/dist/index.mjs
CHANGED
|
@@ -727,10 +727,16 @@ function hitTestLayerCandidatesUI(x, y, candidates, documentWidth, documentHeigh
|
|
|
727
727
|
return null;
|
|
728
728
|
}
|
|
729
729
|
|
|
730
|
+
// src/gestures/selectionTap.ts
|
|
731
|
+
function selectedLayerTapId(hit, wasSelectedAtStart) {
|
|
732
|
+
if (!hit || hit.locked || !wasSelectedAtStart) return null;
|
|
733
|
+
return hit.id;
|
|
734
|
+
}
|
|
735
|
+
|
|
730
736
|
// src/gestures/LayerGestureSurface.tsx
|
|
731
737
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
732
|
-
function LayerGestureSurface({ onSnap }) {
|
|
733
|
-
const { document, dispatch, profile } = useEditor5();
|
|
738
|
+
function LayerGestureSurface({ onSnap, onSelectedLayerTap }) {
|
|
739
|
+
const { document, dispatch, profile, selection } = useEditor5();
|
|
734
740
|
const store = useEditorStore2();
|
|
735
741
|
const metrics = useCanvasMetrics4();
|
|
736
742
|
const { setPreview, setLayerDragActive, setOverDeleteTarget, setSnap } = useInteractionState();
|
|
@@ -821,10 +827,11 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
821
827
|
document.size.height
|
|
822
828
|
);
|
|
823
829
|
}, [document.size.width, document.size.height]);
|
|
824
|
-
const latest = useRef7({ document, metrics, store, dispatch, onSnap, profile });
|
|
825
|
-
latest.current = { document, metrics, store, dispatch, onSnap, profile };
|
|
830
|
+
const latest = useRef7({ document, metrics, store, dispatch, onSnap, profile, selection, onSelectedLayerTap });
|
|
831
|
+
latest.current = { document, metrics, store, dispatch, onSnap, profile, selection, onSelectedLayerTap };
|
|
826
832
|
const grabbedId = useRef7(null);
|
|
827
833
|
const grabbedFrom = useRef7(null);
|
|
834
|
+
const tapWasSelected = useRef7(false);
|
|
828
835
|
const snapState = useRef7(NO_SNAP2);
|
|
829
836
|
const onGrabLayer = useCallback3((id) => {
|
|
830
837
|
const { document: doc, store: s } = latest.current;
|
|
@@ -837,9 +844,23 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
837
844
|
setPreview({ layerId: hit.id, transform: previewTransform });
|
|
838
845
|
setLayerDragActive(true);
|
|
839
846
|
}, [setPreview, setLayerDragActive, previewTransform]);
|
|
847
|
+
const snapshotTapAt = useCallback3((xDp, yDp) => {
|
|
848
|
+
const { document: doc, metrics: m, store: s, selection: selected } = latest.current;
|
|
849
|
+
if (!m) return;
|
|
850
|
+
const layerInteraction = latest.current.profile.interaction?.layers;
|
|
851
|
+
const hit = hitTestDocument(
|
|
852
|
+
doc,
|
|
853
|
+
{ x: xDp / m.width, y: yDp / m.height },
|
|
854
|
+
s.registry,
|
|
855
|
+
(layer, index) => isDirectManipulationLayer(layer, index, layerInteraction)
|
|
856
|
+
);
|
|
857
|
+
tapWasSelected.current = Boolean(hit && selected.includes(hit.id));
|
|
858
|
+
}, []);
|
|
840
859
|
const onSelectAt = useCallback3((xDp, yDp) => {
|
|
841
860
|
const { document: doc, metrics: m, store: s } = latest.current;
|
|
842
861
|
if (!m) return;
|
|
862
|
+
const wasSelected = tapWasSelected.current;
|
|
863
|
+
tapWasSelected.current = false;
|
|
843
864
|
const layerInteraction = latest.current.profile.interaction?.layers;
|
|
844
865
|
const hit = hitTestDocument(
|
|
845
866
|
doc,
|
|
@@ -848,6 +869,8 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
848
869
|
(layer, index) => isDirectManipulationLayer(layer, index, layerInteraction)
|
|
849
870
|
);
|
|
850
871
|
s.select(hit && !hit.locked ? [hit.id] : []);
|
|
872
|
+
const editId = selectedLayerTapId(hit ?? null, wasSelected);
|
|
873
|
+
if (editId) latest.current.onSelectedLayerTap?.(editId);
|
|
851
874
|
}, []);
|
|
852
875
|
const onSnapChanged = useCallback3(
|
|
853
876
|
(centerX, centerY, rotation) => {
|
|
@@ -986,6 +1009,8 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
986
1009
|
const pan = Gesture4.Pan().minPointers(1).maxPointers(2).onTouchesDown((e) => {
|
|
987
1010
|
"worklet";
|
|
988
1011
|
livePointers.value = e.allTouches.length;
|
|
1012
|
+
const touch = e.changedTouches[0] ?? e.allTouches[0];
|
|
1013
|
+
if (touch) runOnJS4(snapshotTapAt)(touch.x, touch.y);
|
|
989
1014
|
}).onTouchesUp((e) => {
|
|
990
1015
|
"worklet";
|
|
991
1016
|
livePointers.value = Math.max(0, e.allTouches.length - e.changedTouches.length);
|
|
@@ -1076,6 +1101,7 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
1076
1101
|
deleteTarget,
|
|
1077
1102
|
aspect,
|
|
1078
1103
|
onGrabLayer,
|
|
1104
|
+
snapshotTapAt,
|
|
1079
1105
|
onSelectAt,
|
|
1080
1106
|
onSnapChanged,
|
|
1081
1107
|
onHoverChanged,
|
|
@@ -1769,8 +1795,9 @@ function Slider({
|
|
|
1769
1795
|
const valueRef = useRef9(value);
|
|
1770
1796
|
valueRef.current = value;
|
|
1771
1797
|
const onLayout = (e) => {
|
|
1772
|
-
|
|
1773
|
-
widthRef.current =
|
|
1798
|
+
const next = e.nativeEvent.layout.width;
|
|
1799
|
+
widthRef.current = next;
|
|
1800
|
+
setWidth((previous) => previous === next ? previous : next);
|
|
1774
1801
|
};
|
|
1775
1802
|
const handlers = useMemo7(() => {
|
|
1776
1803
|
const begin = () => {
|
|
@@ -2888,6 +2915,13 @@ function fitImageToLayoutCell(cell, documentSize, intrinsic, options = {}) {
|
|
|
2888
2915
|
function cellToTransformPatch(cell) {
|
|
2889
2916
|
return { x: cell.x, y: cell.y, scale: cell.scale, rotation: 0 };
|
|
2890
2917
|
}
|
|
2918
|
+
function layoutCellBox(cell, documentSize) {
|
|
2919
|
+
"worklet";
|
|
2920
|
+
return {
|
|
2921
|
+
width: cell.width * documentSize.width,
|
|
2922
|
+
height: cell.height * documentSize.height
|
|
2923
|
+
};
|
|
2924
|
+
}
|
|
2891
2925
|
|
|
2892
2926
|
// src/panels/LayoutPanel.tsx
|
|
2893
2927
|
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
@@ -3557,7 +3591,8 @@ function fitCanvasRect(container, document) {
|
|
|
3557
3591
|
};
|
|
3558
3592
|
}
|
|
3559
3593
|
function composerInputBox(request) {
|
|
3560
|
-
const
|
|
3594
|
+
const provided = request.canvas;
|
|
3595
|
+
const canvas = provided && provided.width > 0 && provided.height > 0 ? provided : fitCanvasRect(request.container, request.document);
|
|
3561
3596
|
const fontSize = Math.max(1, request.fontSize * canvas.width);
|
|
3562
3597
|
const lineHeight = Math.max(1, fontSize * request.lineHeight);
|
|
3563
3598
|
const scale = request.document.width > 0 ? canvas.width / request.document.width : 0;
|
|
@@ -3581,6 +3616,35 @@ function composerInputBox(request) {
|
|
|
3581
3616
|
lineHeight
|
|
3582
3617
|
};
|
|
3583
3618
|
}
|
|
3619
|
+
function sameCaretLine(previous, next) {
|
|
3620
|
+
return previous === next || previous !== null && next !== null && previous.x === next.x && previous.y === next.y && previous.width === next.width && previous.height === next.height;
|
|
3621
|
+
}
|
|
3622
|
+
var CARET_WIDTH = 2;
|
|
3623
|
+
function composerInputOpacity(platform) {
|
|
3624
|
+
return platform === "android" ? 0 : 1;
|
|
3625
|
+
}
|
|
3626
|
+
function composerCaretBox(box, line) {
|
|
3627
|
+
if (!line || !Number.isFinite(line.x) || !Number.isFinite(line.y) || !Number.isFinite(line.width) || !Number.isFinite(line.height)) {
|
|
3628
|
+
return null;
|
|
3629
|
+
}
|
|
3630
|
+
const height = Math.max(1, line.height || box.lineHeight);
|
|
3631
|
+
return {
|
|
3632
|
+
left: box.left + line.x + line.width - CARET_WIDTH / 2,
|
|
3633
|
+
top: box.top + line.y,
|
|
3634
|
+
width: CARET_WIDTH,
|
|
3635
|
+
height
|
|
3636
|
+
};
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
// src/composer/tapGesture.ts
|
|
3640
|
+
var TAP_SLOP = 8;
|
|
3641
|
+
function pressMoved(start, end, slop = TAP_SLOP) {
|
|
3642
|
+
if (!start || !end) return false;
|
|
3643
|
+
if (!Number.isFinite(start.x) || !Number.isFinite(start.y) || !Number.isFinite(end.x) || !Number.isFinite(end.y)) {
|
|
3644
|
+
return false;
|
|
3645
|
+
}
|
|
3646
|
+
return Math.hypot(end.x - start.x, end.y - start.y) > Math.max(0, slop);
|
|
3647
|
+
}
|
|
3584
3648
|
|
|
3585
3649
|
// src/composer/useTextSession.ts
|
|
3586
3650
|
import {
|
|
@@ -3723,7 +3787,7 @@ var SHADOWS = [
|
|
|
3723
3787
|
{ color: "#000000", blur: 0.012, dx: 2e-3, dy: 4e-3 },
|
|
3724
3788
|
{ color: "#000000", blur: 2e-3, dx: 6e-3, dy: 8e-3 }
|
|
3725
3789
|
];
|
|
3726
|
-
function TextComposer({ session, insets, onDone, onCancel }) {
|
|
3790
|
+
function TextComposer({ session, insets, onDone, onCancel, canvas }) {
|
|
3727
3791
|
const theme = useTheme();
|
|
3728
3792
|
const store = useEditorStore5();
|
|
3729
3793
|
const { document } = useEditor22();
|
|
@@ -3733,6 +3797,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3733
3797
|
const [selectionRange, setSelectionRange] = useState10(
|
|
3734
3798
|
void 0
|
|
3735
3799
|
);
|
|
3800
|
+
const [focused, setFocused] = useState10(false);
|
|
3801
|
+
const [caretLine, setCaretLine] = useState10(null);
|
|
3736
3802
|
const data = session.data;
|
|
3737
3803
|
const span = session.span;
|
|
3738
3804
|
const layerId = session.layer?.id;
|
|
@@ -3742,7 +3808,12 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3742
3808
|
setMeasured(null);
|
|
3743
3809
|
return;
|
|
3744
3810
|
}
|
|
3745
|
-
const refresh = () =>
|
|
3811
|
+
const refresh = () => {
|
|
3812
|
+
const next = renderer.textMeasures.get(layerId) ?? null;
|
|
3813
|
+
setMeasured(
|
|
3814
|
+
(previous) => previous && next && previous.width === next.width && previous.height === next.height ? previous : next
|
|
3815
|
+
);
|
|
3816
|
+
};
|
|
3746
3817
|
const unsubscribe = renderer.textMeasures.subscribe(refresh);
|
|
3747
3818
|
refresh();
|
|
3748
3819
|
return unsubscribe;
|
|
@@ -3755,7 +3826,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3755
3826
|
lineHeight: span?.lineHeight ?? 1.2,
|
|
3756
3827
|
centerY: session.layer?.transform.y ?? 0.42,
|
|
3757
3828
|
measured,
|
|
3758
|
-
lines: session.value.split("\n").length
|
|
3829
|
+
lines: session.value.split("\n").length,
|
|
3830
|
+
...canvas ? { canvas } : {}
|
|
3759
3831
|
}),
|
|
3760
3832
|
[
|
|
3761
3833
|
container,
|
|
@@ -3764,20 +3836,41 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3764
3836
|
span?.lineHeight,
|
|
3765
3837
|
session.layer?.transform.y,
|
|
3766
3838
|
session.value,
|
|
3767
|
-
measured
|
|
3839
|
+
measured,
|
|
3840
|
+
canvas
|
|
3768
3841
|
]
|
|
3769
3842
|
);
|
|
3770
3843
|
const fonts = useMemo11(() => fontChoices(store.registry.fonts), [store.registry.fonts]);
|
|
3771
3844
|
useEffect8(() => {
|
|
3772
|
-
const handle = setTimeout(() =>
|
|
3845
|
+
const handle = setTimeout(() => {
|
|
3846
|
+
input.current?.focus();
|
|
3847
|
+
if (session.kind === "existing" && session.value.length > 0) {
|
|
3848
|
+
const end = session.value.length;
|
|
3849
|
+
setSelectionRange({ start: end, end });
|
|
3850
|
+
input.current?.setNativeProps({ selection: { start: end, end } });
|
|
3851
|
+
}
|
|
3852
|
+
}, 50);
|
|
3773
3853
|
return () => clearTimeout(handle);
|
|
3774
3854
|
}, []);
|
|
3775
3855
|
const onSelectionChange = useCallback6(
|
|
3776
3856
|
(event) => {
|
|
3777
|
-
|
|
3857
|
+
const next = event.nativeEvent.selection;
|
|
3858
|
+
setSelectionRange(
|
|
3859
|
+
(previous) => previous && previous.start === next.start && previous.end === next.end ? previous : next
|
|
3860
|
+
);
|
|
3778
3861
|
},
|
|
3779
3862
|
[]
|
|
3780
3863
|
);
|
|
3864
|
+
const caretAt = selectionRange?.end ?? session.value.length;
|
|
3865
|
+
const caretSource = session.value.slice(0, Math.max(0, caretAt));
|
|
3866
|
+
const onCaretLayout = useCallback6((event) => {
|
|
3867
|
+
const lines = event.nativeEvent.lines;
|
|
3868
|
+
const last = lines[lines.length - 1];
|
|
3869
|
+
const next = last ? { x: last.x, y: last.y, width: last.width, height: last.height } : null;
|
|
3870
|
+
setCaretLine((previous) => sameCaretLine(previous, next) ? previous : next);
|
|
3871
|
+
}, []);
|
|
3872
|
+
const caret = composerCaretBox(layout, caretLine);
|
|
3873
|
+
const letterSpacing = span?.letterSpacing && span.fontSize > 0 ? span.letterSpacing * layout.fontSize / span.fontSize : void 0;
|
|
3781
3874
|
const keepFocus = useCallback6(
|
|
3782
3875
|
(apply) => {
|
|
3783
3876
|
apply();
|
|
@@ -3838,6 +3931,7 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3838
3931
|
});
|
|
3839
3932
|
return () => subscription.remove();
|
|
3840
3933
|
}, [onCancel, insets.keyboardVisible]);
|
|
3934
|
+
const tapStart = useRef13(null);
|
|
3841
3935
|
const controlRow = {
|
|
3842
3936
|
flexDirection: "row",
|
|
3843
3937
|
alignItems: "center",
|
|
@@ -3862,6 +3956,54 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3862
3956
|
style: { position: "absolute", left: 0, right: 0, top: 0, bottom: 0, backgroundColor: "#00000055" }
|
|
3863
3957
|
}
|
|
3864
3958
|
),
|
|
3959
|
+
/* @__PURE__ */ jsx27(
|
|
3960
|
+
Pressable15,
|
|
3961
|
+
{
|
|
3962
|
+
accessible: true,
|
|
3963
|
+
collapsable: false,
|
|
3964
|
+
accessibilityRole: "button",
|
|
3965
|
+
accessibilityLabel: "Finish editing text",
|
|
3966
|
+
onAccessibilityTap: onDone,
|
|
3967
|
+
onPressIn: (event) => {
|
|
3968
|
+
tapStart.current = {
|
|
3969
|
+
x: event.nativeEvent.pageX,
|
|
3970
|
+
y: event.nativeEvent.pageY
|
|
3971
|
+
};
|
|
3972
|
+
},
|
|
3973
|
+
onPress: (event) => {
|
|
3974
|
+
const start = tapStart.current;
|
|
3975
|
+
const end = { x: event.nativeEvent.pageX, y: event.nativeEvent.pageY };
|
|
3976
|
+
if (!pressMoved(start, end)) onDone();
|
|
3977
|
+
},
|
|
3978
|
+
style: { position: "absolute", left: 0, right: 0, top: 0, bottom: 0 }
|
|
3979
|
+
}
|
|
3980
|
+
),
|
|
3981
|
+
/* @__PURE__ */ jsx27(
|
|
3982
|
+
Text18,
|
|
3983
|
+
{
|
|
3984
|
+
accessible: false,
|
|
3985
|
+
importantForAccessibility: "no-hide-descendants",
|
|
3986
|
+
pointerEvents: "none",
|
|
3987
|
+
onTextLayout: onCaretLayout,
|
|
3988
|
+
style: {
|
|
3989
|
+
position: "absolute",
|
|
3990
|
+
left: layout.left,
|
|
3991
|
+
width: layout.width,
|
|
3992
|
+
top: layout.top,
|
|
3993
|
+
height: layout.height,
|
|
3994
|
+
opacity: 0,
|
|
3995
|
+
fontSize: layout.fontSize,
|
|
3996
|
+
lineHeight: layout.lineHeight,
|
|
3997
|
+
fontFamily: span?.fontFamily,
|
|
3998
|
+
fontWeight: span?.fontWeight ? `${span.fontWeight}` : void 0,
|
|
3999
|
+
letterSpacing,
|
|
4000
|
+
textAlign: data?.align ?? "center",
|
|
4001
|
+
padding: 0
|
|
4002
|
+
},
|
|
4003
|
+
children: `${caretSource}\u200B`
|
|
4004
|
+
},
|
|
4005
|
+
`${caretSource}:${caretAt}`
|
|
4006
|
+
),
|
|
3865
4007
|
/* @__PURE__ */ jsx27(
|
|
3866
4008
|
TextInput,
|
|
3867
4009
|
{
|
|
@@ -3869,6 +4011,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3869
4011
|
value: session.value,
|
|
3870
4012
|
onChangeText: session.setValue,
|
|
3871
4013
|
onSelectionChange,
|
|
4014
|
+
onFocus: () => setFocused(true),
|
|
4015
|
+
onBlur: () => setFocused(false),
|
|
3872
4016
|
multiline: true,
|
|
3873
4017
|
submitBehavior: "newline",
|
|
3874
4018
|
autoCorrect: false,
|
|
@@ -3888,6 +4032,7 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3888
4032
|
// rgba rather than the 'transparent' keyword because the keyword is
|
|
3889
4033
|
// not reliably honoured for TextInput colour on Android.
|
|
3890
4034
|
color: "rgba(0,0,0,0)",
|
|
4035
|
+
opacity: composerInputOpacity(Platform.OS),
|
|
3891
4036
|
fontSize: layout.fontSize,
|
|
3892
4037
|
lineHeight: layout.lineHeight,
|
|
3893
4038
|
textAlign: data?.align ?? "center",
|
|
@@ -3898,6 +4043,22 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3898
4043
|
cursorColor: theme.colors.accent
|
|
3899
4044
|
}
|
|
3900
4045
|
),
|
|
4046
|
+
focused && caret ? /* @__PURE__ */ jsx27(
|
|
4047
|
+
View18,
|
|
4048
|
+
{
|
|
4049
|
+
pointerEvents: "none",
|
|
4050
|
+
accessible: false,
|
|
4051
|
+
style: {
|
|
4052
|
+
position: "absolute",
|
|
4053
|
+
left: caret.left,
|
|
4054
|
+
top: caret.top,
|
|
4055
|
+
width: caret.width,
|
|
4056
|
+
height: caret.height,
|
|
4057
|
+
borderRadius: caret.width / 2,
|
|
4058
|
+
backgroundColor: theme.colors.accent
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
) : null,
|
|
3901
4062
|
/* @__PURE__ */ jsxs18(
|
|
3902
4063
|
View18,
|
|
3903
4064
|
{
|
|
@@ -4097,7 +4258,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
4097
4258
|
}
|
|
4098
4259
|
function TextComposerHost({
|
|
4099
4260
|
Composer = TextComposer,
|
|
4100
|
-
safeArea
|
|
4261
|
+
safeArea,
|
|
4262
|
+
canvas
|
|
4101
4263
|
}) {
|
|
4102
4264
|
const { setActiveTool } = useEditor22();
|
|
4103
4265
|
const session = useTextSession();
|
|
@@ -4145,7 +4307,8 @@ function TextComposerHost({
|
|
|
4145
4307
|
session,
|
|
4146
4308
|
insets,
|
|
4147
4309
|
onDone: () => finish(true),
|
|
4148
|
-
onCancel: () => finish(false)
|
|
4310
|
+
onCancel: () => finish(false),
|
|
4311
|
+
...canvas ? { canvas } : {}
|
|
4149
4312
|
}
|
|
4150
4313
|
);
|
|
4151
4314
|
}
|
|
@@ -4741,6 +4904,7 @@ export {
|
|
|
4741
4904
|
defaultTheme,
|
|
4742
4905
|
fitImageToLayoutCell,
|
|
4743
4906
|
layoutCellBounds,
|
|
4907
|
+
layoutCellBox,
|
|
4744
4908
|
layoutCells,
|
|
4745
4909
|
mergeSlots,
|
|
4746
4910
|
mergeTheme,
|