@dloizides/ui-media 0.1.0 → 0.2.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 +7 -0
- package/dist/framing.d.mts +12 -40
- package/dist/framing.d.ts +12 -40
- package/dist/framing.js +22 -29
- package/dist/framing.js.map +1 -1
- package/dist/framing.mjs +4 -22
- package/dist/framing.mjs.map +1 -1
- package/dist/index.d.mts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +27 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- KEFI-PEOPLE-1 T12 "Organizer people editor: shared photo framing": `PHOTO_FRAMING_BOUNDS`,
|
|
6
|
+
`DEFAULT_PHOTO_FRAMING`, `clampFraming`, `isDefaultFraming` and the framing types are now
|
|
7
|
+
re-exported from `@dloizides/site-template-kit/framing` (new dependency, no new peers) — the
|
|
8
|
+
same module the kefi-landings renderer uses, so the two can no longer drift.
|
|
9
|
+
|
|
3
10
|
## 0.1.0
|
|
4
11
|
|
|
5
12
|
- Initial release: ImagePickerButton, useImageUpload, PhotoFramingEditor, PHOTO_FRAMING_BOUNDS + clampFraming (parity with kefi-landings photoTransform), framework-free ./framing subpath.
|
package/dist/framing.d.mts
CHANGED
|
@@ -1,47 +1,19 @@
|
|
|
1
|
+
import { PhotoFraming, PhotoFramingInput } from '@dloizides/site-template-kit/framing';
|
|
2
|
+
export { DEFAULT_PHOTO_FRAMING, PHOTO_FRAMING_BOUNDS, PhotoFraming, PhotoFramingInput, clampFraming, isDefaultFraming } from '@dloizides/site-template-kit/framing';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Photo framing: the `{x, y, scale}` an organiser sets on a performer card.
|
|
3
6
|
*
|
|
4
|
-
* The bounds
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The bounds, default, clamp and identity check come from
|
|
8
|
+
* `@dloizides/site-template-kit/framing` — the same module the kefi-landings
|
|
9
|
+
* renderer builds its `--photo-tf` CSS from (KEFI-PEOPLE-1 T12 "Organizer people
|
|
10
|
+
* editor: shared photo framing"). One copy, so the editor can never offer a value
|
|
11
|
+
* the landing page would silently clamp away.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
* subpath.
|
|
12
|
-
*/
|
|
13
|
-
/** A framing value after clamping: every field finite and inside the bounds. */
|
|
14
|
-
interface PhotoFraming {
|
|
15
|
-
/** Horizontal nudge, % of the card. Negative = left. */
|
|
16
|
-
readonly x: number;
|
|
17
|
-
/** Vertical nudge, % of the card. Negative = up. */
|
|
18
|
-
readonly y: number;
|
|
19
|
-
/** Zoom factor. 1 = the photo's natural cover fit. */
|
|
20
|
-
readonly scale: number;
|
|
21
|
-
}
|
|
22
|
-
/** What config / an API may hold: any field missing, null or non-finite. */
|
|
23
|
-
interface PhotoFramingInput {
|
|
24
|
-
x?: number | null;
|
|
25
|
-
y?: number | null;
|
|
26
|
-
scale?: number | null;
|
|
27
|
-
}
|
|
28
|
-
declare const PHOTO_FRAMING_BOUNDS: Readonly<{
|
|
29
|
-
/** Widest nudge, as a percentage of the card, in either direction. */
|
|
30
|
-
maxOffsetPct: 60;
|
|
31
|
-
/** Below 1 the photo shrinks inside an already-roomy frame. */
|
|
32
|
-
minScale: 0.5;
|
|
33
|
-
/** Far above 2 the subject's head leaves the card entirely. */
|
|
34
|
-
maxScale: 3;
|
|
35
|
-
}>;
|
|
36
|
-
declare const DEFAULT_PHOTO_FRAMING: PhotoFraming;
|
|
37
|
-
/**
|
|
38
|
-
* Normalises any stored framing: non-finite fields fall back to the default,
|
|
39
|
-
* the rest are clamped to {@link PHOTO_FRAMING_BOUNDS}. Same rules as the
|
|
40
|
-
* landing page's `photoTransform`, so what the editor previews is what ships.
|
|
13
|
+
* That subpath imports nothing (no ajv, no JSON Schema), so this file stays
|
|
14
|
+
* framework-free: it backs the `@dloizides/ui-media/framing` subpath.
|
|
41
15
|
*/
|
|
42
|
-
|
|
43
|
-
/** True when the framing is the identity (the landing page emits no transform). */
|
|
44
|
-
declare const isDefaultFraming: (framing: PhotoFraming) => boolean;
|
|
16
|
+
|
|
45
17
|
/**
|
|
46
18
|
* An RN `transform` array. Percent translates are relative to the element
|
|
47
19
|
* itself on RN-web, matching the landing page's CSS `translate(x%, y%) scale(s)`.
|
|
@@ -92,4 +64,4 @@ declare function stepFraming(current: PhotoFramingInput | null | undefined, acti
|
|
|
92
64
|
/** False when the press would change nothing (at a bound, or reset on the default). */
|
|
93
65
|
declare function canStepFraming(current: PhotoFramingInput | null | undefined, action: FramingAction, steps?: PhotoFramingSteps): boolean;
|
|
94
66
|
|
|
95
|
-
export {
|
|
67
|
+
export { FramingAction, PHOTO_FRAMING_STEPS, type PhotoFramingSteps, type PhotoFramingTransform, canStepFraming, framingToTransform, stepFraming };
|
package/dist/framing.d.ts
CHANGED
|
@@ -1,47 +1,19 @@
|
|
|
1
|
+
import { PhotoFraming, PhotoFramingInput } from '@dloizides/site-template-kit/framing';
|
|
2
|
+
export { DEFAULT_PHOTO_FRAMING, PHOTO_FRAMING_BOUNDS, PhotoFraming, PhotoFramingInput, clampFraming, isDefaultFraming } from '@dloizides/site-template-kit/framing';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Photo framing: the `{x, y, scale}` an organiser sets on a performer card.
|
|
3
6
|
*
|
|
4
|
-
* The bounds
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The bounds, default, clamp and identity check come from
|
|
8
|
+
* `@dloizides/site-template-kit/framing` — the same module the kefi-landings
|
|
9
|
+
* renderer builds its `--photo-tf` CSS from (KEFI-PEOPLE-1 T12 "Organizer people
|
|
10
|
+
* editor: shared photo framing"). One copy, so the editor can never offer a value
|
|
11
|
+
* the landing page would silently clamp away.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
* subpath.
|
|
12
|
-
*/
|
|
13
|
-
/** A framing value after clamping: every field finite and inside the bounds. */
|
|
14
|
-
interface PhotoFraming {
|
|
15
|
-
/** Horizontal nudge, % of the card. Negative = left. */
|
|
16
|
-
readonly x: number;
|
|
17
|
-
/** Vertical nudge, % of the card. Negative = up. */
|
|
18
|
-
readonly y: number;
|
|
19
|
-
/** Zoom factor. 1 = the photo's natural cover fit. */
|
|
20
|
-
readonly scale: number;
|
|
21
|
-
}
|
|
22
|
-
/** What config / an API may hold: any field missing, null or non-finite. */
|
|
23
|
-
interface PhotoFramingInput {
|
|
24
|
-
x?: number | null;
|
|
25
|
-
y?: number | null;
|
|
26
|
-
scale?: number | null;
|
|
27
|
-
}
|
|
28
|
-
declare const PHOTO_FRAMING_BOUNDS: Readonly<{
|
|
29
|
-
/** Widest nudge, as a percentage of the card, in either direction. */
|
|
30
|
-
maxOffsetPct: 60;
|
|
31
|
-
/** Below 1 the photo shrinks inside an already-roomy frame. */
|
|
32
|
-
minScale: 0.5;
|
|
33
|
-
/** Far above 2 the subject's head leaves the card entirely. */
|
|
34
|
-
maxScale: 3;
|
|
35
|
-
}>;
|
|
36
|
-
declare const DEFAULT_PHOTO_FRAMING: PhotoFraming;
|
|
37
|
-
/**
|
|
38
|
-
* Normalises any stored framing: non-finite fields fall back to the default,
|
|
39
|
-
* the rest are clamped to {@link PHOTO_FRAMING_BOUNDS}. Same rules as the
|
|
40
|
-
* landing page's `photoTransform`, so what the editor previews is what ships.
|
|
13
|
+
* That subpath imports nothing (no ajv, no JSON Schema), so this file stays
|
|
14
|
+
* framework-free: it backs the `@dloizides/ui-media/framing` subpath.
|
|
41
15
|
*/
|
|
42
|
-
|
|
43
|
-
/** True when the framing is the identity (the landing page emits no transform). */
|
|
44
|
-
declare const isDefaultFraming: (framing: PhotoFraming) => boolean;
|
|
16
|
+
|
|
45
17
|
/**
|
|
46
18
|
* An RN `transform` array. Percent translates are relative to the element
|
|
47
19
|
* itself on RN-web, matching the landing page's CSS `translate(x%, y%) scale(s)`.
|
|
@@ -92,4 +64,4 @@ declare function stepFraming(current: PhotoFramingInput | null | undefined, acti
|
|
|
92
64
|
/** False when the press would change nothing (at a bound, or reset on the default). */
|
|
93
65
|
declare function canStepFraming(current: PhotoFramingInput | null | undefined, action: FramingAction, steps?: PhotoFramingSteps): boolean;
|
|
94
66
|
|
|
95
|
-
export {
|
|
67
|
+
export { FramingAction, PHOTO_FRAMING_STEPS, type PhotoFramingSteps, type PhotoFramingTransform, canStepFraming, framingToTransform, stepFraming };
|
package/dist/framing.js
CHANGED
|
@@ -1,27 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var framing = require('@dloizides/site-template-kit/framing');
|
|
4
|
+
|
|
3
5
|
// src/framing/photoFraming.ts
|
|
4
|
-
var PHOTO_FRAMING_BOUNDS = Object.freeze({
|
|
5
|
-
/** Widest nudge, as a percentage of the card, in either direction. */
|
|
6
|
-
maxOffsetPct: 60,
|
|
7
|
-
/** Below 1 the photo shrinks inside an already-roomy frame. */
|
|
8
|
-
minScale: 0.5,
|
|
9
|
-
/** Far above 2 the subject's head leaves the card entirely. */
|
|
10
|
-
maxScale: 3
|
|
11
|
-
});
|
|
12
|
-
var DEFAULT_PHOTO_FRAMING = Object.freeze({ x: 0, y: 0, scale: 1 });
|
|
13
|
-
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
14
|
-
var finiteOr = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
15
|
-
function clampFraming(input) {
|
|
16
|
-
if (!input) return DEFAULT_PHOTO_FRAMING;
|
|
17
|
-
const { maxOffsetPct, minScale, maxScale } = PHOTO_FRAMING_BOUNDS;
|
|
18
|
-
return {
|
|
19
|
-
x: clamp(finiteOr(input.x, DEFAULT_PHOTO_FRAMING.x), -maxOffsetPct, maxOffsetPct),
|
|
20
|
-
y: clamp(finiteOr(input.y, DEFAULT_PHOTO_FRAMING.y), -maxOffsetPct, maxOffsetPct),
|
|
21
|
-
scale: clamp(finiteOr(input.scale, DEFAULT_PHOTO_FRAMING.scale), minScale, maxScale)
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
var isDefaultFraming = (framing) => framing.x === DEFAULT_PHOTO_FRAMING.x && framing.y === DEFAULT_PHOTO_FRAMING.y && framing.scale === DEFAULT_PHOTO_FRAMING.scale;
|
|
25
6
|
var framingToTransform = (framing) => [
|
|
26
7
|
{ translateX: `${framing.x}%` },
|
|
27
8
|
{ translateY: `${framing.y}%` },
|
|
@@ -52,29 +33,41 @@ var DIRECTIONS = {
|
|
|
52
33
|
[FramingAction.MoveRight]: [1, 0, 0]
|
|
53
34
|
};
|
|
54
35
|
function stepFraming(current, action, steps = PHOTO_FRAMING_STEPS) {
|
|
55
|
-
if (action === FramingAction.Reset) return DEFAULT_PHOTO_FRAMING;
|
|
56
|
-
const base = clampFraming(current);
|
|
36
|
+
if (action === FramingAction.Reset) return framing.DEFAULT_PHOTO_FRAMING;
|
|
37
|
+
const base = framing.clampFraming(current);
|
|
57
38
|
const [dx, dy, ds] = DIRECTIONS[action];
|
|
58
|
-
return clampFraming({
|
|
39
|
+
return framing.clampFraming({
|
|
59
40
|
x: round(base.x + dx * steps.offsetPct),
|
|
60
41
|
y: round(base.y + dy * steps.offsetPct),
|
|
61
42
|
scale: round(base.scale + ds * steps.scale)
|
|
62
43
|
});
|
|
63
44
|
}
|
|
64
45
|
function canStepFraming(current, action, steps = PHOTO_FRAMING_STEPS) {
|
|
65
|
-
const base = clampFraming(current);
|
|
46
|
+
const base = framing.clampFraming(current);
|
|
66
47
|
const next = stepFraming(base, action, steps);
|
|
67
48
|
return next.x !== base.x || next.y !== base.y || next.scale !== base.scale;
|
|
68
49
|
}
|
|
69
50
|
|
|
70
|
-
exports
|
|
51
|
+
Object.defineProperty(exports, "DEFAULT_PHOTO_FRAMING", {
|
|
52
|
+
enumerable: true,
|
|
53
|
+
get: function () { return framing.DEFAULT_PHOTO_FRAMING; }
|
|
54
|
+
});
|
|
55
|
+
Object.defineProperty(exports, "PHOTO_FRAMING_BOUNDS", {
|
|
56
|
+
enumerable: true,
|
|
57
|
+
get: function () { return framing.PHOTO_FRAMING_BOUNDS; }
|
|
58
|
+
});
|
|
59
|
+
Object.defineProperty(exports, "clampFraming", {
|
|
60
|
+
enumerable: true,
|
|
61
|
+
get: function () { return framing.clampFraming; }
|
|
62
|
+
});
|
|
63
|
+
Object.defineProperty(exports, "isDefaultFraming", {
|
|
64
|
+
enumerable: true,
|
|
65
|
+
get: function () { return framing.isDefaultFraming; }
|
|
66
|
+
});
|
|
71
67
|
exports.FramingAction = FramingAction;
|
|
72
|
-
exports.PHOTO_FRAMING_BOUNDS = PHOTO_FRAMING_BOUNDS;
|
|
73
68
|
exports.PHOTO_FRAMING_STEPS = PHOTO_FRAMING_STEPS;
|
|
74
69
|
exports.canStepFraming = canStepFraming;
|
|
75
|
-
exports.clampFraming = clampFraming;
|
|
76
70
|
exports.framingToTransform = framingToTransform;
|
|
77
|
-
exports.isDefaultFraming = isDefaultFraming;
|
|
78
71
|
exports.stepFraming = stepFraming;
|
|
79
72
|
//# sourceMappingURL=framing.js.map
|
|
80
73
|
//# sourceMappingURL=framing.js.map
|
package/dist/framing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/framing/photoFraming.ts","../src/framing/FramingAction.ts","../src/framing/stepFraming.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/framing/photoFraming.ts","../src/framing/FramingAction.ts","../src/framing/stepFraming.ts"],"names":["DEFAULT_PHOTO_FRAMING","clampFraming"],"mappings":";;;;;AAiCO,IAAM,kBAAA,GAAqB,CAAC,OAAA,KAAiD;AAAA,EAClF,EAAE,UAAA,EAAY,CAAA,EAAG,OAAA,CAAQ,CAAC,CAAA,CAAA,CAAA,EAAI;AAAA,EAC9B,EAAE,UAAA,EAAY,CAAA,EAAG,OAAA,CAAQ,CAAC,CAAA,CAAA,CAAA,EAAI;AAAA,EAC9B,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA;AACnB;;;AChCO,IAAM,aAAA,GAAgB;AAAA,EAC3B,MAAA,EAAQ,QAAA;AAAA,EACR,OAAA,EAAS,SAAA;AAAA,EACT,MAAA,EAAQ,QAAA;AAAA,EACR,QAAA,EAAU,UAAA;AAAA,EACV,QAAA,EAAU,UAAA;AAAA,EACV,SAAA,EAAW,WAAA;AAAA,EACX,KAAA,EAAO;AACT;;;ACDO,IAAM,mBAAA,GAAyC,OAAO,MAAA,CAAO,EAAE,WAAW,CAAA,EAAG,KAAA,EAAO,KAAK;AAGhG,IAAM,SAAA,GAAY,GAAA;AAClB,IAAM,QAAQ,CAAC,CAAA,KAAsB,KAAK,KAAA,CAAM,CAAA,GAAI,SAAS,CAAA,GAAI,SAAA;AAKjE,IAAM,UAAA,GAAoE;AAAA,EACxE,CAAC,aAAA,CAAc,MAAM,GAAG,CAAC,CAAA,EAAG,GAAG,CAAC,CAAA;AAAA,EAChC,CAAC,aAAA,CAAc,OAAO,GAAG,CAAC,CAAA,EAAG,GAAG,EAAE,CAAA;AAAA,EAClC,CAAC,aAAA,CAAc,MAAM,GAAG,CAAC,CAAA,EAAG,IAAI,CAAC,CAAA;AAAA,EACjC,CAAC,aAAA,CAAc,QAAQ,GAAG,CAAC,CAAA,EAAG,GAAG,CAAC,CAAA;AAAA,EAClC,CAAC,aAAA,CAAc,QAAQ,GAAG,CAAC,EAAA,EAAI,GAAG,CAAC,CAAA;AAAA,EACnC,CAAC,aAAA,CAAc,SAAS,GAAG,CAAC,CAAA,EAAG,GAAG,CAAC;AACrC,CAAA;AAMO,SAAS,WAAA,CACd,OAAA,EACA,MAAA,EACA,KAAA,GAA2B,mBAAA,EACb;AACd,EAAA,IAAI,MAAA,KAAW,aAAA,CAAc,KAAA,EAAO,OAAOA,6BAAA;AAC3C,EAAA,MAAM,IAAA,GAAOC,qBAAa,OAAO,CAAA;AACjC,EAAA,MAAM,CAAC,EAAA,EAAI,EAAA,EAAI,EAAE,CAAA,GAAI,WAAW,MAAM,CAAA;AACtC,EAAA,OAAOA,oBAAA,CAAa;AAAA,IAClB,GAAG,KAAA,CAAM,IAAA,CAAK,CAAA,GAAI,EAAA,GAAK,MAAM,SAAS,CAAA;AAAA,IACtC,GAAG,KAAA,CAAM,IAAA,CAAK,CAAA,GAAI,EAAA,GAAK,MAAM,SAAS,CAAA;AAAA,IACtC,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,GAAQ,EAAA,GAAK,MAAM,KAAK;AAAA,GAC3C,CAAA;AACH;AAGO,SAAS,cAAA,CACd,OAAA,EACA,MAAA,EACA,KAAA,GAA2B,mBAAA,EAClB;AACT,EAAA,MAAM,IAAA,GAAOA,qBAAa,OAAO,CAAA;AACjC,EAAA,MAAM,IAAA,GAAO,WAAA,CAAY,IAAA,EAAM,MAAA,EAAQ,KAAK,CAAA;AAC5C,EAAA,OAAO,IAAA,CAAK,CAAA,KAAM,IAAA,CAAK,CAAA,IAAK,IAAA,CAAK,MAAM,IAAA,CAAK,CAAA,IAAK,IAAA,CAAK,KAAA,KAAU,IAAA,CAAK,KAAA;AACvE","file":"framing.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"]}
|
package/dist/framing.mjs
CHANGED
|
@@ -1,25 +1,7 @@
|
|
|
1
|
+
import { DEFAULT_PHOTO_FRAMING, clampFraming } from '@dloizides/site-template-kit/framing';
|
|
2
|
+
export { DEFAULT_PHOTO_FRAMING, PHOTO_FRAMING_BOUNDS, clampFraming, isDefaultFraming } from '@dloizides/site-template-kit/framing';
|
|
3
|
+
|
|
1
4
|
// src/framing/photoFraming.ts
|
|
2
|
-
var PHOTO_FRAMING_BOUNDS = Object.freeze({
|
|
3
|
-
/** Widest nudge, as a percentage of the card, in either direction. */
|
|
4
|
-
maxOffsetPct: 60,
|
|
5
|
-
/** Below 1 the photo shrinks inside an already-roomy frame. */
|
|
6
|
-
minScale: 0.5,
|
|
7
|
-
/** Far above 2 the subject's head leaves the card entirely. */
|
|
8
|
-
maxScale: 3
|
|
9
|
-
});
|
|
10
|
-
var DEFAULT_PHOTO_FRAMING = Object.freeze({ x: 0, y: 0, scale: 1 });
|
|
11
|
-
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
12
|
-
var finiteOr = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
13
|
-
function clampFraming(input) {
|
|
14
|
-
if (!input) return DEFAULT_PHOTO_FRAMING;
|
|
15
|
-
const { maxOffsetPct, minScale, maxScale } = PHOTO_FRAMING_BOUNDS;
|
|
16
|
-
return {
|
|
17
|
-
x: clamp(finiteOr(input.x, DEFAULT_PHOTO_FRAMING.x), -maxOffsetPct, maxOffsetPct),
|
|
18
|
-
y: clamp(finiteOr(input.y, DEFAULT_PHOTO_FRAMING.y), -maxOffsetPct, maxOffsetPct),
|
|
19
|
-
scale: clamp(finiteOr(input.scale, DEFAULT_PHOTO_FRAMING.scale), minScale, maxScale)
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
var isDefaultFraming = (framing) => framing.x === DEFAULT_PHOTO_FRAMING.x && framing.y === DEFAULT_PHOTO_FRAMING.y && framing.scale === DEFAULT_PHOTO_FRAMING.scale;
|
|
23
5
|
var framingToTransform = (framing) => [
|
|
24
6
|
{ translateX: `${framing.x}%` },
|
|
25
7
|
{ translateY: `${framing.y}%` },
|
|
@@ -65,6 +47,6 @@ function canStepFraming(current, action, steps = PHOTO_FRAMING_STEPS) {
|
|
|
65
47
|
return next.x !== base.x || next.y !== base.y || next.scale !== base.scale;
|
|
66
48
|
}
|
|
67
49
|
|
|
68
|
-
export {
|
|
50
|
+
export { FramingAction, PHOTO_FRAMING_STEPS, canStepFraming, framingToTransform, stepFraming };
|
|
69
51
|
//# sourceMappingURL=framing.mjs.map
|
|
70
52
|
//# sourceMappingURL=framing.mjs.map
|
package/dist/framing.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/framing/photoFraming.ts","../src/framing/FramingAction.ts","../src/framing/stepFraming.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/framing/photoFraming.ts","../src/framing/FramingAction.ts","../src/framing/stepFraming.ts"],"names":[],"mappings":";;;;AAiCO,IAAM,kBAAA,GAAqB,CAAC,OAAA,KAAiD;AAAA,EAClF,EAAE,UAAA,EAAY,CAAA,EAAG,OAAA,CAAQ,CAAC,CAAA,CAAA,CAAA,EAAI;AAAA,EAC9B,EAAE,UAAA,EAAY,CAAA,EAAG,OAAA,CAAQ,CAAC,CAAA,CAAA,CAAA,EAAI;AAAA,EAC9B,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA;AACnB;;;AChCO,IAAM,aAAA,GAAgB;AAAA,EAC3B,MAAA,EAAQ,QAAA;AAAA,EACR,OAAA,EAAS,SAAA;AAAA,EACT,MAAA,EAAQ,QAAA;AAAA,EACR,QAAA,EAAU,UAAA;AAAA,EACV,QAAA,EAAU,UAAA;AAAA,EACV,SAAA,EAAW,WAAA;AAAA,EACX,KAAA,EAAO;AACT;;;ACDO,IAAM,mBAAA,GAAyC,OAAO,MAAA,CAAO,EAAE,WAAW,CAAA,EAAG,KAAA,EAAO,KAAK;AAGhG,IAAM,SAAA,GAAY,GAAA;AAClB,IAAM,QAAQ,CAAC,CAAA,KAAsB,KAAK,KAAA,CAAM,CAAA,GAAI,SAAS,CAAA,GAAI,SAAA;AAKjE,IAAM,UAAA,GAAoE;AAAA,EACxE,CAAC,aAAA,CAAc,MAAM,GAAG,CAAC,CAAA,EAAG,GAAG,CAAC,CAAA;AAAA,EAChC,CAAC,aAAA,CAAc,OAAO,GAAG,CAAC,CAAA,EAAG,GAAG,EAAE,CAAA;AAAA,EAClC,CAAC,aAAA,CAAc,MAAM,GAAG,CAAC,CAAA,EAAG,IAAI,CAAC,CAAA;AAAA,EACjC,CAAC,aAAA,CAAc,QAAQ,GAAG,CAAC,CAAA,EAAG,GAAG,CAAC,CAAA;AAAA,EAClC,CAAC,aAAA,CAAc,QAAQ,GAAG,CAAC,EAAA,EAAI,GAAG,CAAC,CAAA;AAAA,EACnC,CAAC,aAAA,CAAc,SAAS,GAAG,CAAC,CAAA,EAAG,GAAG,CAAC;AACrC,CAAA;AAMO,SAAS,WAAA,CACd,OAAA,EACA,MAAA,EACA,KAAA,GAA2B,mBAAA,EACb;AACd,EAAA,IAAI,MAAA,KAAW,aAAA,CAAc,KAAA,EAAO,OAAO,qBAAA;AAC3C,EAAA,MAAM,IAAA,GAAO,aAAa,OAAO,CAAA;AACjC,EAAA,MAAM,CAAC,EAAA,EAAI,EAAA,EAAI,EAAE,CAAA,GAAI,WAAW,MAAM,CAAA;AACtC,EAAA,OAAO,YAAA,CAAa;AAAA,IAClB,GAAG,KAAA,CAAM,IAAA,CAAK,CAAA,GAAI,EAAA,GAAK,MAAM,SAAS,CAAA;AAAA,IACtC,GAAG,KAAA,CAAM,IAAA,CAAK,CAAA,GAAI,EAAA,GAAK,MAAM,SAAS,CAAA;AAAA,IACtC,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,GAAQ,EAAA,GAAK,MAAM,KAAK;AAAA,GAC3C,CAAA;AACH;AAGO,SAAS,cAAA,CACd,OAAA,EACA,MAAA,EACA,KAAA,GAA2B,mBAAA,EAClB;AACT,EAAA,MAAM,IAAA,GAAO,aAAa,OAAO,CAAA;AACjC,EAAA,MAAM,IAAA,GAAO,WAAA,CAAY,IAAA,EAAM,MAAA,EAAQ,KAAK,CAAA;AAC5C,EAAA,OAAO,IAAA,CAAK,CAAA,KAAM,IAAA,CAAK,CAAA,IAAK,IAAA,CAAK,MAAM,IAAA,CAAK,CAAA,IAAK,IAAA,CAAK,KAAA,KAAU,IAAA,CAAK,KAAA;AACvE","file":"framing.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"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { PhotoFramingTransform, FramingAction, PhotoFramingSteps } from './framing.mjs';
|
|
2
|
+
export { PHOTO_FRAMING_STEPS, canStepFraming, framingToTransform, stepFraming } from './framing.mjs';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { ButtonVariant } from '@dloizides/ui-buttons';
|
|
5
|
+
import { PhotoFramingInput, PhotoFraming } from '@dloizides/site-template-kit/framing';
|
|
6
|
+
export { DEFAULT_PHOTO_FRAMING, PHOTO_FRAMING_BOUNDS, PhotoFraming, PhotoFramingInput, clampFraming, isDefaultFraming } from '@dloizides/site-template-kit/framing';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* The four states of an image upload slot (KEFI-PEOPLE-1 design board "upload
|
|
@@ -140,4 +142,4 @@ interface PhotoFramingEditorProps {
|
|
|
140
142
|
|
|
141
143
|
declare const PhotoFramingEditor: ({ value, onChange, renderPreview, labels, testID, steps, renderGlyph, disabled, }: PhotoFramingEditorProps) => React.ReactElement;
|
|
142
144
|
|
|
143
|
-
export { DEFAULT_IMAGE_ACCEPT, FramingAction, type FramingActionLabel, ImagePickerButton, type ImagePickerButtonProps, type ImagePickerLabels, ImagePickerStatus, type ImageUploadState,
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { PhotoFramingTransform, FramingAction, PhotoFramingSteps } from './framing.js';
|
|
2
|
+
export { PHOTO_FRAMING_STEPS, canStepFraming, framingToTransform, stepFraming } from './framing.js';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { ButtonVariant } from '@dloizides/ui-buttons';
|
|
5
|
+
import { PhotoFramingInput, PhotoFraming } from '@dloizides/site-template-kit/framing';
|
|
6
|
+
export { DEFAULT_PHOTO_FRAMING, PHOTO_FRAMING_BOUNDS, PhotoFraming, PhotoFramingInput, clampFraming, isDefaultFraming } from '@dloizides/site-template-kit/framing';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* The four states of an image upload slot (KEFI-PEOPLE-1 design board "upload
|
|
@@ -140,4 +142,4 @@ interface PhotoFramingEditorProps {
|
|
|
140
142
|
|
|
141
143
|
declare const PhotoFramingEditor: ({ value, onChange, renderPreview, labels, testID, steps, renderGlyph, disabled, }: PhotoFramingEditorProps) => React.ReactElement;
|
|
142
144
|
|
|
143
|
-
export { DEFAULT_IMAGE_ACCEPT, FramingAction, type FramingActionLabel, ImagePickerButton, type ImagePickerButtonProps, type ImagePickerLabels, ImagePickerStatus, type ImageUploadState,
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var framing = require('@dloizides/site-template-kit/framing');
|
|
3
4
|
var react = require('react');
|
|
4
5
|
var reactNative = require('react-native');
|
|
5
6
|
var uiButtons = require('@dloizides/ui-buttons');
|
|
@@ -8,27 +9,6 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
8
9
|
var uiLayout = require('@dloizides/ui-layout');
|
|
9
10
|
|
|
10
11
|
// src/framing/photoFraming.ts
|
|
11
|
-
var PHOTO_FRAMING_BOUNDS = Object.freeze({
|
|
12
|
-
/** Widest nudge, as a percentage of the card, in either direction. */
|
|
13
|
-
maxOffsetPct: 60,
|
|
14
|
-
/** Below 1 the photo shrinks inside an already-roomy frame. */
|
|
15
|
-
minScale: 0.5,
|
|
16
|
-
/** Far above 2 the subject's head leaves the card entirely. */
|
|
17
|
-
maxScale: 3
|
|
18
|
-
});
|
|
19
|
-
var DEFAULT_PHOTO_FRAMING = Object.freeze({ x: 0, y: 0, scale: 1 });
|
|
20
|
-
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
21
|
-
var finiteOr = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
22
|
-
function clampFraming(input) {
|
|
23
|
-
if (!input) return DEFAULT_PHOTO_FRAMING;
|
|
24
|
-
const { maxOffsetPct, minScale, maxScale } = PHOTO_FRAMING_BOUNDS;
|
|
25
|
-
return {
|
|
26
|
-
x: clamp(finiteOr(input.x, DEFAULT_PHOTO_FRAMING.x), -maxOffsetPct, maxOffsetPct),
|
|
27
|
-
y: clamp(finiteOr(input.y, DEFAULT_PHOTO_FRAMING.y), -maxOffsetPct, maxOffsetPct),
|
|
28
|
-
scale: clamp(finiteOr(input.scale, DEFAULT_PHOTO_FRAMING.scale), minScale, maxScale)
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
var isDefaultFraming = (framing) => framing.x === DEFAULT_PHOTO_FRAMING.x && framing.y === DEFAULT_PHOTO_FRAMING.y && framing.scale === DEFAULT_PHOTO_FRAMING.scale;
|
|
32
12
|
var framingToTransform = (framing) => [
|
|
33
13
|
{ translateX: `${framing.x}%` },
|
|
34
14
|
{ translateY: `${framing.y}%` },
|
|
@@ -59,17 +39,17 @@ var DIRECTIONS = {
|
|
|
59
39
|
[FramingAction.MoveRight]: [1, 0, 0]
|
|
60
40
|
};
|
|
61
41
|
function stepFraming(current, action, steps = PHOTO_FRAMING_STEPS) {
|
|
62
|
-
if (action === FramingAction.Reset) return DEFAULT_PHOTO_FRAMING;
|
|
63
|
-
const base = clampFraming(current);
|
|
42
|
+
if (action === FramingAction.Reset) return framing.DEFAULT_PHOTO_FRAMING;
|
|
43
|
+
const base = framing.clampFraming(current);
|
|
64
44
|
const [dx, dy, ds] = DIRECTIONS[action];
|
|
65
|
-
return clampFraming({
|
|
45
|
+
return framing.clampFraming({
|
|
66
46
|
x: round(base.x + dx * steps.offsetPct),
|
|
67
47
|
y: round(base.y + dy * steps.offsetPct),
|
|
68
48
|
scale: round(base.scale + ds * steps.scale)
|
|
69
49
|
});
|
|
70
50
|
}
|
|
71
51
|
function canStepFraming(current, action, steps = PHOTO_FRAMING_STEPS) {
|
|
72
|
-
const base = clampFraming(current);
|
|
52
|
+
const base = framing.clampFraming(current);
|
|
73
53
|
const next = stepFraming(base, action, steps);
|
|
74
54
|
return next.x !== base.x || next.y !== base.y || next.scale !== base.scale;
|
|
75
55
|
}
|
|
@@ -287,19 +267,19 @@ var PhotoFramingEditor = ({
|
|
|
287
267
|
}) => {
|
|
288
268
|
const { width } = reactNative.useWindowDimensions();
|
|
289
269
|
const wide = width >= uiLayout.LAYOUT_COLLAPSE_BREAKPOINT;
|
|
290
|
-
const framing = react.useMemo(() => clampFraming(value), [value]);
|
|
291
|
-
const transform = react.useMemo(() => framingToTransform(framing), [framing]);
|
|
270
|
+
const framing$1 = react.useMemo(() => framing.clampFraming(value), [value]);
|
|
271
|
+
const transform = react.useMemo(() => framingToTransform(framing$1), [framing$1]);
|
|
292
272
|
const onAction = react.useCallback(
|
|
293
|
-
(action) => onChange(stepFraming(framing, action, steps)),
|
|
294
|
-
[framing, onChange, steps]
|
|
273
|
+
(action) => onChange(stepFraming(framing$1, action, steps)),
|
|
274
|
+
[framing$1, onChange, steps]
|
|
295
275
|
);
|
|
296
276
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.root, wide ? styles3.rootWide : null], testID, children: [
|
|
297
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles3.preview, wide ? styles3.previewWide : null], testID: `${testID}-preview`, children: renderPreview({ framing, transform }) }),
|
|
277
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles3.preview, wide ? styles3.previewWide : null], testID: `${testID}-preview`, children: renderPreview({ framing: framing$1, transform }) }),
|
|
298
278
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
299
279
|
FramingControls,
|
|
300
280
|
{
|
|
301
281
|
disabled,
|
|
302
|
-
framing,
|
|
282
|
+
framing: framing$1,
|
|
303
283
|
labels,
|
|
304
284
|
renderGlyph,
|
|
305
285
|
steps,
|
|
@@ -310,18 +290,30 @@ var PhotoFramingEditor = ({
|
|
|
310
290
|
] });
|
|
311
291
|
};
|
|
312
292
|
|
|
293
|
+
Object.defineProperty(exports, "DEFAULT_PHOTO_FRAMING", {
|
|
294
|
+
enumerable: true,
|
|
295
|
+
get: function () { return framing.DEFAULT_PHOTO_FRAMING; }
|
|
296
|
+
});
|
|
297
|
+
Object.defineProperty(exports, "PHOTO_FRAMING_BOUNDS", {
|
|
298
|
+
enumerable: true,
|
|
299
|
+
get: function () { return framing.PHOTO_FRAMING_BOUNDS; }
|
|
300
|
+
});
|
|
301
|
+
Object.defineProperty(exports, "clampFraming", {
|
|
302
|
+
enumerable: true,
|
|
303
|
+
get: function () { return framing.clampFraming; }
|
|
304
|
+
});
|
|
305
|
+
Object.defineProperty(exports, "isDefaultFraming", {
|
|
306
|
+
enumerable: true,
|
|
307
|
+
get: function () { return framing.isDefaultFraming; }
|
|
308
|
+
});
|
|
313
309
|
exports.DEFAULT_IMAGE_ACCEPT = DEFAULT_IMAGE_ACCEPT;
|
|
314
|
-
exports.DEFAULT_PHOTO_FRAMING = DEFAULT_PHOTO_FRAMING;
|
|
315
310
|
exports.FramingAction = FramingAction;
|
|
316
311
|
exports.ImagePickerButton = ImagePickerButton;
|
|
317
312
|
exports.ImagePickerStatus = ImagePickerStatus;
|
|
318
|
-
exports.PHOTO_FRAMING_BOUNDS = PHOTO_FRAMING_BOUNDS;
|
|
319
313
|
exports.PHOTO_FRAMING_STEPS = PHOTO_FRAMING_STEPS;
|
|
320
314
|
exports.PhotoFramingEditor = PhotoFramingEditor;
|
|
321
315
|
exports.canStepFraming = canStepFraming;
|
|
322
|
-
exports.clampFraming = clampFraming;
|
|
323
316
|
exports.framingToTransform = framingToTransform;
|
|
324
|
-
exports.isDefaultFraming = isDefaultFraming;
|
|
325
317
|
exports.pickWebImage = pickWebImage;
|
|
326
318
|
exports.resolvePickerView = resolvePickerView;
|
|
327
319
|
exports.stepFraming = stepFraming;
|
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":["useState","useRef","useEffect","useCallback","StyleSheet","useUi","useMemo","jsxs","View","jsx","Button","Text","styles","IconButton","GAP","useWindowDimensions","LAYOUT_COLLAPSE_BREAKPOINT"],"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 MIRROR `kefi-landings/src/lib/config-to-site.ts` (`photoTransform`,\n * MAX_PHOTO_OFFSET_PCT / MIN_PHOTO_SCALE / MAX_PHOTO_SCALE), which is what the\n * public landing page renders. The editor must never offer a value the landing\n * page would silently clamp away, so the numbers here and there must stay equal.\n * KEFI-PEOPLE-1 T12 \"import the bounds instead of hard-coding them\" removes the copy.\n *\n * This file imports nothing: it backs the framework-free `@dloizides/ui-media/framing`\n * subpath.\n */\n\n/** A framing value after clamping: every field finite and inside the bounds. */\nexport interface PhotoFraming {\n /** Horizontal nudge, % of the card. Negative = left. */\n readonly x: number;\n /** Vertical nudge, % of the card. Negative = up. */\n readonly y: number;\n /** Zoom factor. 1 = the photo's natural cover fit. */\n readonly scale: number;\n}\n\n/** What config / an API may hold: any field missing, null or non-finite. */\nexport interface PhotoFramingInput {\n x?: number | null;\n y?: number | null;\n scale?: number | null;\n}\n\nexport const PHOTO_FRAMING_BOUNDS = Object.freeze({\n /** Widest nudge, as a percentage of the card, in either direction. */\n maxOffsetPct: 60,\n /** Below 1 the photo shrinks inside an already-roomy frame. */\n minScale: 0.5,\n /** Far above 2 the subject's head leaves the card entirely. */\n maxScale: 3,\n});\n\nexport const DEFAULT_PHOTO_FRAMING: PhotoFraming = Object.freeze({ x: 0, y: 0, scale: 1 });\n\nconst clamp = (value: number, min: number, max: number): number => Math.min(max, Math.max(min, value));\n\nconst finiteOr = (value: number | null | undefined, fallback: number): number =>\n typeof value === 'number' && Number.isFinite(value) ? value : fallback;\n\n/**\n * Normalises any stored framing: non-finite fields fall back to the default,\n * the rest are clamped to {@link PHOTO_FRAMING_BOUNDS}. Same rules as the\n * landing page's `photoTransform`, so what the editor previews is what ships.\n */\nexport function clampFraming(input?: PhotoFramingInput | null): PhotoFraming {\n if (!input) return DEFAULT_PHOTO_FRAMING;\n const { maxOffsetPct, minScale, maxScale } = PHOTO_FRAMING_BOUNDS;\n return {\n x: clamp(finiteOr(input.x, DEFAULT_PHOTO_FRAMING.x), -maxOffsetPct, maxOffsetPct),\n y: clamp(finiteOr(input.y, DEFAULT_PHOTO_FRAMING.y), -maxOffsetPct, maxOffsetPct),\n scale: clamp(finiteOr(input.scale, DEFAULT_PHOTO_FRAMING.scale), minScale, maxScale),\n };\n}\n\n/** True when the framing is the identity (the landing page emits no transform). */\nexport const isDefaultFraming = (framing: PhotoFraming): boolean =>\n framing.x === DEFAULT_PHOTO_FRAMING.x &&\n framing.y === DEFAULT_PHOTO_FRAMING.y &&\n framing.scale === DEFAULT_PHOTO_FRAMING.scale;\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/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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DEFAULT_PHOTO_FRAMING, clampFraming } from '@dloizides/site-template-kit/framing';
|
|
2
|
+
export { DEFAULT_PHOTO_FRAMING, PHOTO_FRAMING_BOUNDS, clampFraming, isDefaultFraming } from '@dloizides/site-template-kit/framing';
|
|
1
3
|
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
2
4
|
import { StyleSheet, View, Text, useWindowDimensions } from 'react-native';
|
|
3
5
|
import { Button, IconButton } from '@dloizides/ui-buttons';
|
|
@@ -6,27 +8,6 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
6
8
|
import { LAYOUT_COLLAPSE_BREAKPOINT } from '@dloizides/ui-layout';
|
|
7
9
|
|
|
8
10
|
// src/framing/photoFraming.ts
|
|
9
|
-
var PHOTO_FRAMING_BOUNDS = Object.freeze({
|
|
10
|
-
/** Widest nudge, as a percentage of the card, in either direction. */
|
|
11
|
-
maxOffsetPct: 60,
|
|
12
|
-
/** Below 1 the photo shrinks inside an already-roomy frame. */
|
|
13
|
-
minScale: 0.5,
|
|
14
|
-
/** Far above 2 the subject's head leaves the card entirely. */
|
|
15
|
-
maxScale: 3
|
|
16
|
-
});
|
|
17
|
-
var DEFAULT_PHOTO_FRAMING = Object.freeze({ x: 0, y: 0, scale: 1 });
|
|
18
|
-
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
19
|
-
var finiteOr = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
20
|
-
function clampFraming(input) {
|
|
21
|
-
if (!input) return DEFAULT_PHOTO_FRAMING;
|
|
22
|
-
const { maxOffsetPct, minScale, maxScale } = PHOTO_FRAMING_BOUNDS;
|
|
23
|
-
return {
|
|
24
|
-
x: clamp(finiteOr(input.x, DEFAULT_PHOTO_FRAMING.x), -maxOffsetPct, maxOffsetPct),
|
|
25
|
-
y: clamp(finiteOr(input.y, DEFAULT_PHOTO_FRAMING.y), -maxOffsetPct, maxOffsetPct),
|
|
26
|
-
scale: clamp(finiteOr(input.scale, DEFAULT_PHOTO_FRAMING.scale), minScale, maxScale)
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
var isDefaultFraming = (framing) => framing.x === DEFAULT_PHOTO_FRAMING.x && framing.y === DEFAULT_PHOTO_FRAMING.y && framing.scale === DEFAULT_PHOTO_FRAMING.scale;
|
|
30
11
|
var framingToTransform = (framing) => [
|
|
31
12
|
{ translateX: `${framing.x}%` },
|
|
32
13
|
{ translateY: `${framing.y}%` },
|
|
@@ -308,6 +289,6 @@ var PhotoFramingEditor = ({
|
|
|
308
289
|
] });
|
|
309
290
|
};
|
|
310
291
|
|
|
311
|
-
export { DEFAULT_IMAGE_ACCEPT,
|
|
292
|
+
export { DEFAULT_IMAGE_ACCEPT, FramingAction, ImagePickerButton, ImagePickerStatus, PHOTO_FRAMING_STEPS, PhotoFramingEditor, canStepFraming, framingToTransform, pickWebImage, resolvePickerView, stepFraming, useImageUpload };
|
|
312
293
|
//# sourceMappingURL=index.mjs.map
|
|
313
294
|
//# 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 MIRROR `kefi-landings/src/lib/config-to-site.ts` (`photoTransform`,\n * MAX_PHOTO_OFFSET_PCT / MIN_PHOTO_SCALE / MAX_PHOTO_SCALE), which is what the\n * public landing page renders. The editor must never offer a value the landing\n * page would silently clamp away, so the numbers here and there must stay equal.\n * KEFI-PEOPLE-1 T12 \"import the bounds instead of hard-coding them\" removes the copy.\n *\n * This file imports nothing: it backs the framework-free `@dloizides/ui-media/framing`\n * subpath.\n */\n\n/** A framing value after clamping: every field finite and inside the bounds. */\nexport interface PhotoFraming {\n /** Horizontal nudge, % of the card. Negative = left. */\n readonly x: number;\n /** Vertical nudge, % of the card. Negative = up. */\n readonly y: number;\n /** Zoom factor. 1 = the photo's natural cover fit. */\n readonly scale: number;\n}\n\n/** What config / an API may hold: any field missing, null or non-finite. */\nexport interface PhotoFramingInput {\n x?: number | null;\n y?: number | null;\n scale?: number | null;\n}\n\nexport const PHOTO_FRAMING_BOUNDS = Object.freeze({\n /** Widest nudge, as a percentage of the card, in either direction. */\n maxOffsetPct: 60,\n /** Below 1 the photo shrinks inside an already-roomy frame. */\n minScale: 0.5,\n /** Far above 2 the subject's head leaves the card entirely. */\n maxScale: 3,\n});\n\nexport const DEFAULT_PHOTO_FRAMING: PhotoFraming = Object.freeze({ x: 0, y: 0, scale: 1 });\n\nconst clamp = (value: number, min: number, max: number): number => Math.min(max, Math.max(min, value));\n\nconst finiteOr = (value: number | null | undefined, fallback: number): number =>\n typeof value === 'number' && Number.isFinite(value) ? value : fallback;\n\n/**\n * Normalises any stored framing: non-finite fields fall back to the default,\n * the rest are clamped to {@link PHOTO_FRAMING_BOUNDS}. Same rules as the\n * landing page's `photoTransform`, so what the editor previews is what ships.\n */\nexport function clampFraming(input?: PhotoFramingInput | null): PhotoFraming {\n if (!input) return DEFAULT_PHOTO_FRAMING;\n const { maxOffsetPct, minScale, maxScale } = PHOTO_FRAMING_BOUNDS;\n return {\n x: clamp(finiteOr(input.x, DEFAULT_PHOTO_FRAMING.x), -maxOffsetPct, maxOffsetPct),\n y: clamp(finiteOr(input.y, DEFAULT_PHOTO_FRAMING.y), -maxOffsetPct, maxOffsetPct),\n scale: clamp(finiteOr(input.scale, DEFAULT_PHOTO_FRAMING.scale), minScale, maxScale),\n };\n}\n\n/** True when the framing is the identity (the landing page emits no transform). */\nexport const isDefaultFraming = (framing: PhotoFraming): boolean =>\n framing.x === DEFAULT_PHOTO_FRAMING.x &&\n framing.y === DEFAULT_PHOTO_FRAMING.y &&\n framing.scale === DEFAULT_PHOTO_FRAMING.scale;\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/PhotoFramingEditor.tsx"],"names":["useCallback","styles","StyleSheet","useUi","jsx","Text","jsxs","View","GAP","useMemo"],"mappings":";;;;;;;;;;AAiCO,IAAM,kBAAA,GAAqB,CAAC,OAAA,KAAiD;AAAA,EAClF,EAAE,UAAA,EAAY,CAAA,EAAG,OAAA,CAAQ,CAAC,CAAA,CAAA,CAAA,EAAI;AAAA,EAC9B,EAAE,UAAA,EAAY,CAAA,EAAG,OAAA,CAAQ,CAAC,CAAA,CAAA,CAAA,EAAI;AAAA,EAC9B,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA;AACnB;;;AChCO,IAAM,aAAA,GAAgB;AAAA,EAC3B,MAAA,EAAQ,QAAA;AAAA,EACR,OAAA,EAAS,SAAA;AAAA,EACT,MAAA,EAAQ,QAAA;AAAA,EACR,QAAA,EAAU,UAAA;AAAA,EACV,QAAA,EAAU,UAAA;AAAA,EACV,SAAA,EAAW,WAAA;AAAA,EACX,KAAA,EAAO;AACT;;;ACDO,IAAM,mBAAA,GAAyC,OAAO,MAAA,CAAO,EAAE,WAAW,CAAA,EAAG,KAAA,EAAO,KAAK;AAGhG,IAAM,SAAA,GAAY,GAAA;AAClB,IAAM,QAAQ,CAAC,CAAA,KAAsB,KAAK,KAAA,CAAM,CAAA,GAAI,SAAS,CAAA,GAAI,SAAA;AAKjE,IAAM,UAAA,GAAoE;AAAA,EACxE,CAAC,aAAA,CAAc,MAAM,GAAG,CAAC,CAAA,EAAG,GAAG,CAAC,CAAA;AAAA,EAChC,CAAC,aAAA,CAAc,OAAO,GAAG,CAAC,CAAA,EAAG,GAAG,EAAE,CAAA;AAAA,EAClC,CAAC,aAAA,CAAc,MAAM,GAAG,CAAC,CAAA,EAAG,IAAI,CAAC,CAAA;AAAA,EACjC,CAAC,aAAA,CAAc,QAAQ,GAAG,CAAC,CAAA,EAAG,GAAG,CAAC,CAAA;AAAA,EAClC,CAAC,aAAA,CAAc,QAAQ,GAAG,CAAC,EAAA,EAAI,GAAG,CAAC,CAAA;AAAA,EACnC,CAAC,aAAA,CAAc,SAAS,GAAG,CAAC,CAAA,EAAG,GAAG,CAAC;AACrC,CAAA;AAMO,SAAS,WAAA,CACd,OAAA,EACA,MAAA,EACA,KAAA,GAA2B,mBAAA,EACb;AACd,EAAA,IAAI,MAAA,KAAW,aAAA,CAAc,KAAA,EAAO,OAAO,qBAAA;AAC3C,EAAA,MAAM,IAAA,GAAO,aAAa,OAAO,CAAA;AACjC,EAAA,MAAM,CAAC,EAAA,EAAI,EAAA,EAAI,EAAE,CAAA,GAAI,WAAW,MAAM,CAAA;AACtC,EAAA,OAAO,YAAA,CAAa;AAAA,IAClB,GAAG,KAAA,CAAM,IAAA,CAAK,CAAA,GAAI,EAAA,GAAK,MAAM,SAAS,CAAA;AAAA,IACtC,GAAG,KAAA,CAAM,IAAA,CAAK,CAAA,GAAI,EAAA,GAAK,MAAM,SAAS,CAAA;AAAA,IACtC,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,GAAQ,EAAA,GAAK,MAAM,KAAK;AAAA,GAC3C,CAAA;AACH;AAGO,SAAS,cAAA,CACd,OAAA,EACA,MAAA,EACA,KAAA,GAA2B,mBAAA,EAClB;AACT,EAAA,MAAM,IAAA,GAAO,aAAa,OAAO,CAAA;AACjC,EAAA,MAAM,IAAA,GAAO,WAAA,CAAY,IAAA,EAAM,MAAA,EAAQ,KAAK,CAAA;AAC5C,EAAA,OAAO,IAAA,CAAK,CAAA,KAAM,IAAA,CAAK,CAAA,IAAK,IAAA,CAAK,MAAM,IAAA,CAAK,CAAA,IAAK,IAAA,CAAK,KAAA,KAAU,IAAA,CAAK,KAAA;AACvE;;;ACrDO,IAAM,iBAAA,GAAoB;AAAA,EAC/B,IAAA,EAAM,MAAA;AAAA,EACN,SAAA,EAAW,WAAA;AAAA,EACX,KAAA,EAAO,OAAA;AAAA,EACP,IAAA,EAAM;AACR;ACNO,IAAM,oBAAA,GAAuB;AAM7B,SAAS,YAAA,CAAa,SAAiB,oBAAA,EAA4C;AACxF,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,OAAA,CAAQ,QAAQ,IAAI,CAAA;AAChE,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY;AAC9B,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC5C,IAAA,KAAA,CAAM,IAAA,GAAO,MAAA;AACb,IAAA,KAAA,CAAM,MAAA,GAAS,MAAA;AACf,IAAA,KAAA,CAAM,gBAAA,CAAiB,QAAA,EAAU,MAAM,OAAA,CAAQ,KAAA,CAAM,KAAA,GAAQ,CAAC,CAAA,IAAK,IAAI,CAAA,EAAG,EAAE,IAAA,EAAM,MAAM,CAAA;AACxF,IAAA,KAAA,CAAM,gBAAA,CAAiB,UAAU,MAAM,OAAA,CAAQ,IAAI,CAAA,EAAG,EAAE,IAAA,EAAM,IAAA,EAAM,CAAA;AACpE,IAAA,KAAA,CAAM,KAAA,EAAM;AAAA,EACd,CAAC,CAAA;AACH;AAwBO,SAAS,cAAA,CAAsB;AAAA,EACpC,MAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX;AACF,CAAA,EAAmD;AACjD,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,QAAA;AAAA,IAC1B,QAAA,GAAW,iBAAA,CAAkB,IAAA,GAAO,iBAAA,CAAkB;AAAA,GACxD;AACA,EAAA,MAAM,IAAA,GAAO,OAAO,KAAK,CAAA;AACzB,EAAA,MAAM,OAAA,GAAU,OAAO,IAAI,CAAA;AAE3B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAClB,IAAA,OAAO,MAAM;AACX,MAAA,OAAA,CAAQ,OAAA,GAAU,KAAA;AAAA,IACpB,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,MAAA,GAAS,WAAA,CAAY,CAAC,IAAA,KAAkC;AAC5D,IAAA,IAAI,OAAA,CAAQ,OAAA,EAAS,SAAA,CAAU,IAAI,CAAA;AAAA,EACrC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,IAAA,GAAO,YAAY,YAA2B;AAClD,IAAA,IAAI,KAAK,OAAA,EAAS;AAClB,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AACf,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,EAAS;AAC5B,MAAA,IAAI,SAAS,IAAA,EAAM;AACnB,MAAA,MAAA,CAAO,kBAAkB,SAAS,CAAA;AAClC,MAAA,MAAM,OAAO,IAAI,CAAA;AACjB,MAAA,MAAA,CAAO,kBAAkB,IAAI,CAAA;AAAA,IAC/B,SAAS,KAAA,EAAO;AACd,MAAA,MAAA,CAAO,kBAAkB,KAAK,CAAA;AAC9B,MAAA,OAAA,GAAU,KAAK,CAAA;AAAA,IACjB,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,QAAA,EAAU,MAAA,EAAQ,OAAA,EAAS,MAAM,CAAC,CAAA;AAEtC,EAAA,OAAO,EAAE,QAAQ,IAAA,EAAK;AACxB;AC3BO,SAAS,iBAAA,CAAkB,QAA2B,MAAA,EAAuC;AAClG,EAAA,QAAQ,MAAA;AAAQ,IACd,KAAK,iBAAA,CAAkB,SAAA;AACrB,MAAA,OAAO,EAAE,aAAa,MAAA,CAAO,SAAA,EAAW,SAAS,SAAA,EAAW,UAAA,EAAY,IAAA,EAAM,OAAA,EAAS,KAAA,EAAM;AAAA,IAC/F,KAAK,iBAAA,CAAkB,KAAA;AACrB,MAAA,OAAO,EAAE,WAAA,EAAa,MAAA,CAAO,KAAA,EAAO,OAAA,EAAS,UAAU,UAAA,EAAY,MAAA,CAAO,KAAA,EAAO,OAAA,EAAS,IAAA,EAAK;AAAA,IACjG,KAAK,iBAAA,CAAkB,IAAA;AACrB,MAAA,OAAO,EAAE,WAAA,EAAa,MAAA,CAAO,OAAA,EAAS,OAAA,EAAS,WAAW,UAAA,EAAY,MAAA,CAAO,IAAA,EAAM,OAAA,EAAS,KAAA,EAAM;AAAA,IACpG;AACE,MAAA,OAAO,EAAE,aAAa,MAAA,CAAO,IAAA,EAAM,SAAS,SAAA,EAAW,UAAA,EAAY,IAAA,EAAM,OAAA,EAAS,KAAA,EAAM;AAAA;AAE9F;AAEA,IAAM,UAAA,GAAa,CAAA;AACnB,IAAM,gBAAA,GAAmB,EAAA;AAEzB,IAAM,MAAA,GAAS,WAAW,MAAA,CAAO;AAAA,EAC/B,IAAA,EAAM,EAAE,UAAA,EAAY,YAAA,EAAc,KAAK,UAAA,EAAW;AAAA,EAClD,MAAA,EAAQ,EAAE,QAAA,EAAU,gBAAA;AACtB,CAAC,CAAA;AAEM,SAAS,iBAAA,CAAgC;AAAA,EAC9C,MAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA,GAAS,oBAAA;AAAA,EACT,QAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA,GAAW;AACb,CAAA,EAAsD;AACpD,EAAA,MAAM,EAAE,KAAA,EAAM,GAAI,KAAA,EAAM;AAExB,EAAA,MAAM,OAAA,GAAUA,YAAY,MAAM,YAAA,CAAa,MAAM,CAAA,EAA4B,CAAC,MAAM,CAAC,CAAA;AACzF,EAAA,MAAM,EAAE,MAAA,EAAQ,IAAA,EAAK,GAAI,cAAA,CAAsB,EAAE,MAAA,EAAQ,QAAA,EAAU,QAAA,IAAY,OAAA,EAAS,QAAA,EAAU,OAAA,EAAS,CAAA;AAC3G,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,MAAM,iBAAA,CAAkB,MAAA,EAAQ,MAAM,CAAA,EAAG,CAAC,MAAA,EAAQ,MAAM,CAAC,CAAA;AAC9E,EAAA,MAAM,WAAA,GAAc,WAAW,iBAAA,CAAkB,SAAA;AACjD,EAAA,MAAM,WAAA,GAAc,KAAK,OAAA,GAAU,KAAA,CAAM,SAAS,KAAA,CAAM,KAAK,CAAA,GAAI,KAAA,CAAM,MAAA,CAAO,aAAA;AAE9E,EAAA,uBACE,IAAA,CAAC,IAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,MAAM,MAAA,EACxB,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,mBAAmB,MAAA,CAAO,IAAA;AAAA,QAC1B,oBAAoB,IAAA,CAAK,WAAA;AAAA,QACzB,WAAA,EAAa,KAAA;AAAA,QACb,UAAU,QAAA,IAAY,WAAA;AAAA,QACtB,OAAO,IAAA,CAAK,WAAA;AAAA,QACZ,OAAA,EAAS,WAAA;AAAA,QACT,MAAA,EAAQ,GAAG,MAAM,CAAA,OAAA,CAAA;AAAA,QACjB,SAAS,IAAA,CAAK,OAAA;AAAA,QACd,OAAA,EAAS;AAAA;AAAA,KACX;AAAA,IACC,IAAA,CAAK,eAAe,IAAA,mBACnB,GAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACC,uBAAA,EAAwB,QAAA;AAAA,QACxB,OAAO,CAAC,MAAA,CAAO,QAAQ,EAAE,KAAA,EAAO,aAAa,CAAA;AAAA,QAC7C,MAAA,EAAQ,GAAG,MAAM,CAAA,OAAA,CAAA;AAAA,QAEhB,QAAA,EAAA,IAAA,CAAK;AAAA;AAAA,KACR,GACE;AAAA,GAAA,EACN,CAAA;AAEJ;ACtGA,IAAM,MAAA,GAAS,EAAA;AACf,IAAM,GAAA,GAAM,CAAA;AACZ,IAAM,WAAA,GAAc,EAAA;AACpB,IAAM,YAAA,GAAe,EAAA;AACrB,IAAM,iBAAA,GAAoB,EAAA;AAC1B,IAAM,YAAA,GAAe,EAAA;AACrB,IAAM,OAAA,GAAU,GAAA;AAEhB,IAAM,cAAA,GAAgD;AAAA,EACpD,CAAC,aAAA,CAAc,MAAM,GAAG,GAAA;AAAA,EACxB,CAAC,aAAA,CAAc,OAAO,GAAG,QAAA;AAAA,EACzB,CAAC,aAAA,CAAc,MAAM,GAAG,QAAA;AAAA,EACxB,CAAC,aAAA,CAAc,QAAQ,GAAG,QAAA;AAAA,EAC1B,CAAC,aAAA,CAAc,QAAQ,GAAG,QAAA;AAAA,EAC1B,CAAC,aAAA,CAAc,SAAS,GAAG,QAAA;AAAA,EAC3B,CAAC,aAAA,CAAc,KAAK,GAAG;AACzB,CAAA;AAEA,IAAMC,OAAAA,GAASC,WAAW,MAAA,CAAO;AAAA,EAC/B,IAAA,EAAM,EAAE,GAAA,EAAK,WAAA,EAAY;AAAA,EACzB,OAAA,EAAS,EAAE,GAAA,EAAK,GAAA,EAAI;AAAA,EACpB,OAAA,EAAS,EAAE,QAAA,EAAU,YAAA,EAAc,YAAY,KAAA,EAAM;AAAA,EACrD,KAAK,EAAE,aAAA,EAAe,OAAO,UAAA,EAAY,QAAA,EAAU,KAAK,GAAA,EAAI;AAAA,EAC5D,SAAS,EAAE,QAAA,EAAU,mBAAmB,SAAA,EAAW,QAAA,EAAU,UAAU,YAAA,EAAa;AAAA,EACpF,MAAA,EAAQ,EAAE,KAAA,EAAO,MAAA,EAAQ,QAAQ,MAAA;AACnC,CAAC,CAAA;AAYM,IAAM,kBAAkB,CAAC;AAAA,EAC9B,OAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,KAAgD;AAC9C,EAAA,MAAM,EAAE,KAAA,EAAM,GAAIC,KAAAA,EAAM;AAExB,EAAA,MAAM,OAAA,GAAU,CAAC,MAAA,qBACfC,GAAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACC,iBAAA,EAAmB,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,IAAA;AAAA,MAC1C,WAAA,EAAa,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,KAAA;AAAA,MACpC,WAAA,EAAa,KAAA;AAAA,MACb,UAAU,QAAA,IAAY,CAAC,cAAA,CAAe,OAAA,EAAS,QAAQ,KAAK,CAAA;AAAA,MAC5D,UAAA,EAAY,CAAC,KAAA,EAAO,IAAA,KAClB,WAAA,GACE,YAAY,MAAA,EAAQ,KAAA,EAAO,IAAI,CAAA,mBAE/BA,GAAAA,CAACC,MAAA,EAAK,UAAA,EAAY,KAAA,EAAO,KAAA,EAAO,EAAE,KAAA,EAAO,UAAU,IAAA,EAAK,EACrD,QAAA,EAAA,cAAA,CAAe,MAAM,CAAA,EACxB,CAAA;AAAA,MAGJ,SAAA,EAAW,KAAA;AAAA,MACX,MAAA,EAAQ,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AAAA,MAC3B,OAAA,EAAS,MAAM,QAAA,CAAS,MAAM;AAAA;AAAA,GAChC;AAEF,EAAA,MAAM,OAAA,GAAU,EAAE,KAAA,EAAO,KAAA,CAAM,OAAO,aAAA,EAAc;AAEpD,EAAA,uBACEC,IAAAA,CAACC,IAAAA,EAAA,EAAK,KAAA,EAAON,QAAO,IAAA,EAClB,QAAA,EAAA;AAAA,oBAAAK,IAAAA,CAACC,IAAAA,EAAA,EAAK,KAAA,EAAON,QAAO,OAAA,EAClB,QAAA,EAAA;AAAA,sBAAAG,GAAAA,CAACC,IAAAA,EAAA,EAAK,iBAAA,EAAkB,QAAA,EAAS,KAAA,EAAO,CAACJ,OAAAA,CAAO,OAAA,EAAS,OAAO,CAAA,EAC7D,QAAA,EAAA,MAAA,CAAO,IAAA,EACV,CAAA;AAAA,sBACAK,IAAAA,CAACC,IAAAA,EAAA,EAAK,KAAA,EAAON,QAAO,GAAA,EACjB,QAAA,EAAA;AAAA,QAAA,OAAA,CAAQ,cAAc,OAAO,CAAA;AAAA,wBAC9BG,GAAAA,CAACC,IAAAA,EAAA,EAAK,KAAA,EAAO,CAACJ,OAAAA,CAAO,OAAA,EAAS,EAAE,KAAA,EAAO,KAAA,CAAM,MAAA,CAAO,IAAA,EAAM,CAAA,EAAG,MAAA,EAAQ,CAAA,EAAG,MAAM,CAAA,MAAA,CAAA,EAC3E,QAAA,EAAA,MAAA,CAAO,UAAA,CAAW,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,KAAA,GAAQ,OAAO,CAAC,CAAA,EACxD,CAAA;AAAA,QACC,OAAA,CAAQ,cAAc,MAAM;AAAA,OAAA,EAC/B;AAAA,KAAA,EACF,CAAA;AAAA,oBACAK,IAAAA,CAACC,IAAAA,EAAA,EAAK,KAAA,EAAON,QAAO,OAAA,EAClB,QAAA,EAAA;AAAA,sBAAAG,GAAAA,CAACC,IAAAA,EAAA,EAAK,iBAAA,EAAkB,QAAA,EAAS,KAAA,EAAO,CAACJ,OAAAA,CAAO,OAAA,EAAS,OAAO,CAAA,EAC7D,QAAA,EAAA,MAAA,CAAO,QAAA,EACV,CAAA;AAAA,sBACAK,IAAAA,CAACC,IAAAA,EAAA,EAAK,KAAA,EAAON,QAAO,GAAA,EAClB,QAAA,EAAA;AAAA,wBAAAG,GAAAA,CAACG,IAAAA,EAAA,EAAK,KAAA,EAAON,QAAO,MAAA,EAAQ,CAAA;AAAA,QAC3B,OAAA,CAAQ,cAAc,MAAM,CAAA;AAAA,wBAC7BG,GAAAA,CAACG,IAAAA,EAAA,EAAK,KAAA,EAAON,QAAO,MAAA,EAAQ;AAAA,OAAA,EAC9B,CAAA;AAAA,sBACAK,IAAAA,CAACC,IAAAA,EAAA,EAAK,KAAA,EAAON,QAAO,GAAA,EACjB,QAAA,EAAA;AAAA,QAAA,OAAA,CAAQ,cAAc,QAAQ,CAAA;AAAA,QAC9B,OAAA,CAAQ,cAAc,KAAK,CAAA;AAAA,QAC3B,OAAA,CAAQ,cAAc,SAAS;AAAA,OAAA,EAClC,CAAA;AAAA,sBACAK,IAAAA,CAACC,IAAAA,EAAA,EAAK,KAAA,EAAON,QAAO,GAAA,EAClB,QAAA,EAAA;AAAA,wBAAAG,GAAAA,CAACG,IAAAA,EAAA,EAAK,KAAA,EAAON,QAAO,MAAA,EAAQ,CAAA;AAAA,QAC3B,OAAA,CAAQ,cAAc,QAAQ,CAAA;AAAA,wBAC/BG,GAAAA,CAACG,IAAAA,EAAA,EAAK,KAAA,EAAON,QAAO,MAAA,EAAQ;AAAA,OAAA,EAC9B;AAAA,KAAA,EACF;AAAA,GAAA,EACF,CAAA;AAEJ,CAAA;ACxGA,IAAMO,IAAAA,GAAM,EAAA;AAEZ,IAAMP,OAAAA,GAASC,WAAW,MAAA,CAAO;AAAA,EAC/B,MAAM,EAAE,aAAA,EAAe,UAAU,GAAA,EAAKM,IAAAA,EAAK,YAAY,SAAA,EAAU;AAAA,EACjE,QAAA,EAAU,EAAE,aAAA,EAAe,KAAA,EAAO,YAAY,YAAA,EAAa;AAAA,EAC3D,OAAA,EAAS,EAAE,UAAA,EAAY,QAAA,EAAU,UAAU,QAAA,EAAS;AAAA,EACpD,WAAA,EAAa,EAAE,UAAA,EAAY,CAAA;AAC7B,CAAC,CAAA;AAEM,IAAM,qBAAqB,CAAC;AAAA,EACjC,KAAA;AAAA,EACA,QAAA;AAAA,EACA,aAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA,GAAW;AACb,CAAA,KAAmD;AACjD,EAAA,MAAM,EAAE,KAAA,EAAM,GAAI,mBAAA,EAAoB;AACtC,EAAA,MAAM,OAAO,KAAA,IAAS,0BAAA;AACtB,EAAA,MAAM,OAAA,GAAUC,QAAQ,MAAM,YAAA,CAAa,KAAK,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAC1D,EAAA,MAAM,SAAA,GAAYA,QAAQ,MAAM,kBAAA,CAAmB,OAAO,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEtE,EAAA,MAAM,QAAA,GAAWT,WAAAA;AAAA,IACf,CAAC,MAAA,KAAgC,QAAA,CAAS,YAAY,OAAA,EAAS,MAAA,EAAQ,KAAK,CAAC,CAAA;AAAA,IAC7E,CAAC,OAAA,EAAS,QAAA,EAAU,KAAK;AAAA,GAC3B;AAEA,EAAA,uBACEM,IAAAA,CAACC,IAAAA,EAAA,EAAK,KAAA,EAAO,CAACN,OAAAA,CAAO,IAAA,EAAM,IAAA,GAAOA,OAAAA,CAAO,QAAA,GAAW,IAAI,GAAG,MAAA,EACzD,QAAA,EAAA;AAAA,oBAAAG,GAAAA,CAACG,MAAA,EAAK,KAAA,EAAO,CAACN,OAAAA,CAAO,OAAA,EAAS,OAAOA,OAAAA,CAAO,WAAA,GAAc,IAAI,CAAA,EAAG,MAAA,EAAQ,GAAG,MAAM,CAAA,QAAA,CAAA,EAC/E,wBAAc,EAAE,OAAA,EAAS,SAAA,EAAW,CAAA,EACvC,CAAA;AAAA,oBACAG,GAAAA;AAAA,MAAC,eAAA;AAAA,MAAA;AAAA,QACC,QAAA;AAAA,QACA,OAAA;AAAA,QACA,MAAA;AAAA,QACA,WAAA;AAAA,QACA,KAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA;AAAA;AACF,GAAA,EACF,CAAA;AAEJ","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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dloizides/ui-media",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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",
|
|
@@ -119,5 +119,8 @@
|
|
|
119
119
|
"ts-jest": "^29.1.0",
|
|
120
120
|
"tsup": "^8.0.0",
|
|
121
121
|
"typescript": "^5.4.0"
|
|
122
|
+
},
|
|
123
|
+
"dependencies": {
|
|
124
|
+
"@dloizides/site-template-kit": "^0.4.0"
|
|
122
125
|
}
|
|
123
126
|
}
|