@dloizides/ui-media 0.2.0 → 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/CHANGELOG.md +6 -0
- package/dist/index.d.mts +39 -8
- package/dist/index.d.ts +39 -8
- package/dist/index.js +36 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +35 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- KEFI-PEOPLE-1 T10b "Organizer people editor: ui-media adoption gaps" (backwards compatible):
|
|
6
|
+
- `ImagePickerButton` / `useImageUpload` take a second generic `TResult` (default `void`): `upload` returns `Promise<TResult>` and the new `onUploaded(result)` receives it once the slot is `done`. A throwing `onUploaded` does not flip the slot to error.
|
|
7
|
+
- `PhotoFramingEditor` gains `anchor?: PhotoAnchor` (`center` default | `bottom`); `renderPreview` now also receives `anchor` and `objectPosition` (`center bottom` for kefi-landings `.amb-photo` cut-outs). New exports `PhotoAnchor`, `resolvePhotoAnchor`, `PhotoAnchorLayout`.
|
|
8
|
+
|
|
3
9
|
## 0.2.0
|
|
4
10
|
|
|
5
11
|
- KEFI-PEOPLE-1 T12 "Organizer people editor: shared photo framing": `PHOTO_FRAMING_BOUNDS`,
|
package/dist/index.d.mts
CHANGED
|
@@ -42,8 +42,11 @@ interface ImagePickerLabels {
|
|
|
42
42
|
/** Accessibility hint for the button: "Opens a file picker to choose an image". */
|
|
43
43
|
hint: string;
|
|
44
44
|
}
|
|
45
|
-
interface ImagePickerButtonProps<TFile = File> {
|
|
46
|
-
|
|
45
|
+
interface ImagePickerButtonProps<TFile = File, TResult = void> {
|
|
46
|
+
/** Consumer transport; whatever it resolves with (e.g. the stored URL) goes to `onUploaded`. */
|
|
47
|
+
upload: (file: TFile) => Promise<TResult>;
|
|
48
|
+
/** Called with `upload`'s result once the slot is `done`. */
|
|
49
|
+
onUploaded?: (result: TResult) => void;
|
|
47
50
|
labels: ImagePickerLabels;
|
|
48
51
|
testID: string;
|
|
49
52
|
/** Defaults to the browser file dialog. Native consumers pass their own picker. */
|
|
@@ -64,7 +67,7 @@ interface PickerView {
|
|
|
64
67
|
}
|
|
65
68
|
/** Pure status -> copy/variant mapping (unit-tested; the component only paints it). */
|
|
66
69
|
declare function resolvePickerView(status: ImagePickerStatus, labels: ImagePickerLabels): PickerView;
|
|
67
|
-
declare function ImagePickerButton<TFile = File>({ upload, labels, testID, pickFile, accept, hasImage, onError, disabled, }: ImagePickerButtonProps<TFile>): React.ReactElement;
|
|
70
|
+
declare function ImagePickerButton<TFile = File, TResult = void>({ upload, onUploaded, labels, testID, pickFile, accept, hasImage, onError, disabled, }: ImagePickerButtonProps<TFile, TResult>): React.ReactElement;
|
|
68
71
|
|
|
69
72
|
declare const DEFAULT_IMAGE_ACCEPT = "image/*";
|
|
70
73
|
/**
|
|
@@ -72,9 +75,11 @@ declare const DEFAULT_IMAGE_ACCEPT = "image/*";
|
|
|
72
75
|
* where there is no DOM (native) — a native consumer passes its own `pickFile`.
|
|
73
76
|
*/
|
|
74
77
|
declare function pickWebImage(accept?: string): Promise<File | null>;
|
|
75
|
-
interface UseImageUploadOptions<TFile> {
|
|
78
|
+
interface UseImageUploadOptions<TFile, TResult = void> {
|
|
76
79
|
/** Consumer-owned transport. Resolve = stored; reject = error state. No network code lives here. */
|
|
77
|
-
upload: (file: TFile) => Promise<
|
|
80
|
+
upload: (file: TFile) => Promise<TResult>;
|
|
81
|
+
/** Given what `upload` resolved with (e.g. the stored URL), once the slot is `done`. */
|
|
82
|
+
onUploaded?: (result: TResult) => void;
|
|
78
83
|
/** Picks one file; `null` means the user cancelled. */
|
|
79
84
|
pickFile: () => Promise<TFile | null>;
|
|
80
85
|
/** Start in `done` when the slot already holds an image. */
|
|
@@ -92,7 +97,27 @@ interface ImageUploadState {
|
|
|
92
97
|
* done or error back to uploading on the next pick. A cancelled pick leaves the
|
|
93
98
|
* state as it was, so cancelling "Replace" does not wipe a finished upload.
|
|
94
99
|
*/
|
|
95
|
-
declare function useImageUpload<TFile>({ upload, pickFile, hasImage, onError, }: UseImageUploadOptions<TFile>): ImageUploadState;
|
|
100
|
+
declare function useImageUpload<TFile, TResult = void>({ upload, pickFile, hasImage, onError, onUploaded, }: UseImageUploadOptions<TFile, TResult>): ImageUploadState;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Where the photo sits inside the preview frame. `Bottom` matches a kefi-landings
|
|
104
|
+
* cut-out (`.amb-photo img`: 4:5, `object-fit: contain`, `object-position: center
|
|
105
|
+
* bottom`) — feet on the floor of the card. An `as const` object rather than a TS
|
|
106
|
+
* `const enum` for the same isolatedModules reason as FramingAction.
|
|
107
|
+
*/
|
|
108
|
+
declare const PhotoAnchor: {
|
|
109
|
+
readonly Center: "center";
|
|
110
|
+
readonly Bottom: "bottom";
|
|
111
|
+
};
|
|
112
|
+
type PhotoAnchor = (typeof PhotoAnchor)[keyof typeof PhotoAnchor];
|
|
113
|
+
interface PhotoAnchorLayout {
|
|
114
|
+
/** Applied to the editor's preview column: where the consumer's card sits vertically. */
|
|
115
|
+
justifyContent: 'center' | 'flex-end';
|
|
116
|
+
/** CSS `object-position` the consumer spreads onto its `<img>` / RN-web image. */
|
|
117
|
+
objectPosition: 'center center' | 'center bottom';
|
|
118
|
+
}
|
|
119
|
+
/** Pure anchor -> layout mapping (unit-tested; the editor only applies it). */
|
|
120
|
+
declare function resolvePhotoAnchor(anchor: PhotoAnchor): PhotoAnchorLayout;
|
|
96
121
|
|
|
97
122
|
/** Name + hint of one control. `label` is the full phrase ("Move photo up"). */
|
|
98
123
|
interface FramingActionLabel {
|
|
@@ -114,6 +139,10 @@ interface PhotoFramingPreviewArgs {
|
|
|
114
139
|
framing: PhotoFraming;
|
|
115
140
|
/** Ready to spread onto the photo: `style={{ transform }}`. */
|
|
116
141
|
transform: PhotoFramingTransform;
|
|
142
|
+
/** The editor's `anchor` prop (default `center`). */
|
|
143
|
+
anchor: PhotoAnchor;
|
|
144
|
+
/** CSS `object-position` for that anchor, ready for the photo's style. */
|
|
145
|
+
objectPosition: string;
|
|
117
146
|
}
|
|
118
147
|
interface PhotoFramingEditorProps {
|
|
119
148
|
/** Stored framing; clamped before use, so raw config is safe to pass. */
|
|
@@ -128,6 +157,8 @@ interface PhotoFramingEditorProps {
|
|
|
128
157
|
/** Override the default arrow / plus / minus glyphs (e.g. with ui-icons). */
|
|
129
158
|
renderGlyph?: (action: FramingAction, color: string, size: number) => React.ReactNode;
|
|
130
159
|
disabled?: boolean;
|
|
160
|
+
/** Where the photo sits in the preview. `bottom` for cut-outs (kefi `.amb-photo`). Default `center`. */
|
|
161
|
+
anchor?: PhotoAnchor;
|
|
131
162
|
}
|
|
132
163
|
|
|
133
164
|
/**
|
|
@@ -140,6 +171,6 @@ interface PhotoFramingEditorProps {
|
|
|
140
171
|
* LAYOUT_COLLAPSE_BREAKPOINT up they sit side by side.
|
|
141
172
|
*/
|
|
142
173
|
|
|
143
|
-
declare const PhotoFramingEditor: ({ value, onChange, renderPreview, labels, testID, steps, renderGlyph, disabled, }: PhotoFramingEditorProps) => React.ReactElement;
|
|
174
|
+
declare const PhotoFramingEditor: ({ value, onChange, renderPreview, labels, testID, steps, renderGlyph, disabled, anchor, }: PhotoFramingEditorProps) => React.ReactElement;
|
|
144
175
|
|
|
145
|
-
export { DEFAULT_IMAGE_ACCEPT, FramingAction, type FramingActionLabel, ImagePickerButton, type ImagePickerButtonProps, type ImagePickerLabels, ImagePickerStatus, type ImageUploadState, PhotoFramingEditor, type PhotoFramingEditorProps, type PhotoFramingLabels, type PhotoFramingPreviewArgs, PhotoFramingSteps, PhotoFramingTransform, type PickerView, type UseImageUploadOptions, pickWebImage, resolvePickerView, useImageUpload };
|
|
176
|
+
export { DEFAULT_IMAGE_ACCEPT, FramingAction, type FramingActionLabel, ImagePickerButton, type ImagePickerButtonProps, type ImagePickerLabels, ImagePickerStatus, type ImageUploadState, PhotoAnchor, type PhotoAnchorLayout, PhotoFramingEditor, type PhotoFramingEditorProps, type PhotoFramingLabels, type PhotoFramingPreviewArgs, PhotoFramingSteps, PhotoFramingTransform, type PickerView, type UseImageUploadOptions, pickWebImage, resolvePhotoAnchor, resolvePickerView, useImageUpload };
|
package/dist/index.d.ts
CHANGED
|
@@ -42,8 +42,11 @@ interface ImagePickerLabels {
|
|
|
42
42
|
/** Accessibility hint for the button: "Opens a file picker to choose an image". */
|
|
43
43
|
hint: string;
|
|
44
44
|
}
|
|
45
|
-
interface ImagePickerButtonProps<TFile = File> {
|
|
46
|
-
|
|
45
|
+
interface ImagePickerButtonProps<TFile = File, TResult = void> {
|
|
46
|
+
/** Consumer transport; whatever it resolves with (e.g. the stored URL) goes to `onUploaded`. */
|
|
47
|
+
upload: (file: TFile) => Promise<TResult>;
|
|
48
|
+
/** Called with `upload`'s result once the slot is `done`. */
|
|
49
|
+
onUploaded?: (result: TResult) => void;
|
|
47
50
|
labels: ImagePickerLabels;
|
|
48
51
|
testID: string;
|
|
49
52
|
/** Defaults to the browser file dialog. Native consumers pass their own picker. */
|
|
@@ -64,7 +67,7 @@ interface PickerView {
|
|
|
64
67
|
}
|
|
65
68
|
/** Pure status -> copy/variant mapping (unit-tested; the component only paints it). */
|
|
66
69
|
declare function resolvePickerView(status: ImagePickerStatus, labels: ImagePickerLabels): PickerView;
|
|
67
|
-
declare function ImagePickerButton<TFile = File>({ upload, labels, testID, pickFile, accept, hasImage, onError, disabled, }: ImagePickerButtonProps<TFile>): React.ReactElement;
|
|
70
|
+
declare function ImagePickerButton<TFile = File, TResult = void>({ upload, onUploaded, labels, testID, pickFile, accept, hasImage, onError, disabled, }: ImagePickerButtonProps<TFile, TResult>): React.ReactElement;
|
|
68
71
|
|
|
69
72
|
declare const DEFAULT_IMAGE_ACCEPT = "image/*";
|
|
70
73
|
/**
|
|
@@ -72,9 +75,11 @@ declare const DEFAULT_IMAGE_ACCEPT = "image/*";
|
|
|
72
75
|
* where there is no DOM (native) — a native consumer passes its own `pickFile`.
|
|
73
76
|
*/
|
|
74
77
|
declare function pickWebImage(accept?: string): Promise<File | null>;
|
|
75
|
-
interface UseImageUploadOptions<TFile> {
|
|
78
|
+
interface UseImageUploadOptions<TFile, TResult = void> {
|
|
76
79
|
/** Consumer-owned transport. Resolve = stored; reject = error state. No network code lives here. */
|
|
77
|
-
upload: (file: TFile) => Promise<
|
|
80
|
+
upload: (file: TFile) => Promise<TResult>;
|
|
81
|
+
/** Given what `upload` resolved with (e.g. the stored URL), once the slot is `done`. */
|
|
82
|
+
onUploaded?: (result: TResult) => void;
|
|
78
83
|
/** Picks one file; `null` means the user cancelled. */
|
|
79
84
|
pickFile: () => Promise<TFile | null>;
|
|
80
85
|
/** Start in `done` when the slot already holds an image. */
|
|
@@ -92,7 +97,27 @@ interface ImageUploadState {
|
|
|
92
97
|
* done or error back to uploading on the next pick. A cancelled pick leaves the
|
|
93
98
|
* state as it was, so cancelling "Replace" does not wipe a finished upload.
|
|
94
99
|
*/
|
|
95
|
-
declare function useImageUpload<TFile>({ upload, pickFile, hasImage, onError, }: UseImageUploadOptions<TFile>): ImageUploadState;
|
|
100
|
+
declare function useImageUpload<TFile, TResult = void>({ upload, pickFile, hasImage, onError, onUploaded, }: UseImageUploadOptions<TFile, TResult>): ImageUploadState;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Where the photo sits inside the preview frame. `Bottom` matches a kefi-landings
|
|
104
|
+
* cut-out (`.amb-photo img`: 4:5, `object-fit: contain`, `object-position: center
|
|
105
|
+
* bottom`) — feet on the floor of the card. An `as const` object rather than a TS
|
|
106
|
+
* `const enum` for the same isolatedModules reason as FramingAction.
|
|
107
|
+
*/
|
|
108
|
+
declare const PhotoAnchor: {
|
|
109
|
+
readonly Center: "center";
|
|
110
|
+
readonly Bottom: "bottom";
|
|
111
|
+
};
|
|
112
|
+
type PhotoAnchor = (typeof PhotoAnchor)[keyof typeof PhotoAnchor];
|
|
113
|
+
interface PhotoAnchorLayout {
|
|
114
|
+
/** Applied to the editor's preview column: where the consumer's card sits vertically. */
|
|
115
|
+
justifyContent: 'center' | 'flex-end';
|
|
116
|
+
/** CSS `object-position` the consumer spreads onto its `<img>` / RN-web image. */
|
|
117
|
+
objectPosition: 'center center' | 'center bottom';
|
|
118
|
+
}
|
|
119
|
+
/** Pure anchor -> layout mapping (unit-tested; the editor only applies it). */
|
|
120
|
+
declare function resolvePhotoAnchor(anchor: PhotoAnchor): PhotoAnchorLayout;
|
|
96
121
|
|
|
97
122
|
/** Name + hint of one control. `label` is the full phrase ("Move photo up"). */
|
|
98
123
|
interface FramingActionLabel {
|
|
@@ -114,6 +139,10 @@ interface PhotoFramingPreviewArgs {
|
|
|
114
139
|
framing: PhotoFraming;
|
|
115
140
|
/** Ready to spread onto the photo: `style={{ transform }}`. */
|
|
116
141
|
transform: PhotoFramingTransform;
|
|
142
|
+
/** The editor's `anchor` prop (default `center`). */
|
|
143
|
+
anchor: PhotoAnchor;
|
|
144
|
+
/** CSS `object-position` for that anchor, ready for the photo's style. */
|
|
145
|
+
objectPosition: string;
|
|
117
146
|
}
|
|
118
147
|
interface PhotoFramingEditorProps {
|
|
119
148
|
/** Stored framing; clamped before use, so raw config is safe to pass. */
|
|
@@ -128,6 +157,8 @@ interface PhotoFramingEditorProps {
|
|
|
128
157
|
/** Override the default arrow / plus / minus glyphs (e.g. with ui-icons). */
|
|
129
158
|
renderGlyph?: (action: FramingAction, color: string, size: number) => React.ReactNode;
|
|
130
159
|
disabled?: boolean;
|
|
160
|
+
/** Where the photo sits in the preview. `bottom` for cut-outs (kefi `.amb-photo`). Default `center`. */
|
|
161
|
+
anchor?: PhotoAnchor;
|
|
131
162
|
}
|
|
132
163
|
|
|
133
164
|
/**
|
|
@@ -140,6 +171,6 @@ interface PhotoFramingEditorProps {
|
|
|
140
171
|
* LAYOUT_COLLAPSE_BREAKPOINT up they sit side by side.
|
|
141
172
|
*/
|
|
142
173
|
|
|
143
|
-
declare const PhotoFramingEditor: ({ value, onChange, renderPreview, labels, testID, steps, renderGlyph, disabled, }: PhotoFramingEditorProps) => React.ReactElement;
|
|
174
|
+
declare const PhotoFramingEditor: ({ value, onChange, renderPreview, labels, testID, steps, renderGlyph, disabled, anchor, }: PhotoFramingEditorProps) => React.ReactElement;
|
|
144
175
|
|
|
145
|
-
export { DEFAULT_IMAGE_ACCEPT, FramingAction, type FramingActionLabel, ImagePickerButton, type ImagePickerButtonProps, type ImagePickerLabels, ImagePickerStatus, type ImageUploadState, PhotoFramingEditor, type PhotoFramingEditorProps, type PhotoFramingLabels, type PhotoFramingPreviewArgs, PhotoFramingSteps, PhotoFramingTransform, type PickerView, type UseImageUploadOptions, pickWebImage, resolvePickerView, useImageUpload };
|
|
176
|
+
export { DEFAULT_IMAGE_ACCEPT, FramingAction, type FramingActionLabel, ImagePickerButton, type ImagePickerButtonProps, type ImagePickerLabels, ImagePickerStatus, type ImageUploadState, PhotoAnchor, type PhotoAnchorLayout, PhotoFramingEditor, type PhotoFramingEditorProps, type PhotoFramingLabels, type PhotoFramingPreviewArgs, PhotoFramingSteps, PhotoFramingTransform, type PickerView, type UseImageUploadOptions, pickWebImage, resolvePhotoAnchor, resolvePickerView, useImageUpload };
|
package/dist/index.js
CHANGED
|
@@ -77,7 +77,8 @@ function useImageUpload({
|
|
|
77
77
|
upload,
|
|
78
78
|
pickFile,
|
|
79
79
|
hasImage = false,
|
|
80
|
-
onError
|
|
80
|
+
onError,
|
|
81
|
+
onUploaded
|
|
81
82
|
}) {
|
|
82
83
|
const [status, setStatus] = react.useState(
|
|
83
84
|
hasImage ? ImagePickerStatus.Done : ImagePickerStatus.Idle
|
|
@@ -96,11 +97,12 @@ function useImageUpload({
|
|
|
96
97
|
const pick = react.useCallback(async () => {
|
|
97
98
|
if (busy.current) return;
|
|
98
99
|
busy.current = true;
|
|
100
|
+
let uploaded = null;
|
|
99
101
|
try {
|
|
100
102
|
const file = await pickFile();
|
|
101
103
|
if (file === null) return;
|
|
102
104
|
settle(ImagePickerStatus.Uploading);
|
|
103
|
-
await upload(file);
|
|
105
|
+
uploaded = { result: await upload(file) };
|
|
104
106
|
settle(ImagePickerStatus.Done);
|
|
105
107
|
} catch (error) {
|
|
106
108
|
settle(ImagePickerStatus.Error);
|
|
@@ -108,7 +110,8 @@ function useImageUpload({
|
|
|
108
110
|
} finally {
|
|
109
111
|
busy.current = false;
|
|
110
112
|
}
|
|
111
|
-
|
|
113
|
+
if (uploaded !== null) onUploaded?.(uploaded.result);
|
|
114
|
+
}, [pickFile, upload, onError, onUploaded, settle]);
|
|
112
115
|
return { status, pick };
|
|
113
116
|
}
|
|
114
117
|
function resolvePickerView(status, labels) {
|
|
@@ -131,6 +134,7 @@ var styles = reactNative.StyleSheet.create({
|
|
|
131
134
|
});
|
|
132
135
|
function ImagePickerButton({
|
|
133
136
|
upload,
|
|
137
|
+
onUploaded,
|
|
134
138
|
labels,
|
|
135
139
|
testID,
|
|
136
140
|
pickFile,
|
|
@@ -141,7 +145,13 @@ function ImagePickerButton({
|
|
|
141
145
|
}) {
|
|
142
146
|
const { theme } = uiFeedback.useUi();
|
|
143
147
|
const webPick = react.useCallback(() => pickWebImage(accept), [accept]);
|
|
144
|
-
const { status, pick } = useImageUpload({
|
|
148
|
+
const { status, pick } = useImageUpload({
|
|
149
|
+
upload,
|
|
150
|
+
pickFile: pickFile ?? webPick,
|
|
151
|
+
hasImage,
|
|
152
|
+
onError,
|
|
153
|
+
onUploaded
|
|
154
|
+
});
|
|
145
155
|
const view = react.useMemo(() => resolvePickerView(status, labels), [status, labels]);
|
|
146
156
|
const isUploading = status === ImagePickerStatus.Uploading;
|
|
147
157
|
const statusColor = view.isError ? theme.semantic.error["500"] : theme.colors.textSecondary;
|
|
@@ -248,6 +258,15 @@ var FramingControls = ({
|
|
|
248
258
|
] })
|
|
249
259
|
] });
|
|
250
260
|
};
|
|
261
|
+
|
|
262
|
+
// src/PhotoFramingEditor/PhotoAnchor.ts
|
|
263
|
+
var PhotoAnchor = {
|
|
264
|
+
Center: "center",
|
|
265
|
+
Bottom: "bottom"
|
|
266
|
+
};
|
|
267
|
+
function resolvePhotoAnchor(anchor) {
|
|
268
|
+
return anchor === PhotoAnchor.Bottom ? { justifyContent: "flex-end", objectPosition: "center bottom" } : { justifyContent: "center", objectPosition: "center center" };
|
|
269
|
+
}
|
|
251
270
|
var GAP2 = 16;
|
|
252
271
|
var styles3 = reactNative.StyleSheet.create({
|
|
253
272
|
root: { flexDirection: "column", gap: GAP2, alignItems: "stretch" },
|
|
@@ -263,18 +282,27 @@ var PhotoFramingEditor = ({
|
|
|
263
282
|
testID,
|
|
264
283
|
steps,
|
|
265
284
|
renderGlyph,
|
|
266
|
-
disabled = false
|
|
285
|
+
disabled = false,
|
|
286
|
+
anchor = PhotoAnchor.Center
|
|
267
287
|
}) => {
|
|
268
288
|
const { width } = reactNative.useWindowDimensions();
|
|
269
289
|
const wide = width >= uiLayout.LAYOUT_COLLAPSE_BREAKPOINT;
|
|
270
290
|
const framing$1 = react.useMemo(() => framing.clampFraming(value), [value]);
|
|
271
291
|
const transform = react.useMemo(() => framingToTransform(framing$1), [framing$1]);
|
|
292
|
+
const anchorLayout = react.useMemo(() => resolvePhotoAnchor(anchor), [anchor]);
|
|
272
293
|
const onAction = react.useCallback(
|
|
273
294
|
(action) => onChange(stepFraming(framing$1, action, steps)),
|
|
274
295
|
[framing$1, onChange, steps]
|
|
275
296
|
);
|
|
276
297
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.root, wide ? styles3.rootWide : null], testID, children: [
|
|
277
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
298
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
299
|
+
reactNative.View,
|
|
300
|
+
{
|
|
301
|
+
style: [styles3.preview, { justifyContent: anchorLayout.justifyContent }, wide ? styles3.previewWide : null],
|
|
302
|
+
testID: `${testID}-preview`,
|
|
303
|
+
children: renderPreview({ framing: framing$1, transform, anchor, objectPosition: anchorLayout.objectPosition })
|
|
304
|
+
}
|
|
305
|
+
),
|
|
278
306
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
279
307
|
FramingControls,
|
|
280
308
|
{
|
|
@@ -311,10 +339,12 @@ exports.FramingAction = FramingAction;
|
|
|
311
339
|
exports.ImagePickerButton = ImagePickerButton;
|
|
312
340
|
exports.ImagePickerStatus = ImagePickerStatus;
|
|
313
341
|
exports.PHOTO_FRAMING_STEPS = PHOTO_FRAMING_STEPS;
|
|
342
|
+
exports.PhotoAnchor = PhotoAnchor;
|
|
314
343
|
exports.PhotoFramingEditor = PhotoFramingEditor;
|
|
315
344
|
exports.canStepFraming = canStepFraming;
|
|
316
345
|
exports.framingToTransform = framingToTransform;
|
|
317
346
|
exports.pickWebImage = pickWebImage;
|
|
347
|
+
exports.resolvePhotoAnchor = resolvePhotoAnchor;
|
|
318
348
|
exports.resolvePickerView = resolvePickerView;
|
|
319
349
|
exports.stepFraming = stepFraming;
|
|
320
350
|
exports.useImageUpload = useImageUpload;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/framing/photoFraming.ts","../src/framing/FramingAction.ts","../src/framing/stepFraming.ts","../src/ImagePickerButton/ImagePickerStatus.ts","../src/ImagePickerButton/useImageUpload.ts","../src/ImagePickerButton/ImagePickerButton.tsx","../src/PhotoFramingEditor/FramingControls.tsx","../src/PhotoFramingEditor/PhotoFramingEditor.tsx"],"names":["DEFAULT_PHOTO_FRAMING","clampFraming","useState","useRef","useEffect","useCallback","StyleSheet","useUi","useMemo","jsxs","View","jsx","Button","Text","styles","IconButton","GAP","useWindowDimensions","LAYOUT_COLLAPSE_BREAKPOINT","framing"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Photo framing: the `{x, y, scale}` an organiser sets on a performer card.\n *\n * The bounds, default, clamp and identity check come from\n * `@dloizides/site-template-kit/framing` — the same module the kefi-landings\n * renderer builds its `--photo-tf` CSS from (KEFI-PEOPLE-1 T12 \"Organizer people\n * editor: shared photo framing\"). One copy, so the editor can never offer a value\n * the landing page would silently clamp away.\n *\n * That subpath imports nothing (no ajv, no JSON Schema), so this file stays\n * framework-free: it backs the `@dloizides/ui-media/framing` subpath.\n */\nimport type { PhotoFraming } from '@dloizides/site-template-kit/framing';\n\nexport {\n PHOTO_FRAMING_BOUNDS,\n DEFAULT_PHOTO_FRAMING,\n clampFraming,\n isDefaultFraming,\n} from '@dloizides/site-template-kit/framing';\nexport type { PhotoFraming, PhotoFramingInput } from '@dloizides/site-template-kit/framing';\n\n/**\n * An RN `transform` array. Percent translates are relative to the element\n * itself on RN-web, matching the landing page's CSS `translate(x%, y%) scale(s)`.\n * Typed locally so this module stays free of a react-native import.\n */\nexport type PhotoFramingTransform = [\n { translateX: `${number}%` },\n { translateY: `${number}%` },\n { scale: number },\n];\n\nexport const framingToTransform = (framing: PhotoFraming): PhotoFramingTransform => [\n { translateX: `${framing.x}%` },\n { translateY: `${framing.y}%` },\n { scale: framing.scale },\n];\n","/**\n * The seven controls of the framing editor. An `as const` object rather than a\n * TS `const enum`: a const enum shipped in a .d.ts cannot be read by consumers\n * compiled with `isolatedModules` (every Expo / Babel portal).\n */\nexport const FramingAction = {\n ZoomIn: 'zoomIn',\n ZoomOut: 'zoomOut',\n MoveUp: 'moveUp',\n MoveDown: 'moveDown',\n MoveLeft: 'moveLeft',\n MoveRight: 'moveRight',\n Reset: 'reset',\n} as const;\n\nexport type FramingAction = (typeof FramingAction)[keyof typeof FramingAction];\n","import { FramingAction } from './FramingAction';\nimport type { PhotoFraming, PhotoFramingInput } from './photoFraming';\nimport { DEFAULT_PHOTO_FRAMING, clampFraming } from './photoFraming';\n\n/** How far one press moves or zooms. */\nexport interface PhotoFramingSteps {\n /** Nudge per press, % of the card. */\n offsetPct: number;\n /** Zoom per press, as a scale delta. */\n scale: number;\n}\n\nexport const PHOTO_FRAMING_STEPS: PhotoFramingSteps = Object.freeze({ offsetPct: 5, scale: 0.1 });\n\n/** Three decimals, the same precision the landing page writes into its CSS. */\nconst PRECISION = 1000;\nconst round = (n: number): number => Math.round(n * PRECISION) / PRECISION;\n\ntype MoveAction = Exclude<FramingAction, typeof FramingAction.Reset>;\n\n/** [x, y, scale] direction of each non-reset action. */\nconst DIRECTIONS: Record<MoveAction, readonly [number, number, number]> = {\n [FramingAction.ZoomIn]: [0, 0, 1],\n [FramingAction.ZoomOut]: [0, 0, -1],\n [FramingAction.MoveUp]: [0, -1, 0],\n [FramingAction.MoveDown]: [0, 1, 0],\n [FramingAction.MoveLeft]: [-1, 0, 0],\n [FramingAction.MoveRight]: [1, 0, 0],\n};\n\n/**\n * Applies one control press. The result is always clamped and rounded, so ten\n * presses of +0.1 land on exactly 2, not 1.9999999999999998.\n */\nexport function stepFraming(\n current: PhotoFramingInput | null | undefined,\n action: FramingAction,\n steps: PhotoFramingSteps = PHOTO_FRAMING_STEPS,\n): PhotoFraming {\n if (action === FramingAction.Reset) return DEFAULT_PHOTO_FRAMING;\n const base = clampFraming(current);\n const [dx, dy, ds] = DIRECTIONS[action];\n return clampFraming({\n x: round(base.x + dx * steps.offsetPct),\n y: round(base.y + dy * steps.offsetPct),\n scale: round(base.scale + ds * steps.scale),\n });\n}\n\n/** False when the press would change nothing (at a bound, or reset on the default). */\nexport function canStepFraming(\n current: PhotoFramingInput | null | undefined,\n action: FramingAction,\n steps: PhotoFramingSteps = PHOTO_FRAMING_STEPS,\n): boolean {\n const base = clampFraming(current);\n const next = stepFraming(base, action, steps);\n return next.x !== base.x || next.y !== base.y || next.scale !== base.scale;\n}\n","/**\n * The four states of an image upload slot (KEFI-PEOPLE-1 design board \"upload\n * slot\"). An `as const` object, not a `const enum`, so Babel / isolatedModules\n * consumers can read it from the published .d.ts.\n */\nexport const ImagePickerStatus = {\n Idle: 'idle',\n Uploading: 'uploading',\n Error: 'error',\n Done: 'done',\n} as const;\n\nexport type ImagePickerStatus = (typeof ImagePickerStatus)[keyof typeof ImagePickerStatus];\n","import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { ImagePickerStatus } from './ImagePickerStatus';\n\nexport const DEFAULT_IMAGE_ACCEPT = 'image/*';\n\n/**\n * Opens the browser's file dialog for ONE file. Resolves `null` on cancel, and\n * where there is no DOM (native) — a native consumer passes its own `pickFile`.\n */\nexport function pickWebImage(accept: string = DEFAULT_IMAGE_ACCEPT): Promise<File | null> {\n if (typeof document === 'undefined') return Promise.resolve(null);\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = accept;\n input.addEventListener('change', () => resolve(input.files?.[0] ?? null), { once: true });\n input.addEventListener('cancel', () => resolve(null), { once: true });\n input.click();\n });\n}\n\nexport interface UseImageUploadOptions<TFile> {\n /** Consumer-owned transport. Resolve = stored; reject = error state. No network code lives here. */\n upload: (file: TFile) => Promise<void>;\n /** Picks one file; `null` means the user cancelled. */\n pickFile: () => Promise<TFile | null>;\n /** Start in `done` when the slot already holds an image. */\n hasImage?: boolean;\n /** Told about a failed pick or upload (logging / toasts). */\n onError?: (error: unknown) => void;\n}\n\nexport interface ImageUploadState {\n status: ImagePickerStatus;\n /** Pick then upload. A second call while one is in flight is ignored. */\n pick: () => Promise<void>;\n}\n\n/**\n * The upload slot's state machine: idle -> uploading -> done | error, and from\n * done or error back to uploading on the next pick. A cancelled pick leaves the\n * state as it was, so cancelling \"Replace\" does not wipe a finished upload.\n */\nexport function useImageUpload<TFile>({\n upload,\n pickFile,\n hasImage = false,\n onError,\n}: UseImageUploadOptions<TFile>): ImageUploadState {\n const [status, setStatus] = useState<ImagePickerStatus>(\n hasImage ? ImagePickerStatus.Done : ImagePickerStatus.Idle,\n );\n const busy = useRef(false);\n const mounted = useRef(true);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const settle = useCallback((next: ImagePickerStatus): void => {\n if (mounted.current) setStatus(next);\n }, []);\n\n const pick = useCallback(async (): Promise<void> => {\n if (busy.current) return;\n busy.current = true;\n try {\n const file = await pickFile();\n if (file === null) return;\n settle(ImagePickerStatus.Uploading);\n await upload(file);\n settle(ImagePickerStatus.Done);\n } catch (error) {\n settle(ImagePickerStatus.Error);\n onError?.(error);\n } finally {\n busy.current = false;\n }\n }, [pickFile, upload, onError, settle]);\n\n return { status, pick };\n}\n","/**\r\n * ImagePickerButton — one upload slot: pick an image, hand it to the consumer's\r\n * `upload` function, show idle / uploading / error / done.\r\n *\r\n * No network code and no copy of its own: the transport is a prop and every\r\n * string arrives through `labels`, so each portal passes its FM() keys.\r\n */\r\nimport React, { useCallback, useMemo } from 'react';\r\n\r\nimport { StyleSheet, Text, View } from 'react-native';\r\n\r\nimport { Button } from '@dloizides/ui-buttons';\r\nimport type { ButtonVariant } from '@dloizides/ui-buttons';\r\nimport { useUi } from '@dloizides/ui-feedback';\r\n\r\nimport { ImagePickerStatus } from './ImagePickerStatus';\r\nimport { DEFAULT_IMAGE_ACCEPT, pickWebImage, useImageUpload } from './useImageUpload';\r\n\r\nexport interface ImagePickerLabels {\r\n /** Button, idle: \"Add photo\". */\r\n pick: string;\r\n /** Button, uploading: \"Uploading...\". */\r\n uploading: string;\r\n /** Button, done: \"Replace photo\". */\r\n replace: string;\r\n /** Button, error: \"Try again\". */\r\n retry: string;\r\n /** Status line after a successful upload: \"Photo saved\". */\r\n done: string;\r\n /** Status line after a failure: \"Upload failed\". */\r\n error: string;\r\n /** Accessibility hint for the button: \"Opens a file picker to choose an image\". */\r\n hint: string;\r\n}\r\n\r\nexport interface ImagePickerButtonProps<TFile = File> {\r\n upload: (file: TFile) => Promise<void>;\r\n labels: ImagePickerLabels;\r\n testID: string;\r\n /** Defaults to the browser file dialog. Native consumers pass their own picker. */\r\n pickFile?: () => Promise<TFile | null>;\r\n /** `accept` for the default web picker. Default `image/*`. */\r\n accept?: string;\r\n /** Start in the `done` state (the slot already has an image). */\r\n hasImage?: boolean;\r\n onError?: (error: unknown) => void;\r\n disabled?: boolean;\r\n}\r\n\r\nexport interface PickerView {\r\n buttonLabel: string;\r\n variant: ButtonVariant;\r\n statusText: string | null;\r\n /** Paint the status line in the theme's error colour. */\r\n isError: boolean;\r\n}\r\n\r\n/** Pure status -> copy/variant mapping (unit-tested; the component only paints it). */\r\nexport function resolvePickerView(status: ImagePickerStatus, labels: ImagePickerLabels): PickerView {\r\n switch (status) {\r\n case ImagePickerStatus.Uploading:\r\n return { buttonLabel: labels.uploading, variant: 'outline', statusText: null, isError: false };\r\n case ImagePickerStatus.Error:\r\n return { buttonLabel: labels.retry, variant: 'danger', statusText: labels.error, isError: true };\r\n case ImagePickerStatus.Done:\r\n return { buttonLabel: labels.replace, variant: 'outline', statusText: labels.done, isError: false };\r\n default:\r\n return { buttonLabel: labels.pick, variant: 'primary', statusText: null, isError: false };\r\n }\r\n}\r\n\r\nconst STATUS_GAP = 6;\r\nconst STATUS_FONT_SIZE = 13;\r\n\r\nconst styles = StyleSheet.create({\r\n root: { alignItems: 'flex-start', gap: STATUS_GAP },\r\n status: { fontSize: STATUS_FONT_SIZE },\r\n});\r\n\r\nexport function ImagePickerButton<TFile = File>({\r\n upload,\r\n labels,\r\n testID,\r\n pickFile,\r\n accept = DEFAULT_IMAGE_ACCEPT,\r\n hasImage,\r\n onError,\r\n disabled = false,\r\n}: ImagePickerButtonProps<TFile>): React.ReactElement {\r\n const { theme } = useUi();\r\n // The default picker yields a DOM File; a consumer with another TFile supplies `pickFile`.\r\n const webPick = useCallback(() => pickWebImage(accept) as Promise<TFile | null>, [accept]);\r\n const { status, pick } = useImageUpload<TFile>({ upload, pickFile: pickFile ?? webPick, hasImage, onError });\r\n const view = useMemo(() => resolvePickerView(status, labels), [status, labels]);\r\n const isUploading = status === ImagePickerStatus.Uploading;\r\n const statusColor = view.isError ? theme.semantic.error['500'] : theme.colors.textSecondary;\r\n\r\n return (\r\n <View style={styles.root} testID={testID}>\r\n <Button\r\n accessibilityHint={labels.hint}\r\n accessibilityLabel={view.buttonLabel}\r\n autoLoading={false}\r\n disabled={disabled || isUploading}\r\n label={view.buttonLabel}\r\n loading={isUploading}\r\n testID={`${testID}-button`}\r\n variant={view.variant}\r\n onPress={pick}\r\n />\r\n {view.statusText !== null ? (\r\n <Text\r\n accessibilityLiveRegion=\"polite\"\r\n style={[styles.status, { color: statusColor }]}\r\n testID={`${testID}-status`}\r\n >\r\n {view.statusText}\r\n </Text>\r\n ) : null}\r\n </View>\r\n );\r\n}\r\n","/**\n * The editor's controls: a zoom stepper (minus, readout, plus) and a 3x3\n * position d-pad with reset in the middle. Every control is a 44px ui-buttons\n * IconButton, disabled when the press would change nothing (at a bound).\n */\nimport React from 'react';\n\nimport { StyleSheet, Text, View } from 'react-native';\n\nimport { IconButton } from '@dloizides/ui-buttons';\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { FramingAction } from '../framing/FramingAction';\nimport type { PhotoFraming } from '../framing/photoFraming';\nimport type { PhotoFramingSteps } from '../framing/stepFraming';\nimport { canStepFraming } from '../framing/stepFraming';\nimport type { PhotoFramingLabels } from './types';\n\n/** WCAG AA touch target; IconButton `md` is this size, the d-pad spacers match it. */\nconst TARGET = 44;\nconst GAP = 8;\nconst SECTION_GAP = 16;\nconst HEADING_SIZE = 13;\nconst READOUT_MIN_WIDTH = 56;\nconst READOUT_SIZE = 15;\nconst PERCENT = 100;\n\nconst DEFAULT_GLYPHS: Record<FramingAction, string> = {\n [FramingAction.ZoomIn]: '+',\n [FramingAction.ZoomOut]: '−',\n [FramingAction.MoveUp]: '↑',\n [FramingAction.MoveDown]: '↓',\n [FramingAction.MoveLeft]: '←',\n [FramingAction.MoveRight]: '→',\n [FramingAction.Reset]: '↺',\n};\n\nconst styles = StyleSheet.create({\n root: { gap: SECTION_GAP },\n section: { gap: GAP },\n heading: { fontSize: HEADING_SIZE, fontWeight: '600' },\n row: { flexDirection: 'row', alignItems: 'center', gap: GAP },\n readout: { minWidth: READOUT_MIN_WIDTH, textAlign: 'center', fontSize: READOUT_SIZE },\n spacer: { width: TARGET, height: TARGET },\n});\n\ninterface FramingControlsProps {\n framing: PhotoFraming;\n steps?: PhotoFramingSteps;\n labels: PhotoFramingLabels;\n onAction: (action: FramingAction) => void;\n renderGlyph?: (action: FramingAction, color: string, size: number) => React.ReactNode;\n disabled: boolean;\n testID: string;\n}\n\nexport const FramingControls = ({\n framing,\n steps,\n labels,\n onAction,\n renderGlyph,\n disabled,\n testID,\n}: FramingControlsProps): React.ReactElement => {\n const { theme } = useUi();\n\n const control = (action: FramingAction): React.ReactElement => (\n <IconButton\n accessibilityHint={labels.actions[action].hint}\n actionLabel={labels.actions[action].label}\n autoLoading={false}\n disabled={disabled || !canStepFraming(framing, action, steps)}\n renderIcon={(color, size) =>\n renderGlyph ? (\n renderGlyph(action, color, size)\n ) : (\n <Text accessible={false} style={{ color, fontSize: size }}>\n {DEFAULT_GLYPHS[action]}\n </Text>\n )\n }\n showLabel={false}\n testID={`${testID}-${action}`}\n onPress={() => onAction(action)}\n />\n );\n const heading = { color: theme.colors.textSecondary };\n\n return (\n <View style={styles.root}>\n <View style={styles.section}>\n <Text accessibilityRole=\"header\" style={[styles.heading, heading]}>\n {labels.size}\n </Text>\n <View style={styles.row}>\n {control(FramingAction.ZoomOut)}\n <Text style={[styles.readout, { color: theme.colors.text }]} testID={`${testID}-scale`}>\n {labels.scaleValue(Math.round(framing.scale * PERCENT))}\n </Text>\n {control(FramingAction.ZoomIn)}\n </View>\n </View>\n <View style={styles.section}>\n <Text accessibilityRole=\"header\" style={[styles.heading, heading]}>\n {labels.position}\n </Text>\n <View style={styles.row}>\n <View style={styles.spacer} />\n {control(FramingAction.MoveUp)}\n <View style={styles.spacer} />\n </View>\n <View style={styles.row}>\n {control(FramingAction.MoveLeft)}\n {control(FramingAction.Reset)}\n {control(FramingAction.MoveRight)}\n </View>\n <View style={styles.row}>\n <View style={styles.spacer} />\n {control(FramingAction.MoveDown)}\n <View style={styles.spacer} />\n </View>\n </View>\n </View>\n );\n};\n","/**\n * PhotoFramingEditor — the KEFI-PEOPLE-1 \"A2\" framing screen: a live preview of\n * the consumer's own card (render prop) beside a zoom stepper and a position\n * d-pad. The value is `{x, y, scale}`, clamped to PHOTO_FRAMING_BOUNDS, the same\n * rules the public landing page applies — the preview is what ships.\n *\n * Narrow first: preview stacked over the controls; from ui-layout's\n * LAYOUT_COLLAPSE_BREAKPOINT up they sit side by side.\n */\nimport React, { useCallback, useMemo } from 'react';\n\nimport { StyleSheet, View, useWindowDimensions } from 'react-native';\n\nimport { LAYOUT_COLLAPSE_BREAKPOINT } from '@dloizides/ui-layout';\n\nimport type { FramingAction } from '../framing/FramingAction';\nimport { clampFraming, framingToTransform } from '../framing/photoFraming';\nimport { stepFraming } from '../framing/stepFraming';\nimport { FramingControls } from './FramingControls';\nimport type { PhotoFramingEditorProps } from './types';\n\nconst GAP = 16;\n\nconst styles = StyleSheet.create({\n root: { flexDirection: 'column', gap: GAP, alignItems: 'stretch' },\n rootWide: { flexDirection: 'row', alignItems: 'flex-start' },\n preview: { alignItems: 'center', overflow: 'hidden' },\n previewWide: { flexShrink: 1 },\n});\n\nexport const PhotoFramingEditor = ({\n value,\n onChange,\n renderPreview,\n labels,\n testID,\n steps,\n renderGlyph,\n disabled = false,\n}: PhotoFramingEditorProps): React.ReactElement => {\n const { width } = useWindowDimensions();\n const wide = width >= LAYOUT_COLLAPSE_BREAKPOINT;\n const framing = useMemo(() => clampFraming(value), [value]);\n const transform = useMemo(() => framingToTransform(framing), [framing]);\n\n const onAction = useCallback(\n (action: FramingAction): void => onChange(stepFraming(framing, action, steps)),\n [framing, onChange, steps],\n );\n\n return (\n <View style={[styles.root, wide ? styles.rootWide : null]} testID={testID}>\n <View style={[styles.preview, wide ? styles.previewWide : null]} testID={`${testID}-preview`}>\n {renderPreview({ framing, transform })}\n </View>\n <FramingControls\n disabled={disabled}\n framing={framing}\n labels={labels}\n renderGlyph={renderGlyph}\n steps={steps}\n testID={testID}\n onAction={onAction}\n />\n </View>\n );\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/framing/photoFraming.ts","../src/framing/FramingAction.ts","../src/framing/stepFraming.ts","../src/ImagePickerButton/ImagePickerStatus.ts","../src/ImagePickerButton/useImageUpload.ts","../src/ImagePickerButton/ImagePickerButton.tsx","../src/PhotoFramingEditor/FramingControls.tsx","../src/PhotoFramingEditor/PhotoAnchor.ts","../src/PhotoFramingEditor/PhotoFramingEditor.tsx"],"names":["DEFAULT_PHOTO_FRAMING","clampFraming","useState","useRef","useEffect","useCallback","StyleSheet","useUi","useMemo","jsxs","View","jsx","Button","Text","styles","IconButton","GAP","useWindowDimensions","LAYOUT_COLLAPSE_BREAKPOINT","framing"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Photo framing: the `{x, y, scale}` an organiser sets on a performer card.\n *\n * The bounds, default, clamp and identity check come from\n * `@dloizides/site-template-kit/framing` — the same module the kefi-landings\n * renderer builds its `--photo-tf` CSS from (KEFI-PEOPLE-1 T12 \"Organizer people\n * editor: shared photo framing\"). One copy, so the editor can never offer a value\n * the landing page would silently clamp away.\n *\n * That subpath imports nothing (no ajv, no JSON Schema), so this file stays\n * framework-free: it backs the `@dloizides/ui-media/framing` subpath.\n */\nimport type { PhotoFraming } from '@dloizides/site-template-kit/framing';\n\nexport {\n PHOTO_FRAMING_BOUNDS,\n DEFAULT_PHOTO_FRAMING,\n clampFraming,\n isDefaultFraming,\n} from '@dloizides/site-template-kit/framing';\nexport type { PhotoFraming, PhotoFramingInput } from '@dloizides/site-template-kit/framing';\n\n/**\n * An RN `transform` array. Percent translates are relative to the element\n * itself on RN-web, matching the landing page's CSS `translate(x%, y%) scale(s)`.\n * Typed locally so this module stays free of a react-native import.\n */\nexport type PhotoFramingTransform = [\n { translateX: `${number}%` },\n { translateY: `${number}%` },\n { scale: number },\n];\n\nexport const framingToTransform = (framing: PhotoFraming): PhotoFramingTransform => [\n { translateX: `${framing.x}%` },\n { translateY: `${framing.y}%` },\n { scale: framing.scale },\n];\n","/**\n * The seven controls of the framing editor. An `as const` object rather than a\n * TS `const enum`: a const enum shipped in a .d.ts cannot be read by consumers\n * compiled with `isolatedModules` (every Expo / Babel portal).\n */\nexport const FramingAction = {\n ZoomIn: 'zoomIn',\n ZoomOut: 'zoomOut',\n MoveUp: 'moveUp',\n MoveDown: 'moveDown',\n MoveLeft: 'moveLeft',\n MoveRight: 'moveRight',\n Reset: 'reset',\n} as const;\n\nexport type FramingAction = (typeof FramingAction)[keyof typeof FramingAction];\n","import { FramingAction } from './FramingAction';\nimport type { PhotoFraming, PhotoFramingInput } from './photoFraming';\nimport { DEFAULT_PHOTO_FRAMING, clampFraming } from './photoFraming';\n\n/** How far one press moves or zooms. */\nexport interface PhotoFramingSteps {\n /** Nudge per press, % of the card. */\n offsetPct: number;\n /** Zoom per press, as a scale delta. */\n scale: number;\n}\n\nexport const PHOTO_FRAMING_STEPS: PhotoFramingSteps = Object.freeze({ offsetPct: 5, scale: 0.1 });\n\n/** Three decimals, the same precision the landing page writes into its CSS. */\nconst PRECISION = 1000;\nconst round = (n: number): number => Math.round(n * PRECISION) / PRECISION;\n\ntype MoveAction = Exclude<FramingAction, typeof FramingAction.Reset>;\n\n/** [x, y, scale] direction of each non-reset action. */\nconst DIRECTIONS: Record<MoveAction, readonly [number, number, number]> = {\n [FramingAction.ZoomIn]: [0, 0, 1],\n [FramingAction.ZoomOut]: [0, 0, -1],\n [FramingAction.MoveUp]: [0, -1, 0],\n [FramingAction.MoveDown]: [0, 1, 0],\n [FramingAction.MoveLeft]: [-1, 0, 0],\n [FramingAction.MoveRight]: [1, 0, 0],\n};\n\n/**\n * Applies one control press. The result is always clamped and rounded, so ten\n * presses of +0.1 land on exactly 2, not 1.9999999999999998.\n */\nexport function stepFraming(\n current: PhotoFramingInput | null | undefined,\n action: FramingAction,\n steps: PhotoFramingSteps = PHOTO_FRAMING_STEPS,\n): PhotoFraming {\n if (action === FramingAction.Reset) return DEFAULT_PHOTO_FRAMING;\n const base = clampFraming(current);\n const [dx, dy, ds] = DIRECTIONS[action];\n return clampFraming({\n x: round(base.x + dx * steps.offsetPct),\n y: round(base.y + dy * steps.offsetPct),\n scale: round(base.scale + ds * steps.scale),\n });\n}\n\n/** False when the press would change nothing (at a bound, or reset on the default). */\nexport function canStepFraming(\n current: PhotoFramingInput | null | undefined,\n action: FramingAction,\n steps: PhotoFramingSteps = PHOTO_FRAMING_STEPS,\n): boolean {\n const base = clampFraming(current);\n const next = stepFraming(base, action, steps);\n return next.x !== base.x || next.y !== base.y || next.scale !== base.scale;\n}\n","/**\n * The four states of an image upload slot (KEFI-PEOPLE-1 design board \"upload\n * slot\"). An `as const` object, not a `const enum`, so Babel / isolatedModules\n * consumers can read it from the published .d.ts.\n */\nexport const ImagePickerStatus = {\n Idle: 'idle',\n Uploading: 'uploading',\n Error: 'error',\n Done: 'done',\n} as const;\n\nexport type ImagePickerStatus = (typeof ImagePickerStatus)[keyof typeof ImagePickerStatus];\n","import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { ImagePickerStatus } from './ImagePickerStatus';\n\nexport const DEFAULT_IMAGE_ACCEPT = 'image/*';\n\n/**\n * Opens the browser's file dialog for ONE file. Resolves `null` on cancel, and\n * where there is no DOM (native) — a native consumer passes its own `pickFile`.\n */\nexport function pickWebImage(accept: string = DEFAULT_IMAGE_ACCEPT): Promise<File | null> {\n if (typeof document === 'undefined') return Promise.resolve(null);\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = accept;\n input.addEventListener('change', () => resolve(input.files?.[0] ?? null), { once: true });\n input.addEventListener('cancel', () => resolve(null), { once: true });\n input.click();\n });\n}\n\nexport interface UseImageUploadOptions<TFile, TResult = void> {\n /** Consumer-owned transport. Resolve = stored; reject = error state. No network code lives here. */\n upload: (file: TFile) => Promise<TResult>;\n /** Given what `upload` resolved with (e.g. the stored URL), once the slot is `done`. */\n onUploaded?: (result: TResult) => void;\n /** Picks one file; `null` means the user cancelled. */\n pickFile: () => Promise<TFile | null>;\n /** Start in `done` when the slot already holds an image. */\n hasImage?: boolean;\n /** Told about a failed pick or upload (logging / toasts). */\n onError?: (error: unknown) => void;\n}\n\nexport interface ImageUploadState {\n status: ImagePickerStatus;\n /** Pick then upload. A second call while one is in flight is ignored. */\n pick: () => Promise<void>;\n}\n\n/**\n * The upload slot's state machine: idle -> uploading -> done | error, and from\n * done or error back to uploading on the next pick. A cancelled pick leaves the\n * state as it was, so cancelling \"Replace\" does not wipe a finished upload.\n */\nexport function useImageUpload<TFile, TResult = void>({\n upload,\n pickFile,\n hasImage = false,\n onError,\n onUploaded,\n}: UseImageUploadOptions<TFile, TResult>): ImageUploadState {\n const [status, setStatus] = useState<ImagePickerStatus>(\n hasImage ? ImagePickerStatus.Done : ImagePickerStatus.Idle,\n );\n const busy = useRef(false);\n const mounted = useRef(true);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const settle = useCallback((next: ImagePickerStatus): void => {\n if (mounted.current) setStatus(next);\n }, []);\n\n const pick = useCallback(async (): Promise<void> => {\n if (busy.current) return;\n busy.current = true;\n // Boxed so a falsy / void result still counts as uploaded.\n let uploaded: { result: TResult } | null = null;\n try {\n const file = await pickFile();\n if (file === null) return;\n settle(ImagePickerStatus.Uploading);\n uploaded = { result: await upload(file) };\n settle(ImagePickerStatus.Done);\n } catch (error) {\n settle(ImagePickerStatus.Error);\n onError?.(error);\n } finally {\n busy.current = false;\n }\n // Outside the try: a throwing consumer callback must not repaint a stored upload as failed.\n if (uploaded !== null) onUploaded?.(uploaded.result);\n }, [pickFile, upload, onError, onUploaded, settle]);\n\n return { status, pick };\n}\n","/**\r\n * ImagePickerButton — one upload slot: pick an image, hand it to the consumer's\r\n * `upload` function, show idle / uploading / error / done.\r\n *\r\n * No network code and no copy of its own: the transport is a prop and every\r\n * string arrives through `labels`, so each portal passes its FM() keys.\r\n */\r\nimport React, { useCallback, useMemo } from 'react';\r\n\r\nimport { StyleSheet, Text, View } from 'react-native';\r\n\r\nimport { Button } from '@dloizides/ui-buttons';\r\nimport type { ButtonVariant } from '@dloizides/ui-buttons';\r\nimport { useUi } from '@dloizides/ui-feedback';\r\n\r\nimport { ImagePickerStatus } from './ImagePickerStatus';\r\nimport { DEFAULT_IMAGE_ACCEPT, pickWebImage, useImageUpload } from './useImageUpload';\r\n\r\nexport interface ImagePickerLabels {\r\n /** Button, idle: \"Add photo\". */\r\n pick: string;\r\n /** Button, uploading: \"Uploading...\". */\r\n uploading: string;\r\n /** Button, done: \"Replace photo\". */\r\n replace: string;\r\n /** Button, error: \"Try again\". */\r\n retry: string;\r\n /** Status line after a successful upload: \"Photo saved\". */\r\n done: string;\r\n /** Status line after a failure: \"Upload failed\". */\r\n error: string;\r\n /** Accessibility hint for the button: \"Opens a file picker to choose an image\". */\r\n hint: string;\r\n}\r\n\r\nexport interface ImagePickerButtonProps<TFile = File, TResult = void> {\r\n /** Consumer transport; whatever it resolves with (e.g. the stored URL) goes to `onUploaded`. */\r\n upload: (file: TFile) => Promise<TResult>;\r\n /** Called with `upload`'s result once the slot is `done`. */\r\n onUploaded?: (result: TResult) => void;\r\n labels: ImagePickerLabels;\r\n testID: string;\r\n /** Defaults to the browser file dialog. Native consumers pass their own picker. */\r\n pickFile?: () => Promise<TFile | null>;\r\n /** `accept` for the default web picker. Default `image/*`. */\r\n accept?: string;\r\n /** Start in the `done` state (the slot already has an image). */\r\n hasImage?: boolean;\r\n onError?: (error: unknown) => void;\r\n disabled?: boolean;\r\n}\r\n\r\nexport interface PickerView {\r\n buttonLabel: string;\r\n variant: ButtonVariant;\r\n statusText: string | null;\r\n /** Paint the status line in the theme's error colour. */\r\n isError: boolean;\r\n}\r\n\r\n/** Pure status -> copy/variant mapping (unit-tested; the component only paints it). */\r\nexport function resolvePickerView(status: ImagePickerStatus, labels: ImagePickerLabels): PickerView {\r\n switch (status) {\r\n case ImagePickerStatus.Uploading:\r\n return { buttonLabel: labels.uploading, variant: 'outline', statusText: null, isError: false };\r\n case ImagePickerStatus.Error:\r\n return { buttonLabel: labels.retry, variant: 'danger', statusText: labels.error, isError: true };\r\n case ImagePickerStatus.Done:\r\n return { buttonLabel: labels.replace, variant: 'outline', statusText: labels.done, isError: false };\r\n default:\r\n return { buttonLabel: labels.pick, variant: 'primary', statusText: null, isError: false };\r\n }\r\n}\r\n\r\nconst STATUS_GAP = 6;\r\nconst STATUS_FONT_SIZE = 13;\r\n\r\nconst styles = StyleSheet.create({\r\n root: { alignItems: 'flex-start', gap: STATUS_GAP },\r\n status: { fontSize: STATUS_FONT_SIZE },\r\n});\r\n\r\nexport function ImagePickerButton<TFile = File, TResult = void>({\r\n upload,\r\n onUploaded,\r\n labels,\r\n testID,\r\n pickFile,\r\n accept = DEFAULT_IMAGE_ACCEPT,\r\n hasImage,\r\n onError,\r\n disabled = false,\r\n}: ImagePickerButtonProps<TFile, TResult>): React.ReactElement {\r\n const { theme } = useUi();\r\n // The default picker yields a DOM File; a consumer with another TFile supplies `pickFile`.\r\n const webPick = useCallback(() => pickWebImage(accept) as Promise<TFile | null>, [accept]);\r\n const { status, pick } = useImageUpload<TFile, TResult>({\r\n upload,\r\n pickFile: pickFile ?? webPick,\r\n hasImage,\r\n onError,\r\n onUploaded,\r\n });\r\n const view = useMemo(() => resolvePickerView(status, labels), [status, labels]);\r\n const isUploading = status === ImagePickerStatus.Uploading;\r\n const statusColor = view.isError ? theme.semantic.error['500'] : theme.colors.textSecondary;\r\n\r\n return (\r\n <View style={styles.root} testID={testID}>\r\n <Button\r\n accessibilityHint={labels.hint}\r\n accessibilityLabel={view.buttonLabel}\r\n autoLoading={false}\r\n disabled={disabled || isUploading}\r\n label={view.buttonLabel}\r\n loading={isUploading}\r\n testID={`${testID}-button`}\r\n variant={view.variant}\r\n onPress={pick}\r\n />\r\n {view.statusText !== null ? (\r\n <Text\r\n accessibilityLiveRegion=\"polite\"\r\n style={[styles.status, { color: statusColor }]}\r\n testID={`${testID}-status`}\r\n >\r\n {view.statusText}\r\n </Text>\r\n ) : null}\r\n </View>\r\n );\r\n}\r\n","/**\n * The editor's controls: a zoom stepper (minus, readout, plus) and a 3x3\n * position d-pad with reset in the middle. Every control is a 44px ui-buttons\n * IconButton, disabled when the press would change nothing (at a bound).\n */\nimport React from 'react';\n\nimport { StyleSheet, Text, View } from 'react-native';\n\nimport { IconButton } from '@dloizides/ui-buttons';\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { FramingAction } from '../framing/FramingAction';\nimport type { PhotoFraming } from '../framing/photoFraming';\nimport type { PhotoFramingSteps } from '../framing/stepFraming';\nimport { canStepFraming } from '../framing/stepFraming';\nimport type { PhotoFramingLabels } from './types';\n\n/** WCAG AA touch target; IconButton `md` is this size, the d-pad spacers match it. */\nconst TARGET = 44;\nconst GAP = 8;\nconst SECTION_GAP = 16;\nconst HEADING_SIZE = 13;\nconst READOUT_MIN_WIDTH = 56;\nconst READOUT_SIZE = 15;\nconst PERCENT = 100;\n\nconst DEFAULT_GLYPHS: Record<FramingAction, string> = {\n [FramingAction.ZoomIn]: '+',\n [FramingAction.ZoomOut]: '−',\n [FramingAction.MoveUp]: '↑',\n [FramingAction.MoveDown]: '↓',\n [FramingAction.MoveLeft]: '←',\n [FramingAction.MoveRight]: '→',\n [FramingAction.Reset]: '↺',\n};\n\nconst styles = StyleSheet.create({\n root: { gap: SECTION_GAP },\n section: { gap: GAP },\n heading: { fontSize: HEADING_SIZE, fontWeight: '600' },\n row: { flexDirection: 'row', alignItems: 'center', gap: GAP },\n readout: { minWidth: READOUT_MIN_WIDTH, textAlign: 'center', fontSize: READOUT_SIZE },\n spacer: { width: TARGET, height: TARGET },\n});\n\ninterface FramingControlsProps {\n framing: PhotoFraming;\n steps?: PhotoFramingSteps;\n labels: PhotoFramingLabels;\n onAction: (action: FramingAction) => void;\n renderGlyph?: (action: FramingAction, color: string, size: number) => React.ReactNode;\n disabled: boolean;\n testID: string;\n}\n\nexport const FramingControls = ({\n framing,\n steps,\n labels,\n onAction,\n renderGlyph,\n disabled,\n testID,\n}: FramingControlsProps): React.ReactElement => {\n const { theme } = useUi();\n\n const control = (action: FramingAction): React.ReactElement => (\n <IconButton\n accessibilityHint={labels.actions[action].hint}\n actionLabel={labels.actions[action].label}\n autoLoading={false}\n disabled={disabled || !canStepFraming(framing, action, steps)}\n renderIcon={(color, size) =>\n renderGlyph ? (\n renderGlyph(action, color, size)\n ) : (\n <Text accessible={false} style={{ color, fontSize: size }}>\n {DEFAULT_GLYPHS[action]}\n </Text>\n )\n }\n showLabel={false}\n testID={`${testID}-${action}`}\n onPress={() => onAction(action)}\n />\n );\n const heading = { color: theme.colors.textSecondary };\n\n return (\n <View style={styles.root}>\n <View style={styles.section}>\n <Text accessibilityRole=\"header\" style={[styles.heading, heading]}>\n {labels.size}\n </Text>\n <View style={styles.row}>\n {control(FramingAction.ZoomOut)}\n <Text style={[styles.readout, { color: theme.colors.text }]} testID={`${testID}-scale`}>\n {labels.scaleValue(Math.round(framing.scale * PERCENT))}\n </Text>\n {control(FramingAction.ZoomIn)}\n </View>\n </View>\n <View style={styles.section}>\n <Text accessibilityRole=\"header\" style={[styles.heading, heading]}>\n {labels.position}\n </Text>\n <View style={styles.row}>\n <View style={styles.spacer} />\n {control(FramingAction.MoveUp)}\n <View style={styles.spacer} />\n </View>\n <View style={styles.row}>\n {control(FramingAction.MoveLeft)}\n {control(FramingAction.Reset)}\n {control(FramingAction.MoveRight)}\n </View>\n <View style={styles.row}>\n <View style={styles.spacer} />\n {control(FramingAction.MoveDown)}\n <View style={styles.spacer} />\n </View>\n </View>\n </View>\n );\n};\n","/**\n * Where the photo sits inside the preview frame. `Bottom` matches a kefi-landings\n * cut-out (`.amb-photo img`: 4:5, `object-fit: contain`, `object-position: center\n * bottom`) — feet on the floor of the card. An `as const` object rather than a TS\n * `const enum` for the same isolatedModules reason as FramingAction.\n */\nexport const PhotoAnchor = {\n Center: 'center',\n Bottom: 'bottom',\n} as const;\n\nexport type PhotoAnchor = (typeof PhotoAnchor)[keyof typeof PhotoAnchor];\n\nexport interface PhotoAnchorLayout {\n /** Applied to the editor's preview column: where the consumer's card sits vertically. */\n justifyContent: 'center' | 'flex-end';\n /** CSS `object-position` the consumer spreads onto its `<img>` / RN-web image. */\n objectPosition: 'center center' | 'center bottom';\n}\n\n/** Pure anchor -> layout mapping (unit-tested; the editor only applies it). */\nexport function resolvePhotoAnchor(anchor: PhotoAnchor): PhotoAnchorLayout {\n return anchor === PhotoAnchor.Bottom\n ? { justifyContent: 'flex-end', objectPosition: 'center bottom' }\n : { justifyContent: 'center', objectPosition: 'center center' };\n}\n","/**\n * PhotoFramingEditor — the KEFI-PEOPLE-1 \"A2\" framing screen: a live preview of\n * the consumer's own card (render prop) beside a zoom stepper and a position\n * d-pad. The value is `{x, y, scale}`, clamped to PHOTO_FRAMING_BOUNDS, the same\n * rules the public landing page applies — the preview is what ships.\n *\n * Narrow first: preview stacked over the controls; from ui-layout's\n * LAYOUT_COLLAPSE_BREAKPOINT up they sit side by side.\n */\nimport React, { useCallback, useMemo } from 'react';\n\nimport { StyleSheet, View, useWindowDimensions } from 'react-native';\n\nimport { LAYOUT_COLLAPSE_BREAKPOINT } from '@dloizides/ui-layout';\n\nimport type { FramingAction } from '../framing/FramingAction';\nimport { clampFraming, framingToTransform } from '../framing/photoFraming';\nimport { stepFraming } from '../framing/stepFraming';\nimport { FramingControls } from './FramingControls';\nimport { PhotoAnchor, resolvePhotoAnchor } from './PhotoAnchor';\nimport type { PhotoFramingEditorProps } from './types';\n\nconst GAP = 16;\n\nconst styles = StyleSheet.create({\n root: { flexDirection: 'column', gap: GAP, alignItems: 'stretch' },\n rootWide: { flexDirection: 'row', alignItems: 'flex-start' },\n preview: { alignItems: 'center', overflow: 'hidden' },\n previewWide: { flexShrink: 1 },\n});\n\nexport const PhotoFramingEditor = ({\n value,\n onChange,\n renderPreview,\n labels,\n testID,\n steps,\n renderGlyph,\n disabled = false,\n anchor = PhotoAnchor.Center,\n}: PhotoFramingEditorProps): React.ReactElement => {\n const { width } = useWindowDimensions();\n const wide = width >= LAYOUT_COLLAPSE_BREAKPOINT;\n const framing = useMemo(() => clampFraming(value), [value]);\n const transform = useMemo(() => framingToTransform(framing), [framing]);\n const anchorLayout = useMemo(() => resolvePhotoAnchor(anchor), [anchor]);\n\n const onAction = useCallback(\n (action: FramingAction): void => onChange(stepFraming(framing, action, steps)),\n [framing, onChange, steps],\n );\n\n return (\n <View style={[styles.root, wide ? styles.rootWide : null]} testID={testID}>\n <View\n style={[styles.preview, { justifyContent: anchorLayout.justifyContent }, wide ? styles.previewWide : null]}\n testID={`${testID}-preview`}\n >\n {renderPreview({ framing, transform, anchor, objectPosition: anchorLayout.objectPosition })}\n </View>\n <FramingControls\n disabled={disabled}\n framing={framing}\n labels={labels}\n renderGlyph={renderGlyph}\n steps={steps}\n testID={testID}\n onAction={onAction}\n />\n </View>\n );\n};\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -76,7 +76,8 @@ function useImageUpload({
|
|
|
76
76
|
upload,
|
|
77
77
|
pickFile,
|
|
78
78
|
hasImage = false,
|
|
79
|
-
onError
|
|
79
|
+
onError,
|
|
80
|
+
onUploaded
|
|
80
81
|
}) {
|
|
81
82
|
const [status, setStatus] = useState(
|
|
82
83
|
hasImage ? ImagePickerStatus.Done : ImagePickerStatus.Idle
|
|
@@ -95,11 +96,12 @@ function useImageUpload({
|
|
|
95
96
|
const pick = useCallback(async () => {
|
|
96
97
|
if (busy.current) return;
|
|
97
98
|
busy.current = true;
|
|
99
|
+
let uploaded = null;
|
|
98
100
|
try {
|
|
99
101
|
const file = await pickFile();
|
|
100
102
|
if (file === null) return;
|
|
101
103
|
settle(ImagePickerStatus.Uploading);
|
|
102
|
-
await upload(file);
|
|
104
|
+
uploaded = { result: await upload(file) };
|
|
103
105
|
settle(ImagePickerStatus.Done);
|
|
104
106
|
} catch (error) {
|
|
105
107
|
settle(ImagePickerStatus.Error);
|
|
@@ -107,7 +109,8 @@ function useImageUpload({
|
|
|
107
109
|
} finally {
|
|
108
110
|
busy.current = false;
|
|
109
111
|
}
|
|
110
|
-
|
|
112
|
+
if (uploaded !== null) onUploaded?.(uploaded.result);
|
|
113
|
+
}, [pickFile, upload, onError, onUploaded, settle]);
|
|
111
114
|
return { status, pick };
|
|
112
115
|
}
|
|
113
116
|
function resolvePickerView(status, labels) {
|
|
@@ -130,6 +133,7 @@ var styles = StyleSheet.create({
|
|
|
130
133
|
});
|
|
131
134
|
function ImagePickerButton({
|
|
132
135
|
upload,
|
|
136
|
+
onUploaded,
|
|
133
137
|
labels,
|
|
134
138
|
testID,
|
|
135
139
|
pickFile,
|
|
@@ -140,7 +144,13 @@ function ImagePickerButton({
|
|
|
140
144
|
}) {
|
|
141
145
|
const { theme } = useUi();
|
|
142
146
|
const webPick = useCallback(() => pickWebImage(accept), [accept]);
|
|
143
|
-
const { status, pick } = useImageUpload({
|
|
147
|
+
const { status, pick } = useImageUpload({
|
|
148
|
+
upload,
|
|
149
|
+
pickFile: pickFile ?? webPick,
|
|
150
|
+
hasImage,
|
|
151
|
+
onError,
|
|
152
|
+
onUploaded
|
|
153
|
+
});
|
|
144
154
|
const view = useMemo(() => resolvePickerView(status, labels), [status, labels]);
|
|
145
155
|
const isUploading = status === ImagePickerStatus.Uploading;
|
|
146
156
|
const statusColor = view.isError ? theme.semantic.error["500"] : theme.colors.textSecondary;
|
|
@@ -247,6 +257,15 @@ var FramingControls = ({
|
|
|
247
257
|
] })
|
|
248
258
|
] });
|
|
249
259
|
};
|
|
260
|
+
|
|
261
|
+
// src/PhotoFramingEditor/PhotoAnchor.ts
|
|
262
|
+
var PhotoAnchor = {
|
|
263
|
+
Center: "center",
|
|
264
|
+
Bottom: "bottom"
|
|
265
|
+
};
|
|
266
|
+
function resolvePhotoAnchor(anchor) {
|
|
267
|
+
return anchor === PhotoAnchor.Bottom ? { justifyContent: "flex-end", objectPosition: "center bottom" } : { justifyContent: "center", objectPosition: "center center" };
|
|
268
|
+
}
|
|
250
269
|
var GAP2 = 16;
|
|
251
270
|
var styles3 = StyleSheet.create({
|
|
252
271
|
root: { flexDirection: "column", gap: GAP2, alignItems: "stretch" },
|
|
@@ -262,18 +281,27 @@ var PhotoFramingEditor = ({
|
|
|
262
281
|
testID,
|
|
263
282
|
steps,
|
|
264
283
|
renderGlyph,
|
|
265
|
-
disabled = false
|
|
284
|
+
disabled = false,
|
|
285
|
+
anchor = PhotoAnchor.Center
|
|
266
286
|
}) => {
|
|
267
287
|
const { width } = useWindowDimensions();
|
|
268
288
|
const wide = width >= LAYOUT_COLLAPSE_BREAKPOINT;
|
|
269
289
|
const framing = useMemo(() => clampFraming(value), [value]);
|
|
270
290
|
const transform = useMemo(() => framingToTransform(framing), [framing]);
|
|
291
|
+
const anchorLayout = useMemo(() => resolvePhotoAnchor(anchor), [anchor]);
|
|
271
292
|
const onAction = useCallback(
|
|
272
293
|
(action) => onChange(stepFraming(framing, action, steps)),
|
|
273
294
|
[framing, onChange, steps]
|
|
274
295
|
);
|
|
275
296
|
return /* @__PURE__ */ jsxs(View, { style: [styles3.root, wide ? styles3.rootWide : null], testID, children: [
|
|
276
|
-
/* @__PURE__ */ jsx(
|
|
297
|
+
/* @__PURE__ */ jsx(
|
|
298
|
+
View,
|
|
299
|
+
{
|
|
300
|
+
style: [styles3.preview, { justifyContent: anchorLayout.justifyContent }, wide ? styles3.previewWide : null],
|
|
301
|
+
testID: `${testID}-preview`,
|
|
302
|
+
children: renderPreview({ framing, transform, anchor, objectPosition: anchorLayout.objectPosition })
|
|
303
|
+
}
|
|
304
|
+
),
|
|
277
305
|
/* @__PURE__ */ jsx(
|
|
278
306
|
FramingControls,
|
|
279
307
|
{
|
|
@@ -289,6 +317,6 @@ var PhotoFramingEditor = ({
|
|
|
289
317
|
] });
|
|
290
318
|
};
|
|
291
319
|
|
|
292
|
-
export { DEFAULT_IMAGE_ACCEPT, FramingAction, ImagePickerButton, ImagePickerStatus, PHOTO_FRAMING_STEPS, PhotoFramingEditor, canStepFraming, framingToTransform, pickWebImage, resolvePickerView, stepFraming, useImageUpload };
|
|
320
|
+
export { DEFAULT_IMAGE_ACCEPT, FramingAction, ImagePickerButton, ImagePickerStatus, PHOTO_FRAMING_STEPS, PhotoAnchor, PhotoFramingEditor, canStepFraming, framingToTransform, pickWebImage, resolvePhotoAnchor, resolvePickerView, stepFraming, useImageUpload };
|
|
293
321
|
//# sourceMappingURL=index.mjs.map
|
|
294
322
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/framing/photoFraming.ts","../src/framing/FramingAction.ts","../src/framing/stepFraming.ts","../src/ImagePickerButton/ImagePickerStatus.ts","../src/ImagePickerButton/useImageUpload.ts","../src/ImagePickerButton/ImagePickerButton.tsx","../src/PhotoFramingEditor/FramingControls.tsx","../src/PhotoFramingEditor/PhotoFramingEditor.tsx"],"names":["useCallback","styles","StyleSheet","useUi","jsx","Text","jsxs","View","GAP","useMemo"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Photo framing: the `{x, y, scale}` an organiser sets on a performer card.\n *\n * The bounds, default, clamp and identity check come from\n * `@dloizides/site-template-kit/framing` — the same module the kefi-landings\n * renderer builds its `--photo-tf` CSS from (KEFI-PEOPLE-1 T12 \"Organizer people\n * editor: shared photo framing\"). One copy, so the editor can never offer a value\n * the landing page would silently clamp away.\n *\n * That subpath imports nothing (no ajv, no JSON Schema), so this file stays\n * framework-free: it backs the `@dloizides/ui-media/framing` subpath.\n */\nimport type { PhotoFraming } from '@dloizides/site-template-kit/framing';\n\nexport {\n PHOTO_FRAMING_BOUNDS,\n DEFAULT_PHOTO_FRAMING,\n clampFraming,\n isDefaultFraming,\n} from '@dloizides/site-template-kit/framing';\nexport type { PhotoFraming, PhotoFramingInput } from '@dloizides/site-template-kit/framing';\n\n/**\n * An RN `transform` array. Percent translates are relative to the element\n * itself on RN-web, matching the landing page's CSS `translate(x%, y%) scale(s)`.\n * Typed locally so this module stays free of a react-native import.\n */\nexport type PhotoFramingTransform = [\n { translateX: `${number}%` },\n { translateY: `${number}%` },\n { scale: number },\n];\n\nexport const framingToTransform = (framing: PhotoFraming): PhotoFramingTransform => [\n { translateX: `${framing.x}%` },\n { translateY: `${framing.y}%` },\n { scale: framing.scale },\n];\n","/**\n * The seven controls of the framing editor. An `as const` object rather than a\n * TS `const enum`: a const enum shipped in a .d.ts cannot be read by consumers\n * compiled with `isolatedModules` (every Expo / Babel portal).\n */\nexport const FramingAction = {\n ZoomIn: 'zoomIn',\n ZoomOut: 'zoomOut',\n MoveUp: 'moveUp',\n MoveDown: 'moveDown',\n MoveLeft: 'moveLeft',\n MoveRight: 'moveRight',\n Reset: 'reset',\n} as const;\n\nexport type FramingAction = (typeof FramingAction)[keyof typeof FramingAction];\n","import { FramingAction } from './FramingAction';\nimport type { PhotoFraming, PhotoFramingInput } from './photoFraming';\nimport { DEFAULT_PHOTO_FRAMING, clampFraming } from './photoFraming';\n\n/** How far one press moves or zooms. */\nexport interface PhotoFramingSteps {\n /** Nudge per press, % of the card. */\n offsetPct: number;\n /** Zoom per press, as a scale delta. */\n scale: number;\n}\n\nexport const PHOTO_FRAMING_STEPS: PhotoFramingSteps = Object.freeze({ offsetPct: 5, scale: 0.1 });\n\n/** Three decimals, the same precision the landing page writes into its CSS. */\nconst PRECISION = 1000;\nconst round = (n: number): number => Math.round(n * PRECISION) / PRECISION;\n\ntype MoveAction = Exclude<FramingAction, typeof FramingAction.Reset>;\n\n/** [x, y, scale] direction of each non-reset action. */\nconst DIRECTIONS: Record<MoveAction, readonly [number, number, number]> = {\n [FramingAction.ZoomIn]: [0, 0, 1],\n [FramingAction.ZoomOut]: [0, 0, -1],\n [FramingAction.MoveUp]: [0, -1, 0],\n [FramingAction.MoveDown]: [0, 1, 0],\n [FramingAction.MoveLeft]: [-1, 0, 0],\n [FramingAction.MoveRight]: [1, 0, 0],\n};\n\n/**\n * Applies one control press. The result is always clamped and rounded, so ten\n * presses of +0.1 land on exactly 2, not 1.9999999999999998.\n */\nexport function stepFraming(\n current: PhotoFramingInput | null | undefined,\n action: FramingAction,\n steps: PhotoFramingSteps = PHOTO_FRAMING_STEPS,\n): PhotoFraming {\n if (action === FramingAction.Reset) return DEFAULT_PHOTO_FRAMING;\n const base = clampFraming(current);\n const [dx, dy, ds] = DIRECTIONS[action];\n return clampFraming({\n x: round(base.x + dx * steps.offsetPct),\n y: round(base.y + dy * steps.offsetPct),\n scale: round(base.scale + ds * steps.scale),\n });\n}\n\n/** False when the press would change nothing (at a bound, or reset on the default). */\nexport function canStepFraming(\n current: PhotoFramingInput | null | undefined,\n action: FramingAction,\n steps: PhotoFramingSteps = PHOTO_FRAMING_STEPS,\n): boolean {\n const base = clampFraming(current);\n const next = stepFraming(base, action, steps);\n return next.x !== base.x || next.y !== base.y || next.scale !== base.scale;\n}\n","/**\n * The four states of an image upload slot (KEFI-PEOPLE-1 design board \"upload\n * slot\"). An `as const` object, not a `const enum`, so Babel / isolatedModules\n * consumers can read it from the published .d.ts.\n */\nexport const ImagePickerStatus = {\n Idle: 'idle',\n Uploading: 'uploading',\n Error: 'error',\n Done: 'done',\n} as const;\n\nexport type ImagePickerStatus = (typeof ImagePickerStatus)[keyof typeof ImagePickerStatus];\n","import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { ImagePickerStatus } from './ImagePickerStatus';\n\nexport const DEFAULT_IMAGE_ACCEPT = 'image/*';\n\n/**\n * Opens the browser's file dialog for ONE file. Resolves `null` on cancel, and\n * where there is no DOM (native) — a native consumer passes its own `pickFile`.\n */\nexport function pickWebImage(accept: string = DEFAULT_IMAGE_ACCEPT): Promise<File | null> {\n if (typeof document === 'undefined') return Promise.resolve(null);\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = accept;\n input.addEventListener('change', () => resolve(input.files?.[0] ?? null), { once: true });\n input.addEventListener('cancel', () => resolve(null), { once: true });\n input.click();\n });\n}\n\nexport interface UseImageUploadOptions<TFile> {\n /** Consumer-owned transport. Resolve = stored; reject = error state. No network code lives here. */\n upload: (file: TFile) => Promise<void>;\n /** Picks one file; `null` means the user cancelled. */\n pickFile: () => Promise<TFile | null>;\n /** Start in `done` when the slot already holds an image. */\n hasImage?: boolean;\n /** Told about a failed pick or upload (logging / toasts). */\n onError?: (error: unknown) => void;\n}\n\nexport interface ImageUploadState {\n status: ImagePickerStatus;\n /** Pick then upload. A second call while one is in flight is ignored. */\n pick: () => Promise<void>;\n}\n\n/**\n * The upload slot's state machine: idle -> uploading -> done | error, and from\n * done or error back to uploading on the next pick. A cancelled pick leaves the\n * state as it was, so cancelling \"Replace\" does not wipe a finished upload.\n */\nexport function useImageUpload<TFile>({\n upload,\n pickFile,\n hasImage = false,\n onError,\n}: UseImageUploadOptions<TFile>): ImageUploadState {\n const [status, setStatus] = useState<ImagePickerStatus>(\n hasImage ? ImagePickerStatus.Done : ImagePickerStatus.Idle,\n );\n const busy = useRef(false);\n const mounted = useRef(true);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const settle = useCallback((next: ImagePickerStatus): void => {\n if (mounted.current) setStatus(next);\n }, []);\n\n const pick = useCallback(async (): Promise<void> => {\n if (busy.current) return;\n busy.current = true;\n try {\n const file = await pickFile();\n if (file === null) return;\n settle(ImagePickerStatus.Uploading);\n await upload(file);\n settle(ImagePickerStatus.Done);\n } catch (error) {\n settle(ImagePickerStatus.Error);\n onError?.(error);\n } finally {\n busy.current = false;\n }\n }, [pickFile, upload, onError, settle]);\n\n return { status, pick };\n}\n","/**\r\n * ImagePickerButton — one upload slot: pick an image, hand it to the consumer's\r\n * `upload` function, show idle / uploading / error / done.\r\n *\r\n * No network code and no copy of its own: the transport is a prop and every\r\n * string arrives through `labels`, so each portal passes its FM() keys.\r\n */\r\nimport React, { useCallback, useMemo } from 'react';\r\n\r\nimport { StyleSheet, Text, View } from 'react-native';\r\n\r\nimport { Button } from '@dloizides/ui-buttons';\r\nimport type { ButtonVariant } from '@dloizides/ui-buttons';\r\nimport { useUi } from '@dloizides/ui-feedback';\r\n\r\nimport { ImagePickerStatus } from './ImagePickerStatus';\r\nimport { DEFAULT_IMAGE_ACCEPT, pickWebImage, useImageUpload } from './useImageUpload';\r\n\r\nexport interface ImagePickerLabels {\r\n /** Button, idle: \"Add photo\". */\r\n pick: string;\r\n /** Button, uploading: \"Uploading...\". */\r\n uploading: string;\r\n /** Button, done: \"Replace photo\". */\r\n replace: string;\r\n /** Button, error: \"Try again\". */\r\n retry: string;\r\n /** Status line after a successful upload: \"Photo saved\". */\r\n done: string;\r\n /** Status line after a failure: \"Upload failed\". */\r\n error: string;\r\n /** Accessibility hint for the button: \"Opens a file picker to choose an image\". */\r\n hint: string;\r\n}\r\n\r\nexport interface ImagePickerButtonProps<TFile = File> {\r\n upload: (file: TFile) => Promise<void>;\r\n labels: ImagePickerLabels;\r\n testID: string;\r\n /** Defaults to the browser file dialog. Native consumers pass their own picker. */\r\n pickFile?: () => Promise<TFile | null>;\r\n /** `accept` for the default web picker. Default `image/*`. */\r\n accept?: string;\r\n /** Start in the `done` state (the slot already has an image). */\r\n hasImage?: boolean;\r\n onError?: (error: unknown) => void;\r\n disabled?: boolean;\r\n}\r\n\r\nexport interface PickerView {\r\n buttonLabel: string;\r\n variant: ButtonVariant;\r\n statusText: string | null;\r\n /** Paint the status line in the theme's error colour. */\r\n isError: boolean;\r\n}\r\n\r\n/** Pure status -> copy/variant mapping (unit-tested; the component only paints it). */\r\nexport function resolvePickerView(status: ImagePickerStatus, labels: ImagePickerLabels): PickerView {\r\n switch (status) {\r\n case ImagePickerStatus.Uploading:\r\n return { buttonLabel: labels.uploading, variant: 'outline', statusText: null, isError: false };\r\n case ImagePickerStatus.Error:\r\n return { buttonLabel: labels.retry, variant: 'danger', statusText: labels.error, isError: true };\r\n case ImagePickerStatus.Done:\r\n return { buttonLabel: labels.replace, variant: 'outline', statusText: labels.done, isError: false };\r\n default:\r\n return { buttonLabel: labels.pick, variant: 'primary', statusText: null, isError: false };\r\n }\r\n}\r\n\r\nconst STATUS_GAP = 6;\r\nconst STATUS_FONT_SIZE = 13;\r\n\r\nconst styles = StyleSheet.create({\r\n root: { alignItems: 'flex-start', gap: STATUS_GAP },\r\n status: { fontSize: STATUS_FONT_SIZE },\r\n});\r\n\r\nexport function ImagePickerButton<TFile = File>({\r\n upload,\r\n labels,\r\n testID,\r\n pickFile,\r\n accept = DEFAULT_IMAGE_ACCEPT,\r\n hasImage,\r\n onError,\r\n disabled = false,\r\n}: ImagePickerButtonProps<TFile>): React.ReactElement {\r\n const { theme } = useUi();\r\n // The default picker yields a DOM File; a consumer with another TFile supplies `pickFile`.\r\n const webPick = useCallback(() => pickWebImage(accept) as Promise<TFile | null>, [accept]);\r\n const { status, pick } = useImageUpload<TFile>({ upload, pickFile: pickFile ?? webPick, hasImage, onError });\r\n const view = useMemo(() => resolvePickerView(status, labels), [status, labels]);\r\n const isUploading = status === ImagePickerStatus.Uploading;\r\n const statusColor = view.isError ? theme.semantic.error['500'] : theme.colors.textSecondary;\r\n\r\n return (\r\n <View style={styles.root} testID={testID}>\r\n <Button\r\n accessibilityHint={labels.hint}\r\n accessibilityLabel={view.buttonLabel}\r\n autoLoading={false}\r\n disabled={disabled || isUploading}\r\n label={view.buttonLabel}\r\n loading={isUploading}\r\n testID={`${testID}-button`}\r\n variant={view.variant}\r\n onPress={pick}\r\n />\r\n {view.statusText !== null ? (\r\n <Text\r\n accessibilityLiveRegion=\"polite\"\r\n style={[styles.status, { color: statusColor }]}\r\n testID={`${testID}-status`}\r\n >\r\n {view.statusText}\r\n </Text>\r\n ) : null}\r\n </View>\r\n );\r\n}\r\n","/**\n * The editor's controls: a zoom stepper (minus, readout, plus) and a 3x3\n * position d-pad with reset in the middle. Every control is a 44px ui-buttons\n * IconButton, disabled when the press would change nothing (at a bound).\n */\nimport React from 'react';\n\nimport { StyleSheet, Text, View } from 'react-native';\n\nimport { IconButton } from '@dloizides/ui-buttons';\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { FramingAction } from '../framing/FramingAction';\nimport type { PhotoFraming } from '../framing/photoFraming';\nimport type { PhotoFramingSteps } from '../framing/stepFraming';\nimport { canStepFraming } from '../framing/stepFraming';\nimport type { PhotoFramingLabels } from './types';\n\n/** WCAG AA touch target; IconButton `md` is this size, the d-pad spacers match it. */\nconst TARGET = 44;\nconst GAP = 8;\nconst SECTION_GAP = 16;\nconst HEADING_SIZE = 13;\nconst READOUT_MIN_WIDTH = 56;\nconst READOUT_SIZE = 15;\nconst PERCENT = 100;\n\nconst DEFAULT_GLYPHS: Record<FramingAction, string> = {\n [FramingAction.ZoomIn]: '+',\n [FramingAction.ZoomOut]: '−',\n [FramingAction.MoveUp]: '↑',\n [FramingAction.MoveDown]: '↓',\n [FramingAction.MoveLeft]: '←',\n [FramingAction.MoveRight]: '→',\n [FramingAction.Reset]: '↺',\n};\n\nconst styles = StyleSheet.create({\n root: { gap: SECTION_GAP },\n section: { gap: GAP },\n heading: { fontSize: HEADING_SIZE, fontWeight: '600' },\n row: { flexDirection: 'row', alignItems: 'center', gap: GAP },\n readout: { minWidth: READOUT_MIN_WIDTH, textAlign: 'center', fontSize: READOUT_SIZE },\n spacer: { width: TARGET, height: TARGET },\n});\n\ninterface FramingControlsProps {\n framing: PhotoFraming;\n steps?: PhotoFramingSteps;\n labels: PhotoFramingLabels;\n onAction: (action: FramingAction) => void;\n renderGlyph?: (action: FramingAction, color: string, size: number) => React.ReactNode;\n disabled: boolean;\n testID: string;\n}\n\nexport const FramingControls = ({\n framing,\n steps,\n labels,\n onAction,\n renderGlyph,\n disabled,\n testID,\n}: FramingControlsProps): React.ReactElement => {\n const { theme } = useUi();\n\n const control = (action: FramingAction): React.ReactElement => (\n <IconButton\n accessibilityHint={labels.actions[action].hint}\n actionLabel={labels.actions[action].label}\n autoLoading={false}\n disabled={disabled || !canStepFraming(framing, action, steps)}\n renderIcon={(color, size) =>\n renderGlyph ? (\n renderGlyph(action, color, size)\n ) : (\n <Text accessible={false} style={{ color, fontSize: size }}>\n {DEFAULT_GLYPHS[action]}\n </Text>\n )\n }\n showLabel={false}\n testID={`${testID}-${action}`}\n onPress={() => onAction(action)}\n />\n );\n const heading = { color: theme.colors.textSecondary };\n\n return (\n <View style={styles.root}>\n <View style={styles.section}>\n <Text accessibilityRole=\"header\" style={[styles.heading, heading]}>\n {labels.size}\n </Text>\n <View style={styles.row}>\n {control(FramingAction.ZoomOut)}\n <Text style={[styles.readout, { color: theme.colors.text }]} testID={`${testID}-scale`}>\n {labels.scaleValue(Math.round(framing.scale * PERCENT))}\n </Text>\n {control(FramingAction.ZoomIn)}\n </View>\n </View>\n <View style={styles.section}>\n <Text accessibilityRole=\"header\" style={[styles.heading, heading]}>\n {labels.position}\n </Text>\n <View style={styles.row}>\n <View style={styles.spacer} />\n {control(FramingAction.MoveUp)}\n <View style={styles.spacer} />\n </View>\n <View style={styles.row}>\n {control(FramingAction.MoveLeft)}\n {control(FramingAction.Reset)}\n {control(FramingAction.MoveRight)}\n </View>\n <View style={styles.row}>\n <View style={styles.spacer} />\n {control(FramingAction.MoveDown)}\n <View style={styles.spacer} />\n </View>\n </View>\n </View>\n );\n};\n","/**\n * PhotoFramingEditor — the KEFI-PEOPLE-1 \"A2\" framing screen: a live preview of\n * the consumer's own card (render prop) beside a zoom stepper and a position\n * d-pad. The value is `{x, y, scale}`, clamped to PHOTO_FRAMING_BOUNDS, the same\n * rules the public landing page applies — the preview is what ships.\n *\n * Narrow first: preview stacked over the controls; from ui-layout's\n * LAYOUT_COLLAPSE_BREAKPOINT up they sit side by side.\n */\nimport React, { useCallback, useMemo } from 'react';\n\nimport { StyleSheet, View, useWindowDimensions } from 'react-native';\n\nimport { LAYOUT_COLLAPSE_BREAKPOINT } from '@dloizides/ui-layout';\n\nimport type { FramingAction } from '../framing/FramingAction';\nimport { clampFraming, framingToTransform } from '../framing/photoFraming';\nimport { stepFraming } from '../framing/stepFraming';\nimport { FramingControls } from './FramingControls';\nimport type { PhotoFramingEditorProps } from './types';\n\nconst GAP = 16;\n\nconst styles = StyleSheet.create({\n root: { flexDirection: 'column', gap: GAP, alignItems: 'stretch' },\n rootWide: { flexDirection: 'row', alignItems: 'flex-start' },\n preview: { alignItems: 'center', overflow: 'hidden' },\n previewWide: { flexShrink: 1 },\n});\n\nexport const PhotoFramingEditor = ({\n value,\n onChange,\n renderPreview,\n labels,\n testID,\n steps,\n renderGlyph,\n disabled = false,\n}: PhotoFramingEditorProps): React.ReactElement => {\n const { width } = useWindowDimensions();\n const wide = width >= LAYOUT_COLLAPSE_BREAKPOINT;\n const framing = useMemo(() => clampFraming(value), [value]);\n const transform = useMemo(() => framingToTransform(framing), [framing]);\n\n const onAction = useCallback(\n (action: FramingAction): void => onChange(stepFraming(framing, action, steps)),\n [framing, onChange, steps],\n );\n\n return (\n <View style={[styles.root, wide ? styles.rootWide : null]} testID={testID}>\n <View style={[styles.preview, wide ? styles.previewWide : null]} testID={`${testID}-preview`}>\n {renderPreview({ framing, transform })}\n </View>\n <FramingControls\n disabled={disabled}\n framing={framing}\n labels={labels}\n renderGlyph={renderGlyph}\n steps={steps}\n testID={testID}\n onAction={onAction}\n />\n </View>\n );\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/framing/photoFraming.ts","../src/framing/FramingAction.ts","../src/framing/stepFraming.ts","../src/ImagePickerButton/ImagePickerStatus.ts","../src/ImagePickerButton/useImageUpload.ts","../src/ImagePickerButton/ImagePickerButton.tsx","../src/PhotoFramingEditor/FramingControls.tsx","../src/PhotoFramingEditor/PhotoAnchor.ts","../src/PhotoFramingEditor/PhotoFramingEditor.tsx"],"names":["useCallback","styles","StyleSheet","useUi","jsx","Text","jsxs","View","GAP","useMemo"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Photo framing: the `{x, y, scale}` an organiser sets on a performer card.\n *\n * The bounds, default, clamp and identity check come from\n * `@dloizides/site-template-kit/framing` — the same module the kefi-landings\n * renderer builds its `--photo-tf` CSS from (KEFI-PEOPLE-1 T12 \"Organizer people\n * editor: shared photo framing\"). One copy, so the editor can never offer a value\n * the landing page would silently clamp away.\n *\n * That subpath imports nothing (no ajv, no JSON Schema), so this file stays\n * framework-free: it backs the `@dloizides/ui-media/framing` subpath.\n */\nimport type { PhotoFraming } from '@dloizides/site-template-kit/framing';\n\nexport {\n PHOTO_FRAMING_BOUNDS,\n DEFAULT_PHOTO_FRAMING,\n clampFraming,\n isDefaultFraming,\n} from '@dloizides/site-template-kit/framing';\nexport type { PhotoFraming, PhotoFramingInput } from '@dloizides/site-template-kit/framing';\n\n/**\n * An RN `transform` array. Percent translates are relative to the element\n * itself on RN-web, matching the landing page's CSS `translate(x%, y%) scale(s)`.\n * Typed locally so this module stays free of a react-native import.\n */\nexport type PhotoFramingTransform = [\n { translateX: `${number}%` },\n { translateY: `${number}%` },\n { scale: number },\n];\n\nexport const framingToTransform = (framing: PhotoFraming): PhotoFramingTransform => [\n { translateX: `${framing.x}%` },\n { translateY: `${framing.y}%` },\n { scale: framing.scale },\n];\n","/**\n * The seven controls of the framing editor. An `as const` object rather than a\n * TS `const enum`: a const enum shipped in a .d.ts cannot be read by consumers\n * compiled with `isolatedModules` (every Expo / Babel portal).\n */\nexport const FramingAction = {\n ZoomIn: 'zoomIn',\n ZoomOut: 'zoomOut',\n MoveUp: 'moveUp',\n MoveDown: 'moveDown',\n MoveLeft: 'moveLeft',\n MoveRight: 'moveRight',\n Reset: 'reset',\n} as const;\n\nexport type FramingAction = (typeof FramingAction)[keyof typeof FramingAction];\n","import { FramingAction } from './FramingAction';\nimport type { PhotoFraming, PhotoFramingInput } from './photoFraming';\nimport { DEFAULT_PHOTO_FRAMING, clampFraming } from './photoFraming';\n\n/** How far one press moves or zooms. */\nexport interface PhotoFramingSteps {\n /** Nudge per press, % of the card. */\n offsetPct: number;\n /** Zoom per press, as a scale delta. */\n scale: number;\n}\n\nexport const PHOTO_FRAMING_STEPS: PhotoFramingSteps = Object.freeze({ offsetPct: 5, scale: 0.1 });\n\n/** Three decimals, the same precision the landing page writes into its CSS. */\nconst PRECISION = 1000;\nconst round = (n: number): number => Math.round(n * PRECISION) / PRECISION;\n\ntype MoveAction = Exclude<FramingAction, typeof FramingAction.Reset>;\n\n/** [x, y, scale] direction of each non-reset action. */\nconst DIRECTIONS: Record<MoveAction, readonly [number, number, number]> = {\n [FramingAction.ZoomIn]: [0, 0, 1],\n [FramingAction.ZoomOut]: [0, 0, -1],\n [FramingAction.MoveUp]: [0, -1, 0],\n [FramingAction.MoveDown]: [0, 1, 0],\n [FramingAction.MoveLeft]: [-1, 0, 0],\n [FramingAction.MoveRight]: [1, 0, 0],\n};\n\n/**\n * Applies one control press. The result is always clamped and rounded, so ten\n * presses of +0.1 land on exactly 2, not 1.9999999999999998.\n */\nexport function stepFraming(\n current: PhotoFramingInput | null | undefined,\n action: FramingAction,\n steps: PhotoFramingSteps = PHOTO_FRAMING_STEPS,\n): PhotoFraming {\n if (action === FramingAction.Reset) return DEFAULT_PHOTO_FRAMING;\n const base = clampFraming(current);\n const [dx, dy, ds] = DIRECTIONS[action];\n return clampFraming({\n x: round(base.x + dx * steps.offsetPct),\n y: round(base.y + dy * steps.offsetPct),\n scale: round(base.scale + ds * steps.scale),\n });\n}\n\n/** False when the press would change nothing (at a bound, or reset on the default). */\nexport function canStepFraming(\n current: PhotoFramingInput | null | undefined,\n action: FramingAction,\n steps: PhotoFramingSteps = PHOTO_FRAMING_STEPS,\n): boolean {\n const base = clampFraming(current);\n const next = stepFraming(base, action, steps);\n return next.x !== base.x || next.y !== base.y || next.scale !== base.scale;\n}\n","/**\n * The four states of an image upload slot (KEFI-PEOPLE-1 design board \"upload\n * slot\"). An `as const` object, not a `const enum`, so Babel / isolatedModules\n * consumers can read it from the published .d.ts.\n */\nexport const ImagePickerStatus = {\n Idle: 'idle',\n Uploading: 'uploading',\n Error: 'error',\n Done: 'done',\n} as const;\n\nexport type ImagePickerStatus = (typeof ImagePickerStatus)[keyof typeof ImagePickerStatus];\n","import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { ImagePickerStatus } from './ImagePickerStatus';\n\nexport const DEFAULT_IMAGE_ACCEPT = 'image/*';\n\n/**\n * Opens the browser's file dialog for ONE file. Resolves `null` on cancel, and\n * where there is no DOM (native) — a native consumer passes its own `pickFile`.\n */\nexport function pickWebImage(accept: string = DEFAULT_IMAGE_ACCEPT): Promise<File | null> {\n if (typeof document === 'undefined') return Promise.resolve(null);\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = accept;\n input.addEventListener('change', () => resolve(input.files?.[0] ?? null), { once: true });\n input.addEventListener('cancel', () => resolve(null), { once: true });\n input.click();\n });\n}\n\nexport interface UseImageUploadOptions<TFile, TResult = void> {\n /** Consumer-owned transport. Resolve = stored; reject = error state. No network code lives here. */\n upload: (file: TFile) => Promise<TResult>;\n /** Given what `upload` resolved with (e.g. the stored URL), once the slot is `done`. */\n onUploaded?: (result: TResult) => void;\n /** Picks one file; `null` means the user cancelled. */\n pickFile: () => Promise<TFile | null>;\n /** Start in `done` when the slot already holds an image. */\n hasImage?: boolean;\n /** Told about a failed pick or upload (logging / toasts). */\n onError?: (error: unknown) => void;\n}\n\nexport interface ImageUploadState {\n status: ImagePickerStatus;\n /** Pick then upload. A second call while one is in flight is ignored. */\n pick: () => Promise<void>;\n}\n\n/**\n * The upload slot's state machine: idle -> uploading -> done | error, and from\n * done or error back to uploading on the next pick. A cancelled pick leaves the\n * state as it was, so cancelling \"Replace\" does not wipe a finished upload.\n */\nexport function useImageUpload<TFile, TResult = void>({\n upload,\n pickFile,\n hasImage = false,\n onError,\n onUploaded,\n}: UseImageUploadOptions<TFile, TResult>): ImageUploadState {\n const [status, setStatus] = useState<ImagePickerStatus>(\n hasImage ? ImagePickerStatus.Done : ImagePickerStatus.Idle,\n );\n const busy = useRef(false);\n const mounted = useRef(true);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const settle = useCallback((next: ImagePickerStatus): void => {\n if (mounted.current) setStatus(next);\n }, []);\n\n const pick = useCallback(async (): Promise<void> => {\n if (busy.current) return;\n busy.current = true;\n // Boxed so a falsy / void result still counts as uploaded.\n let uploaded: { result: TResult } | null = null;\n try {\n const file = await pickFile();\n if (file === null) return;\n settle(ImagePickerStatus.Uploading);\n uploaded = { result: await upload(file) };\n settle(ImagePickerStatus.Done);\n } catch (error) {\n settle(ImagePickerStatus.Error);\n onError?.(error);\n } finally {\n busy.current = false;\n }\n // Outside the try: a throwing consumer callback must not repaint a stored upload as failed.\n if (uploaded !== null) onUploaded?.(uploaded.result);\n }, [pickFile, upload, onError, onUploaded, settle]);\n\n return { status, pick };\n}\n","/**\r\n * ImagePickerButton — one upload slot: pick an image, hand it to the consumer's\r\n * `upload` function, show idle / uploading / error / done.\r\n *\r\n * No network code and no copy of its own: the transport is a prop and every\r\n * string arrives through `labels`, so each portal passes its FM() keys.\r\n */\r\nimport React, { useCallback, useMemo } from 'react';\r\n\r\nimport { StyleSheet, Text, View } from 'react-native';\r\n\r\nimport { Button } from '@dloizides/ui-buttons';\r\nimport type { ButtonVariant } from '@dloizides/ui-buttons';\r\nimport { useUi } from '@dloizides/ui-feedback';\r\n\r\nimport { ImagePickerStatus } from './ImagePickerStatus';\r\nimport { DEFAULT_IMAGE_ACCEPT, pickWebImage, useImageUpload } from './useImageUpload';\r\n\r\nexport interface ImagePickerLabels {\r\n /** Button, idle: \"Add photo\". */\r\n pick: string;\r\n /** Button, uploading: \"Uploading...\". */\r\n uploading: string;\r\n /** Button, done: \"Replace photo\". */\r\n replace: string;\r\n /** Button, error: \"Try again\". */\r\n retry: string;\r\n /** Status line after a successful upload: \"Photo saved\". */\r\n done: string;\r\n /** Status line after a failure: \"Upload failed\". */\r\n error: string;\r\n /** Accessibility hint for the button: \"Opens a file picker to choose an image\". */\r\n hint: string;\r\n}\r\n\r\nexport interface ImagePickerButtonProps<TFile = File, TResult = void> {\r\n /** Consumer transport; whatever it resolves with (e.g. the stored URL) goes to `onUploaded`. */\r\n upload: (file: TFile) => Promise<TResult>;\r\n /** Called with `upload`'s result once the slot is `done`. */\r\n onUploaded?: (result: TResult) => void;\r\n labels: ImagePickerLabels;\r\n testID: string;\r\n /** Defaults to the browser file dialog. Native consumers pass their own picker. */\r\n pickFile?: () => Promise<TFile | null>;\r\n /** `accept` for the default web picker. Default `image/*`. */\r\n accept?: string;\r\n /** Start in the `done` state (the slot already has an image). */\r\n hasImage?: boolean;\r\n onError?: (error: unknown) => void;\r\n disabled?: boolean;\r\n}\r\n\r\nexport interface PickerView {\r\n buttonLabel: string;\r\n variant: ButtonVariant;\r\n statusText: string | null;\r\n /** Paint the status line in the theme's error colour. */\r\n isError: boolean;\r\n}\r\n\r\n/** Pure status -> copy/variant mapping (unit-tested; the component only paints it). */\r\nexport function resolvePickerView(status: ImagePickerStatus, labels: ImagePickerLabels): PickerView {\r\n switch (status) {\r\n case ImagePickerStatus.Uploading:\r\n return { buttonLabel: labels.uploading, variant: 'outline', statusText: null, isError: false };\r\n case ImagePickerStatus.Error:\r\n return { buttonLabel: labels.retry, variant: 'danger', statusText: labels.error, isError: true };\r\n case ImagePickerStatus.Done:\r\n return { buttonLabel: labels.replace, variant: 'outline', statusText: labels.done, isError: false };\r\n default:\r\n return { buttonLabel: labels.pick, variant: 'primary', statusText: null, isError: false };\r\n }\r\n}\r\n\r\nconst STATUS_GAP = 6;\r\nconst STATUS_FONT_SIZE = 13;\r\n\r\nconst styles = StyleSheet.create({\r\n root: { alignItems: 'flex-start', gap: STATUS_GAP },\r\n status: { fontSize: STATUS_FONT_SIZE },\r\n});\r\n\r\nexport function ImagePickerButton<TFile = File, TResult = void>({\r\n upload,\r\n onUploaded,\r\n labels,\r\n testID,\r\n pickFile,\r\n accept = DEFAULT_IMAGE_ACCEPT,\r\n hasImage,\r\n onError,\r\n disabled = false,\r\n}: ImagePickerButtonProps<TFile, TResult>): React.ReactElement {\r\n const { theme } = useUi();\r\n // The default picker yields a DOM File; a consumer with another TFile supplies `pickFile`.\r\n const webPick = useCallback(() => pickWebImage(accept) as Promise<TFile | null>, [accept]);\r\n const { status, pick } = useImageUpload<TFile, TResult>({\r\n upload,\r\n pickFile: pickFile ?? webPick,\r\n hasImage,\r\n onError,\r\n onUploaded,\r\n });\r\n const view = useMemo(() => resolvePickerView(status, labels), [status, labels]);\r\n const isUploading = status === ImagePickerStatus.Uploading;\r\n const statusColor = view.isError ? theme.semantic.error['500'] : theme.colors.textSecondary;\r\n\r\n return (\r\n <View style={styles.root} testID={testID}>\r\n <Button\r\n accessibilityHint={labels.hint}\r\n accessibilityLabel={view.buttonLabel}\r\n autoLoading={false}\r\n disabled={disabled || isUploading}\r\n label={view.buttonLabel}\r\n loading={isUploading}\r\n testID={`${testID}-button`}\r\n variant={view.variant}\r\n onPress={pick}\r\n />\r\n {view.statusText !== null ? (\r\n <Text\r\n accessibilityLiveRegion=\"polite\"\r\n style={[styles.status, { color: statusColor }]}\r\n testID={`${testID}-status`}\r\n >\r\n {view.statusText}\r\n </Text>\r\n ) : null}\r\n </View>\r\n );\r\n}\r\n","/**\n * The editor's controls: a zoom stepper (minus, readout, plus) and a 3x3\n * position d-pad with reset in the middle. Every control is a 44px ui-buttons\n * IconButton, disabled when the press would change nothing (at a bound).\n */\nimport React from 'react';\n\nimport { StyleSheet, Text, View } from 'react-native';\n\nimport { IconButton } from '@dloizides/ui-buttons';\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { FramingAction } from '../framing/FramingAction';\nimport type { PhotoFraming } from '../framing/photoFraming';\nimport type { PhotoFramingSteps } from '../framing/stepFraming';\nimport { canStepFraming } from '../framing/stepFraming';\nimport type { PhotoFramingLabels } from './types';\n\n/** WCAG AA touch target; IconButton `md` is this size, the d-pad spacers match it. */\nconst TARGET = 44;\nconst GAP = 8;\nconst SECTION_GAP = 16;\nconst HEADING_SIZE = 13;\nconst READOUT_MIN_WIDTH = 56;\nconst READOUT_SIZE = 15;\nconst PERCENT = 100;\n\nconst DEFAULT_GLYPHS: Record<FramingAction, string> = {\n [FramingAction.ZoomIn]: '+',\n [FramingAction.ZoomOut]: '−',\n [FramingAction.MoveUp]: '↑',\n [FramingAction.MoveDown]: '↓',\n [FramingAction.MoveLeft]: '←',\n [FramingAction.MoveRight]: '→',\n [FramingAction.Reset]: '↺',\n};\n\nconst styles = StyleSheet.create({\n root: { gap: SECTION_GAP },\n section: { gap: GAP },\n heading: { fontSize: HEADING_SIZE, fontWeight: '600' },\n row: { flexDirection: 'row', alignItems: 'center', gap: GAP },\n readout: { minWidth: READOUT_MIN_WIDTH, textAlign: 'center', fontSize: READOUT_SIZE },\n spacer: { width: TARGET, height: TARGET },\n});\n\ninterface FramingControlsProps {\n framing: PhotoFraming;\n steps?: PhotoFramingSteps;\n labels: PhotoFramingLabels;\n onAction: (action: FramingAction) => void;\n renderGlyph?: (action: FramingAction, color: string, size: number) => React.ReactNode;\n disabled: boolean;\n testID: string;\n}\n\nexport const FramingControls = ({\n framing,\n steps,\n labels,\n onAction,\n renderGlyph,\n disabled,\n testID,\n}: FramingControlsProps): React.ReactElement => {\n const { theme } = useUi();\n\n const control = (action: FramingAction): React.ReactElement => (\n <IconButton\n accessibilityHint={labels.actions[action].hint}\n actionLabel={labels.actions[action].label}\n autoLoading={false}\n disabled={disabled || !canStepFraming(framing, action, steps)}\n renderIcon={(color, size) =>\n renderGlyph ? (\n renderGlyph(action, color, size)\n ) : (\n <Text accessible={false} style={{ color, fontSize: size }}>\n {DEFAULT_GLYPHS[action]}\n </Text>\n )\n }\n showLabel={false}\n testID={`${testID}-${action}`}\n onPress={() => onAction(action)}\n />\n );\n const heading = { color: theme.colors.textSecondary };\n\n return (\n <View style={styles.root}>\n <View style={styles.section}>\n <Text accessibilityRole=\"header\" style={[styles.heading, heading]}>\n {labels.size}\n </Text>\n <View style={styles.row}>\n {control(FramingAction.ZoomOut)}\n <Text style={[styles.readout, { color: theme.colors.text }]} testID={`${testID}-scale`}>\n {labels.scaleValue(Math.round(framing.scale * PERCENT))}\n </Text>\n {control(FramingAction.ZoomIn)}\n </View>\n </View>\n <View style={styles.section}>\n <Text accessibilityRole=\"header\" style={[styles.heading, heading]}>\n {labels.position}\n </Text>\n <View style={styles.row}>\n <View style={styles.spacer} />\n {control(FramingAction.MoveUp)}\n <View style={styles.spacer} />\n </View>\n <View style={styles.row}>\n {control(FramingAction.MoveLeft)}\n {control(FramingAction.Reset)}\n {control(FramingAction.MoveRight)}\n </View>\n <View style={styles.row}>\n <View style={styles.spacer} />\n {control(FramingAction.MoveDown)}\n <View style={styles.spacer} />\n </View>\n </View>\n </View>\n );\n};\n","/**\n * Where the photo sits inside the preview frame. `Bottom` matches a kefi-landings\n * cut-out (`.amb-photo img`: 4:5, `object-fit: contain`, `object-position: center\n * bottom`) — feet on the floor of the card. An `as const` object rather than a TS\n * `const enum` for the same isolatedModules reason as FramingAction.\n */\nexport const PhotoAnchor = {\n Center: 'center',\n Bottom: 'bottom',\n} as const;\n\nexport type PhotoAnchor = (typeof PhotoAnchor)[keyof typeof PhotoAnchor];\n\nexport interface PhotoAnchorLayout {\n /** Applied to the editor's preview column: where the consumer's card sits vertically. */\n justifyContent: 'center' | 'flex-end';\n /** CSS `object-position` the consumer spreads onto its `<img>` / RN-web image. */\n objectPosition: 'center center' | 'center bottom';\n}\n\n/** Pure anchor -> layout mapping (unit-tested; the editor only applies it). */\nexport function resolvePhotoAnchor(anchor: PhotoAnchor): PhotoAnchorLayout {\n return anchor === PhotoAnchor.Bottom\n ? { justifyContent: 'flex-end', objectPosition: 'center bottom' }\n : { justifyContent: 'center', objectPosition: 'center center' };\n}\n","/**\n * PhotoFramingEditor — the KEFI-PEOPLE-1 \"A2\" framing screen: a live preview of\n * the consumer's own card (render prop) beside a zoom stepper and a position\n * d-pad. The value is `{x, y, scale}`, clamped to PHOTO_FRAMING_BOUNDS, the same\n * rules the public landing page applies — the preview is what ships.\n *\n * Narrow first: preview stacked over the controls; from ui-layout's\n * LAYOUT_COLLAPSE_BREAKPOINT up they sit side by side.\n */\nimport React, { useCallback, useMemo } from 'react';\n\nimport { StyleSheet, View, useWindowDimensions } from 'react-native';\n\nimport { LAYOUT_COLLAPSE_BREAKPOINT } from '@dloizides/ui-layout';\n\nimport type { FramingAction } from '../framing/FramingAction';\nimport { clampFraming, framingToTransform } from '../framing/photoFraming';\nimport { stepFraming } from '../framing/stepFraming';\nimport { FramingControls } from './FramingControls';\nimport { PhotoAnchor, resolvePhotoAnchor } from './PhotoAnchor';\nimport type { PhotoFramingEditorProps } from './types';\n\nconst GAP = 16;\n\nconst styles = StyleSheet.create({\n root: { flexDirection: 'column', gap: GAP, alignItems: 'stretch' },\n rootWide: { flexDirection: 'row', alignItems: 'flex-start' },\n preview: { alignItems: 'center', overflow: 'hidden' },\n previewWide: { flexShrink: 1 },\n});\n\nexport const PhotoFramingEditor = ({\n value,\n onChange,\n renderPreview,\n labels,\n testID,\n steps,\n renderGlyph,\n disabled = false,\n anchor = PhotoAnchor.Center,\n}: PhotoFramingEditorProps): React.ReactElement => {\n const { width } = useWindowDimensions();\n const wide = width >= LAYOUT_COLLAPSE_BREAKPOINT;\n const framing = useMemo(() => clampFraming(value), [value]);\n const transform = useMemo(() => framingToTransform(framing), [framing]);\n const anchorLayout = useMemo(() => resolvePhotoAnchor(anchor), [anchor]);\n\n const onAction = useCallback(\n (action: FramingAction): void => onChange(stepFraming(framing, action, steps)),\n [framing, onChange, steps],\n );\n\n return (\n <View style={[styles.root, wide ? styles.rootWide : null]} testID={testID}>\n <View\n style={[styles.preview, { justifyContent: anchorLayout.justifyContent }, wide ? styles.previewWide : null]}\n testID={`${testID}-preview`}\n >\n {renderPreview({ framing, transform, anchor, objectPosition: anchorLayout.objectPosition })}\n </View>\n <FramingControls\n disabled={disabled}\n framing={framing}\n labels={labels}\n renderGlyph={renderGlyph}\n steps={steps}\n testID={testID}\n onAction={onAction}\n />\n </View>\n );\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dloizides/ui-media",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Themable React Native (RN-web) media controls: ImagePickerButton (idle/uploading/error/done around a consumer-supplied upload function) and PhotoFramingEditor (live frame preview, size stepper, position d-pad). The framing bounds and clamp are also exported from the framework-free '@dloizides/ui-media/framing' subpath. Part of the dloizides.com shared UI kit.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|