@pictor-sdk/ui 0.2.1 → 0.3.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 +38 -2
- package/dist/index.d.ts +38 -2
- package/dist/index.js +108 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +103 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/composer/TextComposer.tsx +64 -22
- package/src/composer/keyboardLayout.ts +83 -0
- package/src/gestures/LayerGestureSurface.tsx +54 -9
- package/src/index.ts +1 -1
- package/src/panels/layoutMath.ts +23 -0
package/dist/index.d.mts
CHANGED
|
@@ -71,6 +71,15 @@ declare const ThemeContext: React.Context<EditorTheme>;
|
|
|
71
71
|
declare function useTheme(): EditorTheme;
|
|
72
72
|
|
|
73
73
|
interface ComposerInsets {
|
|
74
|
+
/**
|
|
75
|
+
* Distance from the top of the screen to place the Cancel/Done bar.
|
|
76
|
+
*
|
|
77
|
+
* The composer cannot work this out for itself: reading the safe area needs
|
|
78
|
+
* `react-native-safe-area-context`, and this package takes no dependency the
|
|
79
|
+
* host has not already chosen. So the host supplies it, and a host that does
|
|
80
|
+
* not gets 0 — which is what every release before this one drew.
|
|
81
|
+
*/
|
|
82
|
+
top: number;
|
|
74
83
|
/** Distance from the bottom of the screen to place the control bar. */
|
|
75
84
|
bottom: number;
|
|
76
85
|
/** True while the keyboard occupies screen space. */
|
|
@@ -180,9 +189,20 @@ declare function TextComposer({ session, insets, onDone, onCancel }: TextCompose
|
|
|
180
189
|
*
|
|
181
190
|
* @experimental
|
|
182
191
|
*/
|
|
183
|
-
declare function TextComposerHost({ Composer, }: {
|
|
192
|
+
declare function TextComposerHost({ Composer, safeArea, }: {
|
|
184
193
|
/** Replace the default composer without replacing the canvas or editor. */
|
|
185
194
|
Composer?: React__default.ComponentType<TextComposerProps>;
|
|
195
|
+
/**
|
|
196
|
+
* The app's safe-area insets, in dp.
|
|
197
|
+
*
|
|
198
|
+
* Supplied by the host because reading them needs a dependency this package
|
|
199
|
+
* does not take. Omit it and the composer draws exactly where it always did:
|
|
200
|
+
* its bar at the very top, which on a device with a notch means under one.
|
|
201
|
+
*/
|
|
202
|
+
safeArea?: {
|
|
203
|
+
top?: number;
|
|
204
|
+
bottom?: number;
|
|
205
|
+
};
|
|
186
206
|
}): React__default.JSX.Element;
|
|
187
207
|
|
|
188
208
|
/** What the top bar slot receives: the transaction actions and undo state. */
|
|
@@ -807,6 +827,22 @@ interface LayoutImageFit {
|
|
|
807
827
|
declare function fitImageToLayoutCell(cell: Rect, documentSize: Size, intrinsic: Size, options?: LayoutCropOptions): LayoutImageFit;
|
|
808
828
|
/** The transform patch that moves a layer into a cell, leaving its other fields alone. */
|
|
809
829
|
declare function cellToTransformPatch(cell: LayoutCell): Partial<Transform>;
|
|
830
|
+
/**
|
|
831
|
+
* The box a photo draws into when it fills one cell, in document pixels.
|
|
832
|
+
*
|
|
833
|
+
* The cell fixes the box: `fitImageToLayoutCell` picks a crop whose aspect
|
|
834
|
+
* matches the cell, so zooming and panning inside it move the source window
|
|
835
|
+
* and leave the box alone. That is what makes a live crop expressible as a
|
|
836
|
+
* source-space override at all, and it is why a cell's bounds cannot drift
|
|
837
|
+
* while a gesture is running.
|
|
838
|
+
*
|
|
839
|
+
* Paired with `imageSourceTransform` from `@pictor-sdk/skia`, this is
|
|
840
|
+
* everything a host needs to draw an uncommitted crop from a worklet, using
|
|
841
|
+
* the renderer's own arithmetic rather than a second copy of it.
|
|
842
|
+
*
|
|
843
|
+
* @experimental
|
|
844
|
+
*/
|
|
845
|
+
declare function layoutCellBox(cell: Rect, documentSize: Size): Size;
|
|
810
846
|
|
|
811
847
|
/** The active brush: kind, colour and width. Width is a fraction of document width. */
|
|
812
848
|
interface BrushConfig {
|
|
@@ -862,4 +898,4 @@ declare function UiStateProvider({ children }: {
|
|
|
862
898
|
/** Read and write one key of the ephemeral UI state. Throws outside an `<Editor>`. */
|
|
863
899
|
declare function useUiState<T>(key: string, fallback: T): [T, (value: T) => void];
|
|
864
900
|
|
|
865
|
-
export { type AddPhotoController, type AddPhotoControllerOptions, AddPhotoPanel, AdjustPanel, type BrushConfig, CanvasStage, type CanvasStageProps, CropGestureSurface, type CropGestureSurfaceProps, CropGrid, CropPanel, DEFAULT_BRUSH, type DrawController, DrawPanel, DrawingSurface, Editor, EditorGestures, type EditorGesturesProps, type EditorProps, type EditorSlots, type EditorTheme, FilterStrip, type GestureController, type GestureControllerOptions, type GestureMetrics, type GestureSnapEvent, type GestureSurfaceKind, type HostCallbacks, HostCallbacksProvider, InteractionGuides, type InteractionGuidesProps, InteractionProvider, InvalidImageResultError, LayerGestureSurface, type LayerGestureSurfaceProps, LayersPanel, type LayoutCell, type LayoutCropOptions, type LayoutImageFit, LayoutPanel, type LayoutPreset, MediaPanel, type PartialTheme, STRAIGHTEN_LIMIT, Slider, type SliderProps, type StickerAsset, StickerPanel, StoryMediaGestureSurface, type StoryMediaGestureSurfaceProps, StraightenRuler, type StraightenRulerProps, TextComposer, TextComposerHost, type TextComposerProps, TextPanel, type TextSessionController, ThemeContext, ToolPanelHost, ToolRail, TopBar, type TopBarProps, UiStateProvider, type UiStateStore, type UseTextSessionOptions, canvasSizeForAspect, cellToTransformPatch, centeredCropForAspect, createAddPhotoController, createDrawController, createGestureController, createUiStateStore, defaultTheme, fitImageToLayoutCell, layoutCellBounds, layoutCells, mergeSlots, mergeTheme, radiansToStraighten, resolveGestureSurface, resolvePanel, straightenToRadians, useBaseImageLayer, useBaseMediaLayer, useHostCallbacks, useInteractionState, useTargetImageLayer, useTextSession, useTheme, useUiState };
|
|
901
|
+
export { type AddPhotoController, type AddPhotoControllerOptions, AddPhotoPanel, AdjustPanel, type BrushConfig, CanvasStage, type CanvasStageProps, CropGestureSurface, type CropGestureSurfaceProps, CropGrid, CropPanel, DEFAULT_BRUSH, type DrawController, DrawPanel, DrawingSurface, Editor, EditorGestures, type EditorGesturesProps, type EditorProps, type EditorSlots, type EditorTheme, FilterStrip, type GestureController, type GestureControllerOptions, type GestureMetrics, type GestureSnapEvent, type GestureSurfaceKind, type HostCallbacks, HostCallbacksProvider, InteractionGuides, type InteractionGuidesProps, InteractionProvider, InvalidImageResultError, LayerGestureSurface, type LayerGestureSurfaceProps, LayersPanel, type LayoutCell, type LayoutCropOptions, type LayoutImageFit, LayoutPanel, type LayoutPreset, MediaPanel, type PartialTheme, STRAIGHTEN_LIMIT, Slider, type SliderProps, type StickerAsset, StickerPanel, StoryMediaGestureSurface, type StoryMediaGestureSurfaceProps, StraightenRuler, type StraightenRulerProps, TextComposer, TextComposerHost, type TextComposerProps, TextPanel, type TextSessionController, ThemeContext, ToolPanelHost, ToolRail, TopBar, type TopBarProps, UiStateProvider, type UiStateStore, type UseTextSessionOptions, canvasSizeForAspect, cellToTransformPatch, centeredCropForAspect, createAddPhotoController, createDrawController, createGestureController, createUiStateStore, defaultTheme, fitImageToLayoutCell, layoutCellBounds, layoutCellBox, layoutCells, mergeSlots, mergeTheme, radiansToStraighten, resolveGestureSurface, resolvePanel, straightenToRadians, useBaseImageLayer, useBaseMediaLayer, useHostCallbacks, useInteractionState, useTargetImageLayer, useTextSession, useTheme, useUiState };
|
package/dist/index.d.ts
CHANGED
|
@@ -71,6 +71,15 @@ declare const ThemeContext: React.Context<EditorTheme>;
|
|
|
71
71
|
declare function useTheme(): EditorTheme;
|
|
72
72
|
|
|
73
73
|
interface ComposerInsets {
|
|
74
|
+
/**
|
|
75
|
+
* Distance from the top of the screen to place the Cancel/Done bar.
|
|
76
|
+
*
|
|
77
|
+
* The composer cannot work this out for itself: reading the safe area needs
|
|
78
|
+
* `react-native-safe-area-context`, and this package takes no dependency the
|
|
79
|
+
* host has not already chosen. So the host supplies it, and a host that does
|
|
80
|
+
* not gets 0 — which is what every release before this one drew.
|
|
81
|
+
*/
|
|
82
|
+
top: number;
|
|
74
83
|
/** Distance from the bottom of the screen to place the control bar. */
|
|
75
84
|
bottom: number;
|
|
76
85
|
/** True while the keyboard occupies screen space. */
|
|
@@ -180,9 +189,20 @@ declare function TextComposer({ session, insets, onDone, onCancel }: TextCompose
|
|
|
180
189
|
*
|
|
181
190
|
* @experimental
|
|
182
191
|
*/
|
|
183
|
-
declare function TextComposerHost({ Composer, }: {
|
|
192
|
+
declare function TextComposerHost({ Composer, safeArea, }: {
|
|
184
193
|
/** Replace the default composer without replacing the canvas or editor. */
|
|
185
194
|
Composer?: React__default.ComponentType<TextComposerProps>;
|
|
195
|
+
/**
|
|
196
|
+
* The app's safe-area insets, in dp.
|
|
197
|
+
*
|
|
198
|
+
* Supplied by the host because reading them needs a dependency this package
|
|
199
|
+
* does not take. Omit it and the composer draws exactly where it always did:
|
|
200
|
+
* its bar at the very top, which on a device with a notch means under one.
|
|
201
|
+
*/
|
|
202
|
+
safeArea?: {
|
|
203
|
+
top?: number;
|
|
204
|
+
bottom?: number;
|
|
205
|
+
};
|
|
186
206
|
}): React__default.JSX.Element;
|
|
187
207
|
|
|
188
208
|
/** What the top bar slot receives: the transaction actions and undo state. */
|
|
@@ -807,6 +827,22 @@ interface LayoutImageFit {
|
|
|
807
827
|
declare function fitImageToLayoutCell(cell: Rect, documentSize: Size, intrinsic: Size, options?: LayoutCropOptions): LayoutImageFit;
|
|
808
828
|
/** The transform patch that moves a layer into a cell, leaving its other fields alone. */
|
|
809
829
|
declare function cellToTransformPatch(cell: LayoutCell): Partial<Transform>;
|
|
830
|
+
/**
|
|
831
|
+
* The box a photo draws into when it fills one cell, in document pixels.
|
|
832
|
+
*
|
|
833
|
+
* The cell fixes the box: `fitImageToLayoutCell` picks a crop whose aspect
|
|
834
|
+
* matches the cell, so zooming and panning inside it move the source window
|
|
835
|
+
* and leave the box alone. That is what makes a live crop expressible as a
|
|
836
|
+
* source-space override at all, and it is why a cell's bounds cannot drift
|
|
837
|
+
* while a gesture is running.
|
|
838
|
+
*
|
|
839
|
+
* Paired with `imageSourceTransform` from `@pictor-sdk/skia`, this is
|
|
840
|
+
* everything a host needs to draw an uncommitted crop from a worklet, using
|
|
841
|
+
* the renderer's own arithmetic rather than a second copy of it.
|
|
842
|
+
*
|
|
843
|
+
* @experimental
|
|
844
|
+
*/
|
|
845
|
+
declare function layoutCellBox(cell: Rect, documentSize: Size): Size;
|
|
810
846
|
|
|
811
847
|
/** The active brush: kind, colour and width. Width is a fraction of document width. */
|
|
812
848
|
interface BrushConfig {
|
|
@@ -862,4 +898,4 @@ declare function UiStateProvider({ children }: {
|
|
|
862
898
|
/** Read and write one key of the ephemeral UI state. Throws outside an `<Editor>`. */
|
|
863
899
|
declare function useUiState<T>(key: string, fallback: T): [T, (value: T) => void];
|
|
864
900
|
|
|
865
|
-
export { type AddPhotoController, type AddPhotoControllerOptions, AddPhotoPanel, AdjustPanel, type BrushConfig, CanvasStage, type CanvasStageProps, CropGestureSurface, type CropGestureSurfaceProps, CropGrid, CropPanel, DEFAULT_BRUSH, type DrawController, DrawPanel, DrawingSurface, Editor, EditorGestures, type EditorGesturesProps, type EditorProps, type EditorSlots, type EditorTheme, FilterStrip, type GestureController, type GestureControllerOptions, type GestureMetrics, type GestureSnapEvent, type GestureSurfaceKind, type HostCallbacks, HostCallbacksProvider, InteractionGuides, type InteractionGuidesProps, InteractionProvider, InvalidImageResultError, LayerGestureSurface, type LayerGestureSurfaceProps, LayersPanel, type LayoutCell, type LayoutCropOptions, type LayoutImageFit, LayoutPanel, type LayoutPreset, MediaPanel, type PartialTheme, STRAIGHTEN_LIMIT, Slider, type SliderProps, type StickerAsset, StickerPanel, StoryMediaGestureSurface, type StoryMediaGestureSurfaceProps, StraightenRuler, type StraightenRulerProps, TextComposer, TextComposerHost, type TextComposerProps, TextPanel, type TextSessionController, ThemeContext, ToolPanelHost, ToolRail, TopBar, type TopBarProps, UiStateProvider, type UiStateStore, type UseTextSessionOptions, canvasSizeForAspect, cellToTransformPatch, centeredCropForAspect, createAddPhotoController, createDrawController, createGestureController, createUiStateStore, defaultTheme, fitImageToLayoutCell, layoutCellBounds, layoutCells, mergeSlots, mergeTheme, radiansToStraighten, resolveGestureSurface, resolvePanel, straightenToRadians, useBaseImageLayer, useBaseMediaLayer, useHostCallbacks, useInteractionState, useTargetImageLayer, useTextSession, useTheme, useUiState };
|
|
901
|
+
export { type AddPhotoController, type AddPhotoControllerOptions, AddPhotoPanel, AdjustPanel, type BrushConfig, CanvasStage, type CanvasStageProps, CropGestureSurface, type CropGestureSurfaceProps, CropGrid, CropPanel, DEFAULT_BRUSH, type DrawController, DrawPanel, DrawingSurface, Editor, EditorGestures, type EditorGesturesProps, type EditorProps, type EditorSlots, type EditorTheme, FilterStrip, type GestureController, type GestureControllerOptions, type GestureMetrics, type GestureSnapEvent, type GestureSurfaceKind, type HostCallbacks, HostCallbacksProvider, InteractionGuides, type InteractionGuidesProps, InteractionProvider, InvalidImageResultError, LayerGestureSurface, type LayerGestureSurfaceProps, LayersPanel, type LayoutCell, type LayoutCropOptions, type LayoutImageFit, LayoutPanel, type LayoutPreset, MediaPanel, type PartialTheme, STRAIGHTEN_LIMIT, Slider, type SliderProps, type StickerAsset, StickerPanel, StoryMediaGestureSurface, type StoryMediaGestureSurfaceProps, StraightenRuler, type StraightenRulerProps, TextComposer, TextComposerHost, type TextComposerProps, TextPanel, type TextSessionController, ThemeContext, ToolPanelHost, ToolRail, TopBar, type TopBarProps, UiStateProvider, type UiStateStore, type UseTextSessionOptions, canvasSizeForAspect, cellToTransformPatch, centeredCropForAspect, createAddPhotoController, createDrawController, createGestureController, createUiStateStore, defaultTheme, fitImageToLayoutCell, layoutCellBounds, layoutCellBox, layoutCells, mergeSlots, mergeTheme, radiansToStraighten, resolveGestureSurface, resolvePanel, straightenToRadians, useBaseImageLayer, useBaseMediaLayer, useHostCallbacks, useInteractionState, useTargetImageLayer, useTextSession, useTheme, useUiState };
|
package/dist/index.js
CHANGED
|
@@ -75,6 +75,7 @@ __export(index_exports, {
|
|
|
75
75
|
defaultTheme: () => defaultTheme,
|
|
76
76
|
fitImageToLayoutCell: () => fitImageToLayoutCell,
|
|
77
77
|
layoutCellBounds: () => layoutCellBounds,
|
|
78
|
+
layoutCellBox: () => layoutCellBox,
|
|
78
79
|
layoutCells: () => layoutCells,
|
|
79
80
|
mergeSlots: () => mergeSlots,
|
|
80
81
|
mergeTheme: () => mergeTheme,
|
|
@@ -95,7 +96,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
95
96
|
|
|
96
97
|
// src/Editor.tsx
|
|
97
98
|
var import_core30 = require("@pictor-sdk/core");
|
|
98
|
-
var
|
|
99
|
+
var import_skia11 = require("@pictor-sdk/skia");
|
|
99
100
|
var import_react22 = __toESM(require("react"));
|
|
100
101
|
var import_react_native27 = require("react-native");
|
|
101
102
|
var import_react_native_gesture_handler8 = require("react-native-gesture-handler");
|
|
@@ -868,6 +869,7 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
868
869
|
const focalY = (0, import_react_native_reanimated4.useSharedValue)(0.5);
|
|
869
870
|
const pointers = (0, import_react_native_reanimated4.useSharedValue)(0);
|
|
870
871
|
const active = (0, import_react_native_reanimated4.useSharedValue)(false);
|
|
872
|
+
const livePointers = (0, import_react_native_reanimated4.useSharedValue)(0);
|
|
871
873
|
const snapCX = (0, import_react_native_reanimated4.useSharedValue)(false);
|
|
872
874
|
const snapCY = (0, import_react_native_reanimated4.useSharedValue)(false);
|
|
873
875
|
const snapR = (0, import_react_native_reanimated4.useSharedValue)(false);
|
|
@@ -1042,7 +1044,23 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
1042
1044
|
deleted
|
|
1043
1045
|
);
|
|
1044
1046
|
};
|
|
1045
|
-
const
|
|
1047
|
+
const releaseIfEmpty = () => {
|
|
1048
|
+
"worklet";
|
|
1049
|
+
if (livePointers.value > 0) return;
|
|
1050
|
+
endIfIdle();
|
|
1051
|
+
};
|
|
1052
|
+
const pan = import_react_native_gesture_handler4.Gesture.Pan().minPointers(1).maxPointers(2).onTouchesDown((e) => {
|
|
1053
|
+
"worklet";
|
|
1054
|
+
livePointers.value = e.allTouches.length;
|
|
1055
|
+
}).onTouchesUp((e) => {
|
|
1056
|
+
"worklet";
|
|
1057
|
+
livePointers.value = Math.max(0, e.allTouches.length - e.changedTouches.length);
|
|
1058
|
+
releaseIfEmpty();
|
|
1059
|
+
}).onTouchesCancelled(() => {
|
|
1060
|
+
"worklet";
|
|
1061
|
+
livePointers.value = 0;
|
|
1062
|
+
releaseIfEmpty();
|
|
1063
|
+
}).onBegin((e) => {
|
|
1046
1064
|
"worklet";
|
|
1047
1065
|
if (active.value) return;
|
|
1048
1066
|
rawPanX.value = 0;
|
|
@@ -1089,7 +1107,7 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
1089
1107
|
recompute(false);
|
|
1090
1108
|
}).onFinalize(() => {
|
|
1091
1109
|
"worklet";
|
|
1092
|
-
|
|
1110
|
+
releaseIfEmpty();
|
|
1093
1111
|
});
|
|
1094
1112
|
const pinch = import_react_native_gesture_handler4.Gesture.Pinch().onUpdate((e) => {
|
|
1095
1113
|
"worklet";
|
|
@@ -1098,6 +1116,9 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
1098
1116
|
focalY.value = e.focalY / metricsHeight;
|
|
1099
1117
|
syncPointers(e.numberOfPointers);
|
|
1100
1118
|
recompute(false);
|
|
1119
|
+
}).onFinalize(() => {
|
|
1120
|
+
"worklet";
|
|
1121
|
+
releaseIfEmpty();
|
|
1101
1122
|
});
|
|
1102
1123
|
const rotate = import_react_native_gesture_handler4.Gesture.Rotation().enabled(config.rotationEnabled).onUpdate((e) => {
|
|
1103
1124
|
"worklet";
|
|
@@ -1106,6 +1127,9 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
1106
1127
|
focalY.value = e.anchorY / metricsHeight;
|
|
1107
1128
|
syncPointers(e.numberOfPointers);
|
|
1108
1129
|
recompute(false);
|
|
1130
|
+
}).onFinalize(() => {
|
|
1131
|
+
"worklet";
|
|
1132
|
+
releaseIfEmpty();
|
|
1109
1133
|
});
|
|
1110
1134
|
const tap = import_react_native_gesture_handler4.Gesture.Tap().onEnd((e) => {
|
|
1111
1135
|
"worklet";
|
|
@@ -2901,6 +2925,13 @@ function fitImageToLayoutCell(cell, documentSize, intrinsic, options = {}) {
|
|
|
2901
2925
|
function cellToTransformPatch(cell) {
|
|
2902
2926
|
return { x: cell.x, y: cell.y, scale: cell.scale, rotation: 0 };
|
|
2903
2927
|
}
|
|
2928
|
+
function layoutCellBox(cell, documentSize) {
|
|
2929
|
+
"worklet";
|
|
2930
|
+
return {
|
|
2931
|
+
width: cell.width * documentSize.width,
|
|
2932
|
+
height: cell.height * documentSize.height
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2904
2935
|
|
|
2905
2936
|
// src/panels/LayoutPanel.tsx
|
|
2906
2937
|
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
@@ -3496,17 +3527,21 @@ function ToolPanelHost() {
|
|
|
3496
3527
|
var import_core27 = require("@pictor-sdk/core");
|
|
3497
3528
|
var import_react21 = require("react");
|
|
3498
3529
|
var import_react_native24 = require("react-native");
|
|
3530
|
+
var import_skia10 = require("@pictor-sdk/skia");
|
|
3499
3531
|
|
|
3500
3532
|
// src/composer/keyboardLayout.ts
|
|
3501
3533
|
var HIDDEN_KEYBOARD = { height: 0 };
|
|
3502
3534
|
var MAX_DURATION = 400;
|
|
3503
3535
|
var FALLBACK_DURATION = 180;
|
|
3504
|
-
function composerInsets(frame, safeAreaBottom = 0) {
|
|
3536
|
+
function composerInsets(frame, safeAreaBottom = 0, safeAreaTop = 0) {
|
|
3505
3537
|
const height = Number.isFinite(frame.height) ? Math.max(0, frame.height) : 0;
|
|
3506
3538
|
const visible = height > 0;
|
|
3507
3539
|
const raw = frame.duration;
|
|
3508
3540
|
const duration = raw === void 0 || !Number.isFinite(raw) || raw <= 0 ? FALLBACK_DURATION : Math.min(MAX_DURATION, raw);
|
|
3509
3541
|
return {
|
|
3542
|
+
// The keyboard never covers the top, so this one is the safe area and
|
|
3543
|
+
// nothing else.
|
|
3544
|
+
top: Number.isFinite(safeAreaTop) ? Math.max(0, safeAreaTop) : 0,
|
|
3510
3545
|
bottom: visible ? height : Math.max(0, safeAreaBottom),
|
|
3511
3546
|
keyboardVisible: visible,
|
|
3512
3547
|
duration
|
|
@@ -3526,6 +3561,31 @@ function fitCanvasRect(container, document) {
|
|
|
3526
3561
|
top: (container.height - height) / 2
|
|
3527
3562
|
};
|
|
3528
3563
|
}
|
|
3564
|
+
function composerInputBox(request) {
|
|
3565
|
+
const canvas = fitCanvasRect(request.container, request.document);
|
|
3566
|
+
const fontSize = Math.max(1, request.fontSize * canvas.width);
|
|
3567
|
+
const lineHeight = Math.max(1, fontSize * request.lineHeight);
|
|
3568
|
+
const scale = request.document.width > 0 ? canvas.width / request.document.width : 0;
|
|
3569
|
+
const measuredHeight = request.measured && request.measured.height > 0 ? request.measured.height * scale : 0;
|
|
3570
|
+
const estimated = lineHeight * Math.max(1, request.lines);
|
|
3571
|
+
const height = Math.min(
|
|
3572
|
+
Math.max(lineHeight, measuredHeight > 0 ? measuredHeight : estimated),
|
|
3573
|
+
Math.max(lineHeight, canvas.height)
|
|
3574
|
+
);
|
|
3575
|
+
const centre = canvas.top + request.centerY * canvas.height;
|
|
3576
|
+
const top = Math.min(
|
|
3577
|
+
Math.max(0, centre - height / 2),
|
|
3578
|
+
Math.max(0, request.container.height - height)
|
|
3579
|
+
);
|
|
3580
|
+
return {
|
|
3581
|
+
left: canvas.left,
|
|
3582
|
+
width: Math.max(1, canvas.width),
|
|
3583
|
+
top,
|
|
3584
|
+
height: Math.max(1, height),
|
|
3585
|
+
fontSize,
|
|
3586
|
+
lineHeight
|
|
3587
|
+
};
|
|
3588
|
+
}
|
|
3529
3589
|
|
|
3530
3590
|
// src/composer/useTextSession.ts
|
|
3531
3591
|
var import_core26 = require("@pictor-sdk/core");
|
|
@@ -3662,26 +3722,45 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3662
3722
|
const store = (0, import_core27.useEditorStore)();
|
|
3663
3723
|
const { document } = (0, import_core27.useEditor)();
|
|
3664
3724
|
const [container, setContainer] = (0, import_react21.useState)({ width: 0, height: 0 });
|
|
3725
|
+
const renderer = (0, import_skia10.useRenderer)();
|
|
3665
3726
|
const input = (0, import_react21.useRef)(null);
|
|
3666
3727
|
const [selectionRange, setSelectionRange] = (0, import_react21.useState)(
|
|
3667
3728
|
void 0
|
|
3668
3729
|
);
|
|
3669
3730
|
const data = session.data;
|
|
3670
3731
|
const span = session.span;
|
|
3671
|
-
const
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3732
|
+
const layerId = session.layer?.id;
|
|
3733
|
+
const [measured, setMeasured] = (0, import_react21.useState)(null);
|
|
3734
|
+
(0, import_react21.useEffect)(() => {
|
|
3735
|
+
if (!layerId) {
|
|
3736
|
+
setMeasured(null);
|
|
3737
|
+
return;
|
|
3738
|
+
}
|
|
3739
|
+
const refresh = () => setMeasured(renderer.textMeasures.get(layerId) ?? null);
|
|
3740
|
+
const unsubscribe = renderer.textMeasures.subscribe(refresh);
|
|
3741
|
+
refresh();
|
|
3742
|
+
return unsubscribe;
|
|
3743
|
+
}, [renderer, layerId]);
|
|
3744
|
+
const layout = (0, import_react21.useMemo)(
|
|
3745
|
+
() => composerInputBox({
|
|
3746
|
+
container,
|
|
3747
|
+
document: document.size,
|
|
3748
|
+
fontSize: span?.fontSize ?? 0.08,
|
|
3749
|
+
lineHeight: span?.lineHeight ?? 1.2,
|
|
3750
|
+
centerY: session.layer?.transform.y ?? 0.42,
|
|
3751
|
+
measured,
|
|
3752
|
+
lines: session.value.split("\n").length
|
|
3753
|
+
}),
|
|
3754
|
+
[
|
|
3755
|
+
container,
|
|
3756
|
+
document.size,
|
|
3757
|
+
span?.fontSize,
|
|
3758
|
+
span?.lineHeight,
|
|
3759
|
+
session.layer?.transform.y,
|
|
3760
|
+
session.value,
|
|
3761
|
+
measured
|
|
3762
|
+
]
|
|
3763
|
+
);
|
|
3685
3764
|
const fonts = (0, import_react21.useMemo)(() => (0, import_core27.fontChoices)(store.registry.fonts), [store.registry.fonts]);
|
|
3686
3765
|
(0, import_react21.useEffect)(() => {
|
|
3687
3766
|
const handle = setTimeout(() => input.current?.focus(), 50);
|
|
@@ -3820,7 +3899,7 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3820
3899
|
position: "absolute",
|
|
3821
3900
|
left: 0,
|
|
3822
3901
|
right: 0,
|
|
3823
|
-
top:
|
|
3902
|
+
top: insets.top,
|
|
3824
3903
|
height: 56,
|
|
3825
3904
|
flexDirection: "row",
|
|
3826
3905
|
alignItems: "center",
|
|
@@ -4011,7 +4090,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
4011
4090
|
);
|
|
4012
4091
|
}
|
|
4013
4092
|
function TextComposerHost({
|
|
4014
|
-
Composer = TextComposer
|
|
4093
|
+
Composer = TextComposer,
|
|
4094
|
+
safeArea
|
|
4015
4095
|
}) {
|
|
4016
4096
|
const { setActiveTool } = (0, import_core27.useEditor)();
|
|
4017
4097
|
const session = useTextSession();
|
|
@@ -4040,7 +4120,10 @@ function TextComposerHost({
|
|
|
4040
4120
|
hide.remove();
|
|
4041
4121
|
};
|
|
4042
4122
|
}, []);
|
|
4043
|
-
const insets = (0, import_react21.useMemo)(
|
|
4123
|
+
const insets = (0, import_react21.useMemo)(
|
|
4124
|
+
() => composerInsets(frame, safeArea?.bottom ?? 0, safeArea?.top ?? 0),
|
|
4125
|
+
[frame, safeArea?.bottom, safeArea?.top]
|
|
4126
|
+
);
|
|
4044
4127
|
const finish = (0, import_react21.useCallback)(
|
|
4045
4128
|
(commit) => {
|
|
4046
4129
|
if (commit) session.commit();
|
|
@@ -4165,7 +4248,7 @@ function CanvasStage({ children, onError }) {
|
|
|
4165
4248
|
const { previews } = useInteractionState();
|
|
4166
4249
|
const surface = resolveGestureSurface(activeTool);
|
|
4167
4250
|
return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
|
|
4168
|
-
|
|
4251
|
+
import_skia11.EditorCanvas,
|
|
4169
4252
|
{
|
|
4170
4253
|
style: { flex: 1 },
|
|
4171
4254
|
onError,
|
|
@@ -4285,7 +4368,7 @@ function Editor({
|
|
|
4285
4368
|
|
|
4286
4369
|
// src/gestures/EditorGestures.tsx
|
|
4287
4370
|
var import_core32 = require("@pictor-sdk/core");
|
|
4288
|
-
var
|
|
4371
|
+
var import_skia12 = require("@pictor-sdk/skia");
|
|
4289
4372
|
var import_react23 = require("react");
|
|
4290
4373
|
var import_react_native28 = require("react-native");
|
|
4291
4374
|
var import_react_native_gesture_handler9 = require("react-native-gesture-handler");
|
|
@@ -4393,7 +4476,7 @@ function createGestureController(store, getMetrics, options = {}) {
|
|
|
4393
4476
|
var import_jsx_runtime31 = require("react/jsx-runtime");
|
|
4394
4477
|
function EditorGestures({ onSnap }) {
|
|
4395
4478
|
const store = (0, import_core32.useEditorStore)();
|
|
4396
|
-
const metrics = (0,
|
|
4479
|
+
const metrics = (0, import_skia12.useCanvasMetrics)();
|
|
4397
4480
|
const metricsRef = (0, import_react23.useRef)(metrics);
|
|
4398
4481
|
metricsRef.current = metrics;
|
|
4399
4482
|
const onSnapRef = (0, import_react23.useRef)(onSnap);
|
|
@@ -4645,6 +4728,7 @@ function TextPanel() {
|
|
|
4645
4728
|
defaultTheme,
|
|
4646
4729
|
fitImageToLayoutCell,
|
|
4647
4730
|
layoutCellBounds,
|
|
4731
|
+
layoutCellBox,
|
|
4648
4732
|
layoutCells,
|
|
4649
4733
|
mergeSlots,
|
|
4650
4734
|
mergeTheme,
|