@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.d.mts
CHANGED
|
@@ -87,6 +87,14 @@ interface ComposerInsets {
|
|
|
87
87
|
/** Animation duration to match the platform's own, clamped to something sane. */
|
|
88
88
|
duration: number;
|
|
89
89
|
}
|
|
90
|
+
interface CanvasRect {
|
|
91
|
+
/** Size of the fitted canvas inside the container. */
|
|
92
|
+
width: number;
|
|
93
|
+
height: number;
|
|
94
|
+
/** Offset of the canvas inside the container, which centres it. */
|
|
95
|
+
left: number;
|
|
96
|
+
top: number;
|
|
97
|
+
}
|
|
90
98
|
|
|
91
99
|
/**
|
|
92
100
|
* The state and commands behind a text-editing session.
|
|
@@ -167,6 +175,11 @@ interface TextComposerProps {
|
|
|
167
175
|
onDone(): void;
|
|
168
176
|
/** Abandon, restoring the document. */
|
|
169
177
|
onCancel(): void;
|
|
178
|
+
/**
|
|
179
|
+
* The canvas rectangle in this composer's coordinates, when the host draws a
|
|
180
|
+
* cover canvas. Omitted, the composer assumes a centred contain fit.
|
|
181
|
+
*/
|
|
182
|
+
canvas?: CanvasRect;
|
|
170
183
|
}
|
|
171
184
|
/**
|
|
172
185
|
* The full-screen Story text composer.
|
|
@@ -176,20 +189,22 @@ interface TextComposerProps {
|
|
|
176
189
|
* `renderDocument` — so what is on screen while composing is what exports.
|
|
177
190
|
*
|
|
178
191
|
* The `TextInput` itself is invisible and stretched over the canvas: it owns
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
192
|
+
* focus and the IME, while the *visible* text is the real Skia layer
|
|
193
|
+
* underneath. On Android the platform paints composing glyphs black regardless
|
|
194
|
+
* of the input's colour, so the input is fully hidden and a caret is drawn at
|
|
195
|
+
* the measured selection instead. That is what keeps preview and export
|
|
196
|
+
* identical without giving up text editing.
|
|
182
197
|
*
|
|
183
198
|
* @experimental
|
|
184
199
|
*/
|
|
185
|
-
declare function TextComposer({ session, insets, onDone, onCancel }: TextComposerProps): React__default.JSX.Element;
|
|
200
|
+
declare function TextComposer({ session, insets, onDone, onCancel, canvas }: TextComposerProps): React__default.JSX.Element;
|
|
186
201
|
/**
|
|
187
202
|
* Mounts a text session and the composer for it, and keeps the keyboard inset
|
|
188
203
|
* up to date. This is what `<Editor>` renders while the text tool is active.
|
|
189
204
|
*
|
|
190
205
|
* @experimental
|
|
191
206
|
*/
|
|
192
|
-
declare function TextComposerHost({ Composer, safeArea, }: {
|
|
207
|
+
declare function TextComposerHost({ Composer, safeArea, canvas, }: {
|
|
193
208
|
/** Replace the default composer without replacing the canvas or editor. */
|
|
194
209
|
Composer?: React__default.ComponentType<TextComposerProps>;
|
|
195
210
|
/**
|
|
@@ -203,6 +218,8 @@ declare function TextComposerHost({ Composer, safeArea, }: {
|
|
|
203
218
|
top?: number;
|
|
204
219
|
bottom?: number;
|
|
205
220
|
};
|
|
221
|
+
/** The canvas rectangle, for a host that draws a cover canvas. */
|
|
222
|
+
canvas?: CanvasRect;
|
|
206
223
|
}): React__default.JSX.Element;
|
|
207
224
|
|
|
208
225
|
/** What the top bar slot receives: the transaction actions and undo state. */
|
|
@@ -605,6 +622,12 @@ declare function EditorGestures({ onSnap }: EditorGesturesProps): React__default
|
|
|
605
622
|
interface LayerGestureSurfaceProps {
|
|
606
623
|
/** Fired once per snap engagement — hook up haptics here if you want them. */
|
|
607
624
|
onSnap?: (event: GestureSnapEvent) => void;
|
|
625
|
+
/**
|
|
626
|
+
* Fired when a tap lands on a layer that was already selected when the
|
|
627
|
+
* gesture began. Hosts use this for a second-tap action such as opening the
|
|
628
|
+
* text composer; the SDK itself stays unaware of what a layer means.
|
|
629
|
+
*/
|
|
630
|
+
onSelectedLayerTap?: (layerId: string) => void;
|
|
608
631
|
}
|
|
609
632
|
/**
|
|
610
633
|
* Direct manipulation of overlay layers: grab the top-most layer under the
|
|
@@ -622,7 +645,7 @@ interface LayerGestureSurfaceProps {
|
|
|
622
645
|
* - **One history entry.** The document is written exactly once, when the
|
|
623
646
|
* gesture settles, and not at all if nothing actually moved.
|
|
624
647
|
*/
|
|
625
|
-
declare function LayerGestureSurface({ onSnap }: LayerGestureSurfaceProps): React__default.JSX.Element;
|
|
648
|
+
declare function LayerGestureSurface({ onSnap, onSelectedLayerTap }: LayerGestureSurfaceProps): React__default.JSX.Element;
|
|
626
649
|
|
|
627
650
|
/** The centre and rotation guides shown while a layer snaps. */
|
|
628
651
|
interface InteractionGuidesProps {
|
|
@@ -827,6 +850,22 @@ interface LayoutImageFit {
|
|
|
827
850
|
declare function fitImageToLayoutCell(cell: Rect, documentSize: Size, intrinsic: Size, options?: LayoutCropOptions): LayoutImageFit;
|
|
828
851
|
/** The transform patch that moves a layer into a cell, leaving its other fields alone. */
|
|
829
852
|
declare function cellToTransformPatch(cell: LayoutCell): Partial<Transform>;
|
|
853
|
+
/**
|
|
854
|
+
* The box a photo draws into when it fills one cell, in document pixels.
|
|
855
|
+
*
|
|
856
|
+
* The cell fixes the box: `fitImageToLayoutCell` picks a crop whose aspect
|
|
857
|
+
* matches the cell, so zooming and panning inside it move the source window
|
|
858
|
+
* and leave the box alone. That is what makes a live crop expressible as a
|
|
859
|
+
* source-space override at all, and it is why a cell's bounds cannot drift
|
|
860
|
+
* while a gesture is running.
|
|
861
|
+
*
|
|
862
|
+
* Paired with `imageSourceTransform` from `@pictor-sdk/skia`, this is
|
|
863
|
+
* everything a host needs to draw an uncommitted crop from a worklet, using
|
|
864
|
+
* the renderer's own arithmetic rather than a second copy of it.
|
|
865
|
+
*
|
|
866
|
+
* @experimental
|
|
867
|
+
*/
|
|
868
|
+
declare function layoutCellBox(cell: Rect, documentSize: Size): Size;
|
|
830
869
|
|
|
831
870
|
/** The active brush: kind, colour and width. Width is a fraction of document width. */
|
|
832
871
|
interface BrushConfig {
|
|
@@ -882,4 +921,4 @@ declare function UiStateProvider({ children }: {
|
|
|
882
921
|
/** Read and write one key of the ephemeral UI state. Throws outside an `<Editor>`. */
|
|
883
922
|
declare function useUiState<T>(key: string, fallback: T): [T, (value: T) => void];
|
|
884
923
|
|
|
885
|
-
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 };
|
|
924
|
+
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
|
@@ -87,6 +87,14 @@ interface ComposerInsets {
|
|
|
87
87
|
/** Animation duration to match the platform's own, clamped to something sane. */
|
|
88
88
|
duration: number;
|
|
89
89
|
}
|
|
90
|
+
interface CanvasRect {
|
|
91
|
+
/** Size of the fitted canvas inside the container. */
|
|
92
|
+
width: number;
|
|
93
|
+
height: number;
|
|
94
|
+
/** Offset of the canvas inside the container, which centres it. */
|
|
95
|
+
left: number;
|
|
96
|
+
top: number;
|
|
97
|
+
}
|
|
90
98
|
|
|
91
99
|
/**
|
|
92
100
|
* The state and commands behind a text-editing session.
|
|
@@ -167,6 +175,11 @@ interface TextComposerProps {
|
|
|
167
175
|
onDone(): void;
|
|
168
176
|
/** Abandon, restoring the document. */
|
|
169
177
|
onCancel(): void;
|
|
178
|
+
/**
|
|
179
|
+
* The canvas rectangle in this composer's coordinates, when the host draws a
|
|
180
|
+
* cover canvas. Omitted, the composer assumes a centred contain fit.
|
|
181
|
+
*/
|
|
182
|
+
canvas?: CanvasRect;
|
|
170
183
|
}
|
|
171
184
|
/**
|
|
172
185
|
* The full-screen Story text composer.
|
|
@@ -176,20 +189,22 @@ interface TextComposerProps {
|
|
|
176
189
|
* `renderDocument` — so what is on screen while composing is what exports.
|
|
177
190
|
*
|
|
178
191
|
* The `TextInput` itself is invisible and stretched over the canvas: it owns
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
192
|
+
* focus and the IME, while the *visible* text is the real Skia layer
|
|
193
|
+
* underneath. On Android the platform paints composing glyphs black regardless
|
|
194
|
+
* of the input's colour, so the input is fully hidden and a caret is drawn at
|
|
195
|
+
* the measured selection instead. That is what keeps preview and export
|
|
196
|
+
* identical without giving up text editing.
|
|
182
197
|
*
|
|
183
198
|
* @experimental
|
|
184
199
|
*/
|
|
185
|
-
declare function TextComposer({ session, insets, onDone, onCancel }: TextComposerProps): React__default.JSX.Element;
|
|
200
|
+
declare function TextComposer({ session, insets, onDone, onCancel, canvas }: TextComposerProps): React__default.JSX.Element;
|
|
186
201
|
/**
|
|
187
202
|
* Mounts a text session and the composer for it, and keeps the keyboard inset
|
|
188
203
|
* up to date. This is what `<Editor>` renders while the text tool is active.
|
|
189
204
|
*
|
|
190
205
|
* @experimental
|
|
191
206
|
*/
|
|
192
|
-
declare function TextComposerHost({ Composer, safeArea, }: {
|
|
207
|
+
declare function TextComposerHost({ Composer, safeArea, canvas, }: {
|
|
193
208
|
/** Replace the default composer without replacing the canvas or editor. */
|
|
194
209
|
Composer?: React__default.ComponentType<TextComposerProps>;
|
|
195
210
|
/**
|
|
@@ -203,6 +218,8 @@ declare function TextComposerHost({ Composer, safeArea, }: {
|
|
|
203
218
|
top?: number;
|
|
204
219
|
bottom?: number;
|
|
205
220
|
};
|
|
221
|
+
/** The canvas rectangle, for a host that draws a cover canvas. */
|
|
222
|
+
canvas?: CanvasRect;
|
|
206
223
|
}): React__default.JSX.Element;
|
|
207
224
|
|
|
208
225
|
/** What the top bar slot receives: the transaction actions and undo state. */
|
|
@@ -605,6 +622,12 @@ declare function EditorGestures({ onSnap }: EditorGesturesProps): React__default
|
|
|
605
622
|
interface LayerGestureSurfaceProps {
|
|
606
623
|
/** Fired once per snap engagement — hook up haptics here if you want them. */
|
|
607
624
|
onSnap?: (event: GestureSnapEvent) => void;
|
|
625
|
+
/**
|
|
626
|
+
* Fired when a tap lands on a layer that was already selected when the
|
|
627
|
+
* gesture began. Hosts use this for a second-tap action such as opening the
|
|
628
|
+
* text composer; the SDK itself stays unaware of what a layer means.
|
|
629
|
+
*/
|
|
630
|
+
onSelectedLayerTap?: (layerId: string) => void;
|
|
608
631
|
}
|
|
609
632
|
/**
|
|
610
633
|
* Direct manipulation of overlay layers: grab the top-most layer under the
|
|
@@ -622,7 +645,7 @@ interface LayerGestureSurfaceProps {
|
|
|
622
645
|
* - **One history entry.** The document is written exactly once, when the
|
|
623
646
|
* gesture settles, and not at all if nothing actually moved.
|
|
624
647
|
*/
|
|
625
|
-
declare function LayerGestureSurface({ onSnap }: LayerGestureSurfaceProps): React__default.JSX.Element;
|
|
648
|
+
declare function LayerGestureSurface({ onSnap, onSelectedLayerTap }: LayerGestureSurfaceProps): React__default.JSX.Element;
|
|
626
649
|
|
|
627
650
|
/** The centre and rotation guides shown while a layer snaps. */
|
|
628
651
|
interface InteractionGuidesProps {
|
|
@@ -827,6 +850,22 @@ interface LayoutImageFit {
|
|
|
827
850
|
declare function fitImageToLayoutCell(cell: Rect, documentSize: Size, intrinsic: Size, options?: LayoutCropOptions): LayoutImageFit;
|
|
828
851
|
/** The transform patch that moves a layer into a cell, leaving its other fields alone. */
|
|
829
852
|
declare function cellToTransformPatch(cell: LayoutCell): Partial<Transform>;
|
|
853
|
+
/**
|
|
854
|
+
* The box a photo draws into when it fills one cell, in document pixels.
|
|
855
|
+
*
|
|
856
|
+
* The cell fixes the box: `fitImageToLayoutCell` picks a crop whose aspect
|
|
857
|
+
* matches the cell, so zooming and panning inside it move the source window
|
|
858
|
+
* and leave the box alone. That is what makes a live crop expressible as a
|
|
859
|
+
* source-space override at all, and it is why a cell's bounds cannot drift
|
|
860
|
+
* while a gesture is running.
|
|
861
|
+
*
|
|
862
|
+
* Paired with `imageSourceTransform` from `@pictor-sdk/skia`, this is
|
|
863
|
+
* everything a host needs to draw an uncommitted crop from a worklet, using
|
|
864
|
+
* the renderer's own arithmetic rather than a second copy of it.
|
|
865
|
+
*
|
|
866
|
+
* @experimental
|
|
867
|
+
*/
|
|
868
|
+
declare function layoutCellBox(cell: Rect, documentSize: Size): Size;
|
|
830
869
|
|
|
831
870
|
/** The active brush: kind, colour and width. Width is a fraction of document width. */
|
|
832
871
|
interface BrushConfig {
|
|
@@ -882,4 +921,4 @@ declare function UiStateProvider({ children }: {
|
|
|
882
921
|
/** Read and write one key of the ephemeral UI state. Throws outside an `<Editor>`. */
|
|
883
922
|
declare function useUiState<T>(key: string, fallback: T): [T, (value: T) => void];
|
|
884
923
|
|
|
885
|
-
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 };
|
|
924
|
+
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,
|
|
@@ -792,10 +793,16 @@ function hitTestLayerCandidatesUI(x, y, candidates, documentWidth, documentHeigh
|
|
|
792
793
|
return null;
|
|
793
794
|
}
|
|
794
795
|
|
|
796
|
+
// src/gestures/selectionTap.ts
|
|
797
|
+
function selectedLayerTapId(hit, wasSelectedAtStart) {
|
|
798
|
+
if (!hit || hit.locked || !wasSelectedAtStart) return null;
|
|
799
|
+
return hit.id;
|
|
800
|
+
}
|
|
801
|
+
|
|
795
802
|
// src/gestures/LayerGestureSurface.tsx
|
|
796
803
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
797
|
-
function LayerGestureSurface({ onSnap }) {
|
|
798
|
-
const { document, dispatch, profile } = (0, import_core8.useEditor)();
|
|
804
|
+
function LayerGestureSurface({ onSnap, onSelectedLayerTap }) {
|
|
805
|
+
const { document, dispatch, profile, selection } = (0, import_core8.useEditor)();
|
|
799
806
|
const store = (0, import_core8.useEditorStore)();
|
|
800
807
|
const metrics = (0, import_skia4.useCanvasMetrics)();
|
|
801
808
|
const { setPreview, setLayerDragActive, setOverDeleteTarget, setSnap } = useInteractionState();
|
|
@@ -886,10 +893,11 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
886
893
|
document.size.height
|
|
887
894
|
);
|
|
888
895
|
}, [document.size.width, document.size.height]);
|
|
889
|
-
const latest = (0, import_react9.useRef)({ document, metrics, store, dispatch, onSnap, profile });
|
|
890
|
-
latest.current = { document, metrics, store, dispatch, onSnap, profile };
|
|
896
|
+
const latest = (0, import_react9.useRef)({ document, metrics, store, dispatch, onSnap, profile, selection, onSelectedLayerTap });
|
|
897
|
+
latest.current = { document, metrics, store, dispatch, onSnap, profile, selection, onSelectedLayerTap };
|
|
891
898
|
const grabbedId = (0, import_react9.useRef)(null);
|
|
892
899
|
const grabbedFrom = (0, import_react9.useRef)(null);
|
|
900
|
+
const tapWasSelected = (0, import_react9.useRef)(false);
|
|
893
901
|
const snapState = (0, import_react9.useRef)(import_core8.NO_SNAP);
|
|
894
902
|
const onGrabLayer = (0, import_react9.useCallback)((id) => {
|
|
895
903
|
const { document: doc, store: s } = latest.current;
|
|
@@ -902,9 +910,23 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
902
910
|
setPreview({ layerId: hit.id, transform: previewTransform });
|
|
903
911
|
setLayerDragActive(true);
|
|
904
912
|
}, [setPreview, setLayerDragActive, previewTransform]);
|
|
913
|
+
const snapshotTapAt = (0, import_react9.useCallback)((xDp, yDp) => {
|
|
914
|
+
const { document: doc, metrics: m, store: s, selection: selected } = latest.current;
|
|
915
|
+
if (!m) return;
|
|
916
|
+
const layerInteraction = latest.current.profile.interaction?.layers;
|
|
917
|
+
const hit = (0, import_core8.hitTestDocument)(
|
|
918
|
+
doc,
|
|
919
|
+
{ x: xDp / m.width, y: yDp / m.height },
|
|
920
|
+
s.registry,
|
|
921
|
+
(layer, index) => (0, import_core8.isDirectManipulationLayer)(layer, index, layerInteraction)
|
|
922
|
+
);
|
|
923
|
+
tapWasSelected.current = Boolean(hit && selected.includes(hit.id));
|
|
924
|
+
}, []);
|
|
905
925
|
const onSelectAt = (0, import_react9.useCallback)((xDp, yDp) => {
|
|
906
926
|
const { document: doc, metrics: m, store: s } = latest.current;
|
|
907
927
|
if (!m) return;
|
|
928
|
+
const wasSelected = tapWasSelected.current;
|
|
929
|
+
tapWasSelected.current = false;
|
|
908
930
|
const layerInteraction = latest.current.profile.interaction?.layers;
|
|
909
931
|
const hit = (0, import_core8.hitTestDocument)(
|
|
910
932
|
doc,
|
|
@@ -913,6 +935,8 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
913
935
|
(layer, index) => (0, import_core8.isDirectManipulationLayer)(layer, index, layerInteraction)
|
|
914
936
|
);
|
|
915
937
|
s.select(hit && !hit.locked ? [hit.id] : []);
|
|
938
|
+
const editId = selectedLayerTapId(hit ?? null, wasSelected);
|
|
939
|
+
if (editId) latest.current.onSelectedLayerTap?.(editId);
|
|
916
940
|
}, []);
|
|
917
941
|
const onSnapChanged = (0, import_react9.useCallback)(
|
|
918
942
|
(centerX, centerY, rotation) => {
|
|
@@ -1051,6 +1075,8 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
1051
1075
|
const pan = import_react_native_gesture_handler4.Gesture.Pan().minPointers(1).maxPointers(2).onTouchesDown((e) => {
|
|
1052
1076
|
"worklet";
|
|
1053
1077
|
livePointers.value = e.allTouches.length;
|
|
1078
|
+
const touch = e.changedTouches[0] ?? e.allTouches[0];
|
|
1079
|
+
if (touch) (0, import_react_native_worklets4.runOnJS)(snapshotTapAt)(touch.x, touch.y);
|
|
1054
1080
|
}).onTouchesUp((e) => {
|
|
1055
1081
|
"worklet";
|
|
1056
1082
|
livePointers.value = Math.max(0, e.allTouches.length - e.changedTouches.length);
|
|
@@ -1141,6 +1167,7 @@ function LayerGestureSurface({ onSnap }) {
|
|
|
1141
1167
|
deleteTarget,
|
|
1142
1168
|
aspect,
|
|
1143
1169
|
onGrabLayer,
|
|
1170
|
+
snapshotTapAt,
|
|
1144
1171
|
onSelectAt,
|
|
1145
1172
|
onSnapChanged,
|
|
1146
1173
|
onHoverChanged,
|
|
@@ -1818,8 +1845,9 @@ function Slider({
|
|
|
1818
1845
|
const valueRef = (0, import_react12.useRef)(value);
|
|
1819
1846
|
valueRef.current = value;
|
|
1820
1847
|
const onLayout = (e) => {
|
|
1821
|
-
|
|
1822
|
-
widthRef.current =
|
|
1848
|
+
const next = e.nativeEvent.layout.width;
|
|
1849
|
+
widthRef.current = next;
|
|
1850
|
+
setWidth((previous) => previous === next ? previous : next);
|
|
1823
1851
|
};
|
|
1824
1852
|
const handlers = (0, import_react12.useMemo)(() => {
|
|
1825
1853
|
const begin = () => {
|
|
@@ -2924,6 +2952,13 @@ function fitImageToLayoutCell(cell, documentSize, intrinsic, options = {}) {
|
|
|
2924
2952
|
function cellToTransformPatch(cell) {
|
|
2925
2953
|
return { x: cell.x, y: cell.y, scale: cell.scale, rotation: 0 };
|
|
2926
2954
|
}
|
|
2955
|
+
function layoutCellBox(cell, documentSize) {
|
|
2956
|
+
"worklet";
|
|
2957
|
+
return {
|
|
2958
|
+
width: cell.width * documentSize.width,
|
|
2959
|
+
height: cell.height * documentSize.height
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2927
2962
|
|
|
2928
2963
|
// src/panels/LayoutPanel.tsx
|
|
2929
2964
|
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
@@ -3554,7 +3589,8 @@ function fitCanvasRect(container, document) {
|
|
|
3554
3589
|
};
|
|
3555
3590
|
}
|
|
3556
3591
|
function composerInputBox(request) {
|
|
3557
|
-
const
|
|
3592
|
+
const provided = request.canvas;
|
|
3593
|
+
const canvas = provided && provided.width > 0 && provided.height > 0 ? provided : fitCanvasRect(request.container, request.document);
|
|
3558
3594
|
const fontSize = Math.max(1, request.fontSize * canvas.width);
|
|
3559
3595
|
const lineHeight = Math.max(1, fontSize * request.lineHeight);
|
|
3560
3596
|
const scale = request.document.width > 0 ? canvas.width / request.document.width : 0;
|
|
@@ -3578,6 +3614,35 @@ function composerInputBox(request) {
|
|
|
3578
3614
|
lineHeight
|
|
3579
3615
|
};
|
|
3580
3616
|
}
|
|
3617
|
+
function sameCaretLine(previous, next) {
|
|
3618
|
+
return previous === next || previous !== null && next !== null && previous.x === next.x && previous.y === next.y && previous.width === next.width && previous.height === next.height;
|
|
3619
|
+
}
|
|
3620
|
+
var CARET_WIDTH = 2;
|
|
3621
|
+
function composerInputOpacity(platform) {
|
|
3622
|
+
return platform === "android" ? 0 : 1;
|
|
3623
|
+
}
|
|
3624
|
+
function composerCaretBox(box, line) {
|
|
3625
|
+
if (!line || !Number.isFinite(line.x) || !Number.isFinite(line.y) || !Number.isFinite(line.width) || !Number.isFinite(line.height)) {
|
|
3626
|
+
return null;
|
|
3627
|
+
}
|
|
3628
|
+
const height = Math.max(1, line.height || box.lineHeight);
|
|
3629
|
+
return {
|
|
3630
|
+
left: box.left + line.x + line.width - CARET_WIDTH / 2,
|
|
3631
|
+
top: box.top + line.y,
|
|
3632
|
+
width: CARET_WIDTH,
|
|
3633
|
+
height
|
|
3634
|
+
};
|
|
3635
|
+
}
|
|
3636
|
+
|
|
3637
|
+
// src/composer/tapGesture.ts
|
|
3638
|
+
var TAP_SLOP = 8;
|
|
3639
|
+
function pressMoved(start, end, slop = TAP_SLOP) {
|
|
3640
|
+
if (!start || !end) return false;
|
|
3641
|
+
if (!Number.isFinite(start.x) || !Number.isFinite(start.y) || !Number.isFinite(end.x) || !Number.isFinite(end.y)) {
|
|
3642
|
+
return false;
|
|
3643
|
+
}
|
|
3644
|
+
return Math.hypot(end.x - start.x, end.y - start.y) > Math.max(0, slop);
|
|
3645
|
+
}
|
|
3581
3646
|
|
|
3582
3647
|
// src/composer/useTextSession.ts
|
|
3583
3648
|
var import_core26 = require("@pictor-sdk/core");
|
|
@@ -3709,7 +3774,7 @@ var SHADOWS = [
|
|
|
3709
3774
|
{ color: "#000000", blur: 0.012, dx: 2e-3, dy: 4e-3 },
|
|
3710
3775
|
{ color: "#000000", blur: 2e-3, dx: 6e-3, dy: 8e-3 }
|
|
3711
3776
|
];
|
|
3712
|
-
function TextComposer({ session, insets, onDone, onCancel }) {
|
|
3777
|
+
function TextComposer({ session, insets, onDone, onCancel, canvas }) {
|
|
3713
3778
|
const theme = useTheme();
|
|
3714
3779
|
const store = (0, import_core27.useEditorStore)();
|
|
3715
3780
|
const { document } = (0, import_core27.useEditor)();
|
|
@@ -3719,6 +3784,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3719
3784
|
const [selectionRange, setSelectionRange] = (0, import_react21.useState)(
|
|
3720
3785
|
void 0
|
|
3721
3786
|
);
|
|
3787
|
+
const [focused, setFocused] = (0, import_react21.useState)(false);
|
|
3788
|
+
const [caretLine, setCaretLine] = (0, import_react21.useState)(null);
|
|
3722
3789
|
const data = session.data;
|
|
3723
3790
|
const span = session.span;
|
|
3724
3791
|
const layerId = session.layer?.id;
|
|
@@ -3728,7 +3795,12 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3728
3795
|
setMeasured(null);
|
|
3729
3796
|
return;
|
|
3730
3797
|
}
|
|
3731
|
-
const refresh = () =>
|
|
3798
|
+
const refresh = () => {
|
|
3799
|
+
const next = renderer.textMeasures.get(layerId) ?? null;
|
|
3800
|
+
setMeasured(
|
|
3801
|
+
(previous) => previous && next && previous.width === next.width && previous.height === next.height ? previous : next
|
|
3802
|
+
);
|
|
3803
|
+
};
|
|
3732
3804
|
const unsubscribe = renderer.textMeasures.subscribe(refresh);
|
|
3733
3805
|
refresh();
|
|
3734
3806
|
return unsubscribe;
|
|
@@ -3741,7 +3813,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3741
3813
|
lineHeight: span?.lineHeight ?? 1.2,
|
|
3742
3814
|
centerY: session.layer?.transform.y ?? 0.42,
|
|
3743
3815
|
measured,
|
|
3744
|
-
lines: session.value.split("\n").length
|
|
3816
|
+
lines: session.value.split("\n").length,
|
|
3817
|
+
...canvas ? { canvas } : {}
|
|
3745
3818
|
}),
|
|
3746
3819
|
[
|
|
3747
3820
|
container,
|
|
@@ -3750,20 +3823,41 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3750
3823
|
span?.lineHeight,
|
|
3751
3824
|
session.layer?.transform.y,
|
|
3752
3825
|
session.value,
|
|
3753
|
-
measured
|
|
3826
|
+
measured,
|
|
3827
|
+
canvas
|
|
3754
3828
|
]
|
|
3755
3829
|
);
|
|
3756
3830
|
const fonts = (0, import_react21.useMemo)(() => (0, import_core27.fontChoices)(store.registry.fonts), [store.registry.fonts]);
|
|
3757
3831
|
(0, import_react21.useEffect)(() => {
|
|
3758
|
-
const handle = setTimeout(() =>
|
|
3832
|
+
const handle = setTimeout(() => {
|
|
3833
|
+
input.current?.focus();
|
|
3834
|
+
if (session.kind === "existing" && session.value.length > 0) {
|
|
3835
|
+
const end = session.value.length;
|
|
3836
|
+
setSelectionRange({ start: end, end });
|
|
3837
|
+
input.current?.setNativeProps({ selection: { start: end, end } });
|
|
3838
|
+
}
|
|
3839
|
+
}, 50);
|
|
3759
3840
|
return () => clearTimeout(handle);
|
|
3760
3841
|
}, []);
|
|
3761
3842
|
const onSelectionChange = (0, import_react21.useCallback)(
|
|
3762
3843
|
(event) => {
|
|
3763
|
-
|
|
3844
|
+
const next = event.nativeEvent.selection;
|
|
3845
|
+
setSelectionRange(
|
|
3846
|
+
(previous) => previous && previous.start === next.start && previous.end === next.end ? previous : next
|
|
3847
|
+
);
|
|
3764
3848
|
},
|
|
3765
3849
|
[]
|
|
3766
3850
|
);
|
|
3851
|
+
const caretAt = selectionRange?.end ?? session.value.length;
|
|
3852
|
+
const caretSource = session.value.slice(0, Math.max(0, caretAt));
|
|
3853
|
+
const onCaretLayout = (0, import_react21.useCallback)((event) => {
|
|
3854
|
+
const lines = event.nativeEvent.lines;
|
|
3855
|
+
const last = lines[lines.length - 1];
|
|
3856
|
+
const next = last ? { x: last.x, y: last.y, width: last.width, height: last.height } : null;
|
|
3857
|
+
setCaretLine((previous) => sameCaretLine(previous, next) ? previous : next);
|
|
3858
|
+
}, []);
|
|
3859
|
+
const caret = composerCaretBox(layout, caretLine);
|
|
3860
|
+
const letterSpacing = span?.letterSpacing && span.fontSize > 0 ? span.letterSpacing * layout.fontSize / span.fontSize : void 0;
|
|
3767
3861
|
const keepFocus = (0, import_react21.useCallback)(
|
|
3768
3862
|
(apply) => {
|
|
3769
3863
|
apply();
|
|
@@ -3824,6 +3918,7 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3824
3918
|
});
|
|
3825
3919
|
return () => subscription.remove();
|
|
3826
3920
|
}, [onCancel, insets.keyboardVisible]);
|
|
3921
|
+
const tapStart = (0, import_react21.useRef)(null);
|
|
3827
3922
|
const controlRow = {
|
|
3828
3923
|
flexDirection: "row",
|
|
3829
3924
|
alignItems: "center",
|
|
@@ -3848,6 +3943,54 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3848
3943
|
style: { position: "absolute", left: 0, right: 0, top: 0, bottom: 0, backgroundColor: "#00000055" }
|
|
3849
3944
|
}
|
|
3850
3945
|
),
|
|
3946
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
3947
|
+
import_react_native24.Pressable,
|
|
3948
|
+
{
|
|
3949
|
+
accessible: true,
|
|
3950
|
+
collapsable: false,
|
|
3951
|
+
accessibilityRole: "button",
|
|
3952
|
+
accessibilityLabel: "Finish editing text",
|
|
3953
|
+
onAccessibilityTap: onDone,
|
|
3954
|
+
onPressIn: (event) => {
|
|
3955
|
+
tapStart.current = {
|
|
3956
|
+
x: event.nativeEvent.pageX,
|
|
3957
|
+
y: event.nativeEvent.pageY
|
|
3958
|
+
};
|
|
3959
|
+
},
|
|
3960
|
+
onPress: (event) => {
|
|
3961
|
+
const start = tapStart.current;
|
|
3962
|
+
const end = { x: event.nativeEvent.pageX, y: event.nativeEvent.pageY };
|
|
3963
|
+
if (!pressMoved(start, end)) onDone();
|
|
3964
|
+
},
|
|
3965
|
+
style: { position: "absolute", left: 0, right: 0, top: 0, bottom: 0 }
|
|
3966
|
+
}
|
|
3967
|
+
),
|
|
3968
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
3969
|
+
import_react_native24.Text,
|
|
3970
|
+
{
|
|
3971
|
+
accessible: false,
|
|
3972
|
+
importantForAccessibility: "no-hide-descendants",
|
|
3973
|
+
pointerEvents: "none",
|
|
3974
|
+
onTextLayout: onCaretLayout,
|
|
3975
|
+
style: {
|
|
3976
|
+
position: "absolute",
|
|
3977
|
+
left: layout.left,
|
|
3978
|
+
width: layout.width,
|
|
3979
|
+
top: layout.top,
|
|
3980
|
+
height: layout.height,
|
|
3981
|
+
opacity: 0,
|
|
3982
|
+
fontSize: layout.fontSize,
|
|
3983
|
+
lineHeight: layout.lineHeight,
|
|
3984
|
+
fontFamily: span?.fontFamily,
|
|
3985
|
+
fontWeight: span?.fontWeight ? `${span.fontWeight}` : void 0,
|
|
3986
|
+
letterSpacing,
|
|
3987
|
+
textAlign: data?.align ?? "center",
|
|
3988
|
+
padding: 0
|
|
3989
|
+
},
|
|
3990
|
+
children: `${caretSource}\u200B`
|
|
3991
|
+
},
|
|
3992
|
+
`${caretSource}:${caretAt}`
|
|
3993
|
+
),
|
|
3851
3994
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
3852
3995
|
import_react_native24.TextInput,
|
|
3853
3996
|
{
|
|
@@ -3855,6 +3998,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3855
3998
|
value: session.value,
|
|
3856
3999
|
onChangeText: session.setValue,
|
|
3857
4000
|
onSelectionChange,
|
|
4001
|
+
onFocus: () => setFocused(true),
|
|
4002
|
+
onBlur: () => setFocused(false),
|
|
3858
4003
|
multiline: true,
|
|
3859
4004
|
submitBehavior: "newline",
|
|
3860
4005
|
autoCorrect: false,
|
|
@@ -3874,6 +4019,7 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3874
4019
|
// rgba rather than the 'transparent' keyword because the keyword is
|
|
3875
4020
|
// not reliably honoured for TextInput colour on Android.
|
|
3876
4021
|
color: "rgba(0,0,0,0)",
|
|
4022
|
+
opacity: composerInputOpacity(import_react_native24.Platform.OS),
|
|
3877
4023
|
fontSize: layout.fontSize,
|
|
3878
4024
|
lineHeight: layout.lineHeight,
|
|
3879
4025
|
textAlign: data?.align ?? "center",
|
|
@@ -3884,6 +4030,22 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
3884
4030
|
cursorColor: theme.colors.accent
|
|
3885
4031
|
}
|
|
3886
4032
|
),
|
|
4033
|
+
focused && caret ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
4034
|
+
import_react_native24.View,
|
|
4035
|
+
{
|
|
4036
|
+
pointerEvents: "none",
|
|
4037
|
+
accessible: false,
|
|
4038
|
+
style: {
|
|
4039
|
+
position: "absolute",
|
|
4040
|
+
left: caret.left,
|
|
4041
|
+
top: caret.top,
|
|
4042
|
+
width: caret.width,
|
|
4043
|
+
height: caret.height,
|
|
4044
|
+
borderRadius: caret.width / 2,
|
|
4045
|
+
backgroundColor: theme.colors.accent
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
) : null,
|
|
3887
4049
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
3888
4050
|
import_react_native24.View,
|
|
3889
4051
|
{
|
|
@@ -4083,7 +4245,8 @@ function TextComposer({ session, insets, onDone, onCancel }) {
|
|
|
4083
4245
|
}
|
|
4084
4246
|
function TextComposerHost({
|
|
4085
4247
|
Composer = TextComposer,
|
|
4086
|
-
safeArea
|
|
4248
|
+
safeArea,
|
|
4249
|
+
canvas
|
|
4087
4250
|
}) {
|
|
4088
4251
|
const { setActiveTool } = (0, import_core27.useEditor)();
|
|
4089
4252
|
const session = useTextSession();
|
|
@@ -4131,7 +4294,8 @@ function TextComposerHost({
|
|
|
4131
4294
|
session,
|
|
4132
4295
|
insets,
|
|
4133
4296
|
onDone: () => finish(true),
|
|
4134
|
-
onCancel: () => finish(false)
|
|
4297
|
+
onCancel: () => finish(false),
|
|
4298
|
+
...canvas ? { canvas } : {}
|
|
4135
4299
|
}
|
|
4136
4300
|
);
|
|
4137
4301
|
}
|
|
@@ -4720,6 +4884,7 @@ function TextPanel() {
|
|
|
4720
4884
|
defaultTheme,
|
|
4721
4885
|
fitImageToLayoutCell,
|
|
4722
4886
|
layoutCellBounds,
|
|
4887
|
+
layoutCellBox,
|
|
4723
4888
|
layoutCells,
|
|
4724
4889
|
mergeSlots,
|
|
4725
4890
|
mergeTheme,
|