@pictor-sdk/ui 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.d.mts +29 -6
- package/dist/index.d.ts +29 -6
- package/dist/index.js +171 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +171 -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/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 = () => {
|
|
@@ -3564,7 +3591,8 @@ function fitCanvasRect(container, document) {
|
|
|
3564
3591
|
};
|
|
3565
3592
|
}
|
|
3566
3593
|
function composerInputBox(request) {
|
|
3567
|
-
const
|
|
3594
|
+
const provided = request.canvas;
|
|
3595
|
+
const canvas = provided && provided.width > 0 && provided.height > 0 ? provided : fitCanvasRect(request.container, request.document);
|
|
3568
3596
|
const fontSize = Math.max(1, request.fontSize * canvas.width);
|
|
3569
3597
|
const lineHeight = Math.max(1, fontSize * request.lineHeight);
|
|
3570
3598
|
const scale = request.document.width > 0 ? canvas.width / request.document.width : 0;
|
|
@@ -3588,6 +3616,35 @@ function composerInputBox(request) {
|
|
|
3588
3616
|
lineHeight
|
|
3589
3617
|
};
|
|
3590
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
|
+
}
|
|
3591
3648
|
|
|
3592
3649
|
// src/composer/useTextSession.ts
|
|
3593
3650
|
import {
|
|
@@ -3730,7 +3787,7 @@ var SHADOWS = [
|
|
|
3730
3787
|
{ color: "#000000", blur: 0.012, dx: 2e-3, dy: 4e-3 },
|
|
3731
3788
|
{ color: "#000000", blur: 2e-3, dx: 6e-3, dy: 8e-3 }
|
|
3732
3789
|
];
|
|
3733
|
-
function TextComposer({ session, insets, onDone, onCancel }) {
|
|
3790
|
+
function TextComposer({ session, insets, onDone, onCancel, canvas }) {
|
|
3734
3791
|
const theme = useTheme();
|
|
3735
3792
|
const store = useEditorStore5();
|
|
3736
3793
|
const { document } = useEditor22();
|
|
@@ -3740,6 +3797,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3740
3797
|
const [selectionRange, setSelectionRange] = useState10(
|
|
3741
3798
|
void 0
|
|
3742
3799
|
);
|
|
3800
|
+
const [focused, setFocused] = useState10(false);
|
|
3801
|
+
const [caretLine, setCaretLine] = useState10(null);
|
|
3743
3802
|
const data = session.data;
|
|
3744
3803
|
const span = session.span;
|
|
3745
3804
|
const layerId = session.layer?.id;
|
|
@@ -3749,7 +3808,12 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3749
3808
|
setMeasured(null);
|
|
3750
3809
|
return;
|
|
3751
3810
|
}
|
|
3752
|
-
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
|
+
};
|
|
3753
3817
|
const unsubscribe = renderer.textMeasures.subscribe(refresh);
|
|
3754
3818
|
refresh();
|
|
3755
3819
|
return unsubscribe;
|
|
@@ -3762,7 +3826,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3762
3826
|
lineHeight: span?.lineHeight ?? 1.2,
|
|
3763
3827
|
centerY: session.layer?.transform.y ?? 0.42,
|
|
3764
3828
|
measured,
|
|
3765
|
-
lines: session.value.split("\n").length
|
|
3829
|
+
lines: session.value.split("\n").length,
|
|
3830
|
+
...canvas ? { canvas } : {}
|
|
3766
3831
|
}),
|
|
3767
3832
|
[
|
|
3768
3833
|
container,
|
|
@@ -3771,20 +3836,41 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3771
3836
|
span?.lineHeight,
|
|
3772
3837
|
session.layer?.transform.y,
|
|
3773
3838
|
session.value,
|
|
3774
|
-
measured
|
|
3839
|
+
measured,
|
|
3840
|
+
canvas
|
|
3775
3841
|
]
|
|
3776
3842
|
);
|
|
3777
3843
|
const fonts = useMemo11(() => fontChoices(store.registry.fonts), [store.registry.fonts]);
|
|
3778
3844
|
useEffect8(() => {
|
|
3779
|
-
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);
|
|
3780
3853
|
return () => clearTimeout(handle);
|
|
3781
3854
|
}, []);
|
|
3782
3855
|
const onSelectionChange = useCallback6(
|
|
3783
3856
|
(event) => {
|
|
3784
|
-
|
|
3857
|
+
const next = event.nativeEvent.selection;
|
|
3858
|
+
setSelectionRange(
|
|
3859
|
+
(previous) => previous && previous.start === next.start && previous.end === next.end ? previous : next
|
|
3860
|
+
);
|
|
3785
3861
|
},
|
|
3786
3862
|
[]
|
|
3787
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;
|
|
3788
3874
|
const keepFocus = useCallback6(
|
|
3789
3875
|
(apply) => {
|
|
3790
3876
|
apply();
|
|
@@ -3845,6 +3931,7 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3845
3931
|
});
|
|
3846
3932
|
return () => subscription.remove();
|
|
3847
3933
|
}, [onCancel, insets.keyboardVisible]);
|
|
3934
|
+
const tapStart = useRef13(null);
|
|
3848
3935
|
const controlRow = {
|
|
3849
3936
|
flexDirection: "row",
|
|
3850
3937
|
alignItems: "center",
|
|
@@ -3869,6 +3956,54 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3869
3956
|
style: { position: "absolute", left: 0, right: 0, top: 0, bottom: 0, backgroundColor: "#00000055" }
|
|
3870
3957
|
}
|
|
3871
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
|
+
),
|
|
3872
4007
|
/* @__PURE__ */ jsx27(
|
|
3873
4008
|
TextInput,
|
|
3874
4009
|
{
|
|
@@ -3876,6 +4011,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3876
4011
|
value: session.value,
|
|
3877
4012
|
onChangeText: session.setValue,
|
|
3878
4013
|
onSelectionChange,
|
|
4014
|
+
onFocus: () => setFocused(true),
|
|
4015
|
+
onBlur: () => setFocused(false),
|
|
3879
4016
|
multiline: true,
|
|
3880
4017
|
submitBehavior: "newline",
|
|
3881
4018
|
autoCorrect: false,
|
|
@@ -3895,6 +4032,7 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3895
4032
|
// rgba rather than the 'transparent' keyword because the keyword is
|
|
3896
4033
|
// not reliably honoured for TextInput colour on Android.
|
|
3897
4034
|
color: "rgba(0,0,0,0)",
|
|
4035
|
+
opacity: composerInputOpacity(Platform.OS),
|
|
3898
4036
|
fontSize: layout.fontSize,
|
|
3899
4037
|
lineHeight: layout.lineHeight,
|
|
3900
4038
|
textAlign: data?.align ?? "center",
|
|
@@ -3905,6 +4043,22 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3905
4043
|
cursorColor: theme.colors.accent
|
|
3906
4044
|
}
|
|
3907
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,
|
|
3908
4062
|
/* @__PURE__ */ jsxs18(
|
|
3909
4063
|
View18,
|
|
3910
4064
|
{
|
|
@@ -4104,7 +4258,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
4104
4258
|
}
|
|
4105
4259
|
function TextComposerHost({
|
|
4106
4260
|
Composer = TextComposer,
|
|
4107
|
-
safeArea
|
|
4261
|
+
safeArea,
|
|
4262
|
+
canvas
|
|
4108
4263
|
}) {
|
|
4109
4264
|
const { setActiveTool } = useEditor22();
|
|
4110
4265
|
const session = useTextSession();
|
|
@@ -4152,7 +4307,8 @@ function TextComposerHost({
|
|
|
4152
4307
|
session,
|
|
4153
4308
|
insets,
|
|
4154
4309
|
onDone: () => finish(true),
|
|
4155
|
-
onCancel: () => finish(false)
|
|
4310
|
+
onCancel: () => finish(false),
|
|
4311
|
+
...canvas ? { canvas } : {}
|
|
4156
4312
|
}
|
|
4157
4313
|
);
|
|
4158
4314
|
}
|