@forthtilliath/react-native-kit 0.18.0 → 0.20.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.
Files changed (37) hide show
  1. package/README.md +60 -16
  2. package/dist/components/picker/PhotoPicker.d.ts +22 -29
  3. package/dist/components/picker/PhotoPicker.d.ts.map +1 -1
  4. package/dist/components/picker/PhotoPicker.js +49 -81
  5. package/dist/components/picker/PhotoPicker.styles.d.ts +63 -0
  6. package/dist/components/picker/PhotoPicker.styles.d.ts.map +1 -0
  7. package/dist/components/picker/PhotoPicker.styles.js +59 -0
  8. package/dist/components/picker/QuantityStepper.d.ts +32 -31
  9. package/dist/components/picker/QuantityStepper.d.ts.map +1 -1
  10. package/dist/components/picker/QuantityStepper.js +39 -69
  11. package/dist/components/picker/QuantityStepper.styles.d.ts +59 -0
  12. package/dist/components/picker/QuantityStepper.styles.d.ts.map +1 -0
  13. package/dist/components/picker/QuantityStepper.styles.js +46 -0
  14. package/dist/components/settings/UpdateSettingsScreen.d.ts +5 -1
  15. package/dist/components/settings/UpdateSettingsScreen.d.ts.map +1 -1
  16. package/dist/components/settings/UpdateSettingsScreen.js +3 -3
  17. package/dist/components/settings/UpdateSettingsScreen.styles.d.ts +2 -0
  18. package/dist/components/settings/UpdateSettingsScreen.styles.d.ts.map +1 -1
  19. package/dist/components/theme/BackgroundPicker.d.ts +46 -0
  20. package/dist/components/theme/BackgroundPicker.d.ts.map +1 -0
  21. package/dist/components/theme/BackgroundPicker.js +79 -0
  22. package/dist/components/theme/findBackground.d.ts +20 -0
  23. package/dist/components/theme/findBackground.d.ts.map +1 -0
  24. package/dist/components/theme/findBackground.js +12 -0
  25. package/dist/components/theme/index.d.ts +2 -0
  26. package/dist/components/theme/index.d.ts.map +1 -1
  27. package/dist/components/theme/index.js +2 -0
  28. package/dist/hooks/index.d.ts +1 -0
  29. package/dist/hooks/index.d.ts.map +1 -1
  30. package/dist/hooks/index.js +1 -0
  31. package/dist/hooks/useViewShare.d.ts +39 -0
  32. package/dist/hooks/useViewShare.d.ts.map +1 -0
  33. package/dist/hooks/useViewShare.js +75 -0
  34. package/dist/utils/helpers/shareViewAsImage.d.ts +5 -3
  35. package/dist/utils/helpers/shareViewAsImage.d.ts.map +1 -1
  36. package/dist/utils/helpers/shareViewAsImage.js +3 -1
  37. package/package.json +8 -8
@@ -4,52 +4,7 @@ import { Pressable, Text, TextInput, View } from "react-native";
4
4
  import { usePressRepeat } from "../../hooks/usePressRepeat.js";
5
5
  import { useKitLocale } from "../../i18n/locale.js";
6
6
  import { mergeSlotStyles } from "../../utils/helpers/mergeSlotStyles.js";
7
- const defaultA11yLabels = {
8
- fr: {
9
- decrease: (label) => `Retirer : ${label}`,
10
- increase: (label) => `Ajouter : ${label}`,
11
- input: (label) => `Quantité : ${label}`,
12
- },
13
- en: {
14
- decrease: (label) => `Decrease: ${label}`,
15
- increase: (label) => `Increase: ${label}`,
16
- input: (label) => `Quantity: ${label}`,
17
- },
18
- };
19
- const defaultStyles = {
20
- row: {
21
- flexDirection: "row",
22
- alignItems: "center",
23
- gap: 6,
24
- },
25
- button: {
26
- width: 40,
27
- height: 40,
28
- borderRadius: 20,
29
- backgroundColor: "#2563eb",
30
- alignItems: "center",
31
- justifyContent: "center",
32
- },
33
- buttonDisabled: { opacity: 0.35 },
34
- buttonText: {
35
- color: "#ffffff",
36
- fontSize: 22,
37
- fontWeight: "700",
38
- lineHeight: 26,
39
- },
40
- input: {
41
- width: 52,
42
- height: 40,
43
- borderRadius: 8,
44
- borderWidth: 1,
45
- borderColor: "#d4d4d4",
46
- color: "#111111",
47
- fontSize: 18,
48
- fontWeight: "700",
49
- textAlign: "center",
50
- padding: 0,
51
- },
52
- };
7
+ import { defaultA11yLabels, defaultStyles, } from "./QuantityStepper.styles.js";
53
8
  /** Keeps the digits, and a leading "-" when negatives are allowed. */
54
9
  function sanitize(text, allowsNegative) {
55
10
  const digits = text.replace(/\D/g, "");
@@ -57,14 +12,21 @@ function sanitize(text, allowsNegative) {
57
12
  ? `-${digits}`
58
13
  : digits;
59
14
  }
15
+ /** Characters a bound takes in the field; 15 digits (+ "-") if unbounded. */
16
+ function lengthOf(bound) {
17
+ if (Number.isFinite(bound))
18
+ return String(bound).length;
19
+ return bound < 0 ? 16 : 15;
20
+ }
60
21
  /**
61
- * A whole-number counter from `min` (0) to `max`: −/+ buttons to adjust
62
- * (optionally faster on long press, or repeating while held), and a numeric
63
- * field in the middle to type a large number in one go (other characters are
64
- * dropped, the result is clamped). A typed value that is not a number yet or
65
- * below `min` (an empty field, a lone "-", the "2" of "25" with `min={10}`)
66
- * stays as typed until the field loses focus. A negative `min` allows
67
- * negative values (penalties, defeat tokens…).
22
+ * A whole-number counter from `min` (0) to `max`: −/+ buttons to adjust by
23
+ * `step` (optionally faster on long press, or repeating while held), and a
24
+ * numeric field in the middle to type a large number in one go (other
25
+ * characters are dropped, the result is clamped). A typed value that is not a
26
+ * number yet or below `min` (an empty field, a lone "-", the "2" of "25" with
27
+ * `min={10}`) stays as typed until the field loses focus. A negative `min`
28
+ * allows negative values (penalties, defeat tokens…); `-Infinity`/`Infinity`
29
+ * remove a bound.
68
30
  *
69
31
  * @example
70
32
  * ```tsx
@@ -72,10 +34,10 @@ function sanitize(text, allowsNegative) {
72
34
  *
73
35
  * <QuantityStepper value={count} onChange={setCount} label="Croissants" />
74
36
  * <QuantityStepper value={bans} onChange={setBans} label="Bannissements" min={1} max={40} longPressStep={5} />
75
- * <QuantityStepper value={score} onChange={setScore} label="Points" min={-999} repeatOnHold />
37
+ * <QuantityStepper value={score} onChange={setScore} label="Points" min={-Infinity} max={Infinity} step={4} repeatOnHold onStep={tapFeedback} />
76
38
  * ```
77
39
  */
78
- export function QuantityStepper({ value, onChange, label, min = 0, max = 999, longPressStep, repeatOnHold = false, locale, a11yLabels, styles, }) {
40
+ export function QuantityStepper({ value, onChange, label, min = 0, max = 999, step = 1, onStep, longPressStep, repeatOnHold = false, maxFontSizeMultiplier, locale, a11yLabels, styles, }) {
79
41
  const labels = { ...defaultA11yLabels[useKitLocale(locale)], ...a11yLabels };
80
42
  const merged = mergeSlotStyles(defaultStyles, styles);
81
43
  const atMin = value <= min;
@@ -96,38 +58,46 @@ export function QuantityStepper({ value, onChange, label, min = 0, max = 999, lo
96
58
  setDraft(null);
97
59
  onChange(clamp(next));
98
60
  }
61
+ /** A −/+ press: moves by `delta`; `false` when a bound stopped it. */
99
62
  function stepBy(delta) {
100
63
  const next = clamp(valueRef.current + delta);
101
- // A button disabled at its bound may never report the release.
102
- if (next === valueRef.current) {
103
- repeat.stop();
104
- return;
105
- }
64
+ if (next === valueRef.current)
65
+ return false;
106
66
  valueRef.current = next;
107
67
  set(next);
68
+ onStep?.(next);
69
+ return true;
108
70
  }
109
- function startRepeat(delta) {
71
+ function startRepeat(direction) {
110
72
  repeat.start(() => {
111
- stepBy(delta);
73
+ // A button disabled at its bound may never report the release.
74
+ if (!stepBy(direction * step))
75
+ repeat.stop();
112
76
  });
113
77
  }
114
78
  // Held: repeat from press-in to press-out. Otherwise: one step per tap,
115
79
  // and `longPressStep` on long press.
116
80
  const tapsOnly = !repeatOnHold;
117
81
  const jumps = tapsOnly && longPressStep !== undefined;
82
+ // A tap starts from the rendered value, even if the parent ignored the
83
+ // previous onChange.
84
+ function tap(delta) {
85
+ valueRef.current = value;
86
+ stepBy(delta);
87
+ }
118
88
  return (_jsxs(View, { style: merged.row, children: [_jsx(Pressable, { style: [merged.button, atMin && merged.buttonDisabled], onPress: tapsOnly
119
89
  ? () => {
120
- set(value - 1);
90
+ tap(-step);
121
91
  }
122
92
  : undefined, onLongPress: jumps
123
93
  ? () => {
124
- set(value - longPressStep);
94
+ tap(-longPressStep);
125
95
  }
126
96
  : undefined, onPressIn: repeatOnHold
127
97
  ? () => {
128
98
  startRepeat(-1);
129
99
  }
130
- : undefined, onPressOut: repeatOnHold ? repeat.stop : undefined, disabled: atMin, hitSlop: 6, accessibilityRole: "button", accessibilityLabel: labels.decrease(label), children: _jsx(Text, { style: merged.buttonText, children: "\u2212" }) }), _jsx(TextInput, { style: merged.input, value: draft ?? String(value), onChangeText: (text) => {
100
+ : undefined, onPressOut: repeatOnHold ? repeat.stop : undefined, disabled: atMin, hitSlop: 6, accessibilityRole: "button", accessibilityLabel: labels.decrease(label), children: _jsx(Text, { style: merged.buttonText, maxFontSizeMultiplier: maxFontSizeMultiplier, children: "\u2212" }) }), _jsx(TextInput, { style: merged.input, value: draft ?? String(value), onChangeText: (text) => {
131
101
  const cleaned = sanitize(text, allowsNegative);
132
102
  const typed = Number(cleaned);
133
103
  if (cleaned === "" || cleaned === "-" || typed < min) {
@@ -142,17 +112,17 @@ export function QuantityStepper({ value, onChange, label, min = 0, max = 999, lo
142
112
  set(draft === "" || draft === "-" ? 0 : Number(draft));
143
113
  },
144
114
  // "number-pad" has no "-" key on Android.
145
- keyboardType: allowsNegative ? "numeric" : "number-pad", selectTextOnFocus: true, maxLength: Math.max(String(min).length, String(max).length), accessibilityLabel: labels.input(label) }), _jsx(Pressable, { style: [merged.button, atMax && merged.buttonDisabled], onPress: tapsOnly
115
+ keyboardType: allowsNegative ? "numeric" : "number-pad", selectTextOnFocus: true, maxFontSizeMultiplier: maxFontSizeMultiplier, maxLength: Math.max(lengthOf(min), lengthOf(max)), accessibilityLabel: labels.input(label) }), _jsx(Pressable, { style: [merged.button, atMax && merged.buttonDisabled], onPress: tapsOnly
146
116
  ? () => {
147
- set(value + 1);
117
+ tap(step);
148
118
  }
149
119
  : undefined, onLongPress: jumps
150
120
  ? () => {
151
- set(value + longPressStep);
121
+ tap(longPressStep);
152
122
  }
153
123
  : undefined, onPressIn: repeatOnHold
154
124
  ? () => {
155
125
  startRepeat(1);
156
126
  }
157
- : undefined, onPressOut: repeatOnHold ? repeat.stop : undefined, disabled: atMax, hitSlop: 6, accessibilityRole: "button", accessibilityLabel: labels.increase(label), children: _jsx(Text, { style: merged.buttonText, children: "+" }) })] }));
127
+ : undefined, onPressOut: repeatOnHold ? repeat.stop : undefined, disabled: atMax, hitSlop: 6, accessibilityRole: "button", accessibilityLabel: labels.increase(label), children: _jsx(Text, { style: merged.buttonText, maxFontSizeMultiplier: maxFontSizeMultiplier, children: "+" }) })] }));
158
128
  }
@@ -0,0 +1,59 @@
1
+ import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ import type { KitLocale } from "../../i18n/locale.js";
3
+ /** Per-slot style overrides for {@link QuantityStepper}, each merged after its default. */
4
+ export interface QuantityStepperStyles {
5
+ /** The row: − button, field, + button. */
6
+ row?: StyleProp<ViewStyle>;
7
+ /** Both round buttons. */
8
+ button?: StyleProp<ViewStyle>;
9
+ /** Added on top of `button` for "−" at `min` and "+" at `max`. */
10
+ buttonDisabled?: StyleProp<ViewStyle>;
11
+ /** The "−" / "+" glyphs. */
12
+ buttonText?: StyleProp<TextStyle>;
13
+ /** The numeric field in the middle (its text color included). */
14
+ input?: StyleProp<TextStyle>;
15
+ }
16
+ /** Screen-reader labels of the three controls, built from the stepper's `label`. */
17
+ export interface QuantityStepperA11yLabels {
18
+ decrease: (label: string) => string;
19
+ increase: (label: string) => string;
20
+ input: (label: string) => string;
21
+ }
22
+ export declare const defaultA11yLabels: Record<KitLocale, QuantityStepperA11yLabels>;
23
+ export declare const defaultStyles: {
24
+ row: {
25
+ flexDirection: "row";
26
+ alignItems: "center";
27
+ gap: number;
28
+ };
29
+ button: {
30
+ width: number;
31
+ height: number;
32
+ borderRadius: number;
33
+ backgroundColor: string;
34
+ alignItems: "center";
35
+ justifyContent: "center";
36
+ };
37
+ buttonDisabled: {
38
+ opacity: number;
39
+ };
40
+ buttonText: {
41
+ color: string;
42
+ fontSize: number;
43
+ fontWeight: "700";
44
+ lineHeight: number;
45
+ };
46
+ input: {
47
+ width: number;
48
+ height: number;
49
+ borderRadius: number;
50
+ borderWidth: number;
51
+ borderColor: string;
52
+ color: string;
53
+ fontSize: number;
54
+ fontWeight: "700";
55
+ textAlign: "center";
56
+ padding: number;
57
+ };
58
+ };
59
+ //# sourceMappingURL=QuantityStepper.styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QuantityStepper.styles.d.ts","sourceRoot":"","sources":["../../../src/components/picker/QuantityStepper.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD,2FAA2F;AAC3F,MAAM,WAAW,qBAAqB;IACpC,0CAA0C;IAC1C,GAAG,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9B,kEAAkE;IAClE,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,4BAA4B;IAC5B,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,iEAAiE;IACjE,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,oFAAoF;AACpF,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAClC;AAED,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,yBAAyB,CAW1E,CAAC;AAEF,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCzB,CAAC"}
@@ -0,0 +1,46 @@
1
+ export const defaultA11yLabels = {
2
+ fr: {
3
+ decrease: (label) => `Retirer : ${label}`,
4
+ increase: (label) => `Ajouter : ${label}`,
5
+ input: (label) => `Quantité : ${label}`,
6
+ },
7
+ en: {
8
+ decrease: (label) => `Decrease: ${label}`,
9
+ increase: (label) => `Increase: ${label}`,
10
+ input: (label) => `Quantity: ${label}`,
11
+ },
12
+ };
13
+ export const defaultStyles = {
14
+ row: {
15
+ flexDirection: "row",
16
+ alignItems: "center",
17
+ gap: 6,
18
+ },
19
+ button: {
20
+ width: 40,
21
+ height: 40,
22
+ borderRadius: 20,
23
+ backgroundColor: "#2563eb",
24
+ alignItems: "center",
25
+ justifyContent: "center",
26
+ },
27
+ buttonDisabled: { opacity: 0.35 },
28
+ buttonText: {
29
+ color: "#ffffff",
30
+ fontSize: 22,
31
+ fontWeight: "700",
32
+ lineHeight: 26,
33
+ },
34
+ input: {
35
+ width: 52,
36
+ height: 40,
37
+ borderRadius: 8,
38
+ borderWidth: 1,
39
+ borderColor: "#d4d4d4",
40
+ color: "#111111",
41
+ fontSize: 18,
42
+ fontWeight: "700",
43
+ textAlign: "center",
44
+ padding: 0,
45
+ },
46
+ };
@@ -7,7 +7,11 @@ export interface UpdateSettingsScreenProps {
7
7
  checkForUpdate: () => Promise<UpdateSettingsScreenRelease | null>;
8
8
  /** Positive when `a` is newer than `b`, e.g. semver comparison. */
9
9
  compareVersions: (a: string, b: string) => number;
10
- downloadAndInstallApk: (apkUrl: string, onProgress?: (fraction: number) => void) => Promise<void>;
10
+ /**
11
+ * Receives the release's `apkSha256`: pass it on as
12
+ * `expectedSha256` so a swapped or damaged APK is never installed.
13
+ */
14
+ downloadAndInstallApk: (apkUrl: string, onProgress?: (fraction: number) => void, apkSha256?: string) => Promise<void>;
11
15
  /** Omit to not show a version history section at all. */
12
16
  fetchReleaseHistory?: () => Promise<UpdateSettingsScreenHistoryEntry[]>;
13
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"UpdateSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/UpdateSettingsScreen.tsx"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,SAAS,EAEf,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EACV,gCAAgC,EAChC,0BAA0B,EAC1B,2BAA2B,EAC3B,0BAA0B,EAC3B,MAAM,kCAAkC,CAAC;AAI1C,YAAY,EACV,gCAAgC,EAChC,0BAA0B,EAC1B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,kCAAkC,CAAC;AAU1C,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,cAAc,EAAE,MAAM,OAAO,CAAC,2BAA2B,GAAG,IAAI,CAAC,CAAC;IAClE,mEAAmE;IACnE,eAAe,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD,qBAAqB,EAAE,CACrB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,KACpC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,yDAAyD;IACzD,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC,gCAAgC,EAAE,CAAC,CAAC;IACxE;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,0BAA0B,CAAC;IACpC;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,0BAA0B,CAAC;CACrC;AAQD,wBAAgB,oBAAoB,CAAC,EACnC,cAAc,EACd,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EACN,MAAM,EACN,MAAM,GACP,EAAE,yBAAyB,+BAuJ3B"}
1
+ {"version":3,"file":"UpdateSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/UpdateSettingsScreen.tsx"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,SAAS,EAEf,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EACV,gCAAgC,EAChC,0BAA0B,EAC1B,2BAA2B,EAC3B,0BAA0B,EAC3B,MAAM,kCAAkC,CAAC;AAI1C,YAAY,EACV,gCAAgC,EAChC,0BAA0B,EAC1B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,kCAAkC,CAAC;AAU1C,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,cAAc,EAAE,MAAM,OAAO,CAAC,2BAA2B,GAAG,IAAI,CAAC,CAAC;IAClE,mEAAmE;IACnE,eAAe,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD;;;OAGG;IACH,qBAAqB,EAAE,CACrB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACvC,SAAS,CAAC,EAAE,MAAM,KACf,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,yDAAyD;IACzD,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC,gCAAgC,EAAE,CAAC,CAAC;IACxE;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,0BAA0B,CAAC;IACpC;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,0BAA0B,CAAC;CACrC;AAQD,wBAAgB,oBAAoB,CAAC,EACnC,cAAc,EACd,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EACN,MAAM,EACN,MAAM,GACP,EAAE,yBAAyB,+BA8J3B"}
@@ -52,12 +52,12 @@ export function UpdateSettingsScreen({ currentVersion, checkForUpdate, compareVe
52
52
  void handleCheckForUpdate();
53
53
  // eslint-disable-next-line @eslint-react/exhaustive-deps -- runs once on mount by design; handleCheckForUpdate is expected to be stable enough for the screen's lifetime.
54
54
  }, []);
55
- async function handleInstall(apkUrl) {
55
+ async function handleInstall({ apkUrl, apkSha256, }) {
56
56
  setUpdateState({ status: "downloading", progress: 0 });
57
57
  try {
58
58
  await downloadAndInstallApk(apkUrl, (progress) => {
59
59
  setUpdateState({ status: "downloading", progress });
60
- });
60
+ }, apkSha256);
61
61
  setUpdateState({ status: "idle" });
62
62
  }
63
63
  catch {
@@ -74,6 +74,6 @@ export function UpdateSettingsScreen({ currentVersion, checkForUpdate, compareVe
74
74
  updateState.status !== "downloading" && (_jsx(Pressable, { style: [merged.button, isBusy && merged.buttonDisabled], onPress: () => {
75
75
  void handleCheckForUpdate();
76
76
  }, disabled: isBusy, accessibilityRole: "button", accessibilityLabel: t.checkButton, children: updateState.status === "checking" ? (_jsx(ActivityIndicator, { color: activityIndicatorColor })) : (_jsx(Text, { style: merged.buttonText, children: t.checkButton })) })), updateState.status === "up-to-date" && (_jsx(Text, { style: merged.helpText, children: t.upToDate })), updateState.status === "error" && (_jsx(Text, { style: merged.errorText, children: updateState.message })), updateState.status === "available" && (_jsxs(View, { style: merged.updateAvailableBox, children: [_jsx(Text, { style: merged.updateAvailableTitle, children: t.availableTitle(updateState.release.version) }), updateState.release.notes ? (_jsx(ChangelogNotes, { notes: updateState.release.notes, styles: merged.notes })) : null, _jsx(Pressable, { style: merged.button, onPress: () => {
77
- void handleInstall(updateState.release.apkUrl);
77
+ void handleInstall(updateState.release);
78
78
  }, accessibilityRole: "button", accessibilityLabel: t.installButtonAccessibilityLabel(updateState.release.version), children: _jsx(Text, { style: merged.buttonText, children: t.installButton(updateState.release.version) }) })] })), updateState.status === "downloading" && (_jsxs(View, { style: merged.updateAvailableBox, children: [_jsx(Text, { style: merged.helpText, children: t.downloadingLabel(Math.round(updateState.progress * 100)) }), _jsx(Text, { style: merged.helpText, children: t.downloadingHint })] })), releaseHistory && releaseHistory.length > 0 && (_jsx(UpdateSettingsScreenHistory, { releaseHistory: releaseHistory, historyTitle: t.historyTitle, dateLocale: dateLocale, notesStyles: notesStyles, styles: merged }))] }));
79
79
  }
@@ -4,6 +4,8 @@ export interface UpdateSettingsScreenRelease {
4
4
  version: string;
5
5
  notes: string;
6
6
  apkUrl: string;
7
+ /** SHA-256 of the APK, handed back to `downloadAndInstallApk`. */
8
+ apkSha256?: string;
7
9
  }
8
10
  export interface UpdateSettingsScreenHistoryEntry {
9
11
  version: string;
@@ -1 +1 @@
1
- {"version":3,"file":"UpdateSettingsScreen.styles.d.ts","sourceRoot":"","sources":["../../../src/components/settings/UpdateSettingsScreen.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAInF,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gCAAgC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,0BAA0B;IACzC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,+BAA+B,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9D,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/B,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAChC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9B,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,kBAAkB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,oBAAoB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5C,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,oBAAoB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5C,gBAAgB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACxC,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,oBAAoB,CAAC;CAC9B;AAED,eAAO,MAAM,aAAa;;;;;;;kCAOI,MAAM;;mDAEW,MAAM;oCAErB,MAAM;;;;;;;;;;kCAYR,MAAM;;mDAEW,MAAM;oCAErB,MAAM;;;;CAI6B,CAAC;AAEpE,eAAO,MAAM,aAAa,EAAE,QAAQ,CAClC,IAAI,CAAC,0BAA0B,EAAE,wBAAwB,GAAG,OAAO,CAAC,CACrE,GACC,IAAI,CAAC,0BAA0B,EAAE,wBAAwB,GAAG,OAAO,CA8DpE,CAAC"}
1
+ {"version":3,"file":"UpdateSettingsScreen.styles.d.ts","sourceRoot":"","sources":["../../../src/components/settings/UpdateSettingsScreen.styles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ import type { BackgroundOption } from "./findBackground.js";
3
+ export type { BackgroundOption } from "./findBackground.js";
4
+ export interface BackgroundPickerStyles {
5
+ /** The wrapping row of swatches. */
6
+ row?: StyleProp<ViewStyle>;
7
+ /** One option: swatch + label. */
8
+ option?: StyleProp<ViewStyle>;
9
+ swatch?: StyleProp<ViewStyle>;
10
+ /** Added on top of `swatch` for the chosen one. */
11
+ swatchSelected?: StyleProp<ViewStyle>;
12
+ card?: StyleProp<ViewStyle>;
13
+ /** The round badge holding the check mark. */
14
+ check?: StyleProp<ViewStyle>;
15
+ label?: StyleProp<TextStyle>;
16
+ labelSelected?: StyleProp<TextStyle>;
17
+ checkIconColor?: string;
18
+ }
19
+ export interface BackgroundPickerProps<T extends BackgroundOption> {
20
+ options: readonly T[];
21
+ /** Id of the chosen option. */
22
+ value: string;
23
+ /** Receives the chosen option — add a haptic tick here if wanted. */
24
+ onChange: (option: T) => void;
25
+ styles?: BackgroundPickerStyles;
26
+ }
27
+ /**
28
+ * Radio group of background swatches: each one draws the page and a card in
29
+ * the colours on offer, the chosen one outlined with a check mark. Pair it
30
+ * with `findBackground` (`./findBackground`) to read the saved id, and check each palette's
31
+ * contrast with `contrastRatio` from `@forthtilliath/ts-kit/color`.
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * <BackgroundPicker
36
+ * options={[
37
+ * { id: "night", label: "Bleu nuit", page: "#0f172a", card: "#1e293b" },
38
+ * { id: "felt", label: "Tapis vert", page: "#0b1f15", card: "#112c1e" },
39
+ * ]}
40
+ * value={settings.background}
41
+ * onChange={(option) => { selectFeedback(); save(option.id); }}
42
+ * />
43
+ * ```
44
+ */
45
+ export declare function BackgroundPicker<T extends BackgroundOption>({ options, value, onChange, styles, }: BackgroundPickerProps<T>): import("react").JSX.Element;
46
+ //# sourceMappingURL=BackgroundPicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackgroundPicker.d.ts","sourceRoot":"","sources":["../../../src/components/theme/BackgroundPicker.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAMpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,sBAAsB;IACrC,oCAAoC;IACpC,GAAG,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3B,kCAAkC;IAClC,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9B,mDAAmD;IACnD,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5B,8CAA8C;IAC9C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB,CAAC,CAAC,SAAS,gBAAgB;IAC/D,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IACtB,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,sBAAsB,CAAC;CACjC;AAwCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EAAE,EAC3D,OAAO,EACP,KAAK,EACL,QAAQ,EACR,MAAM,GACP,EAAE,qBAAqB,CAAC,CAAC,CAAC,+BAoD1B"}
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Pressable, Text, View } from "react-native";
3
+ import { Ionicons } from "@expo/vector-icons";
4
+ import { mergeSlotStyles } from "../../utils/helpers/mergeSlotStyles.js";
5
+ const defaultStyles = {
6
+ row: { flexDirection: "row", flexWrap: "wrap", gap: 8 },
7
+ option: {
8
+ flexGrow: 1,
9
+ flexBasis: "22%",
10
+ alignItems: "center",
11
+ gap: 4,
12
+ },
13
+ swatch: {
14
+ width: "100%",
15
+ aspectRatio: 1,
16
+ borderRadius: 12,
17
+ borderWidth: 2,
18
+ borderColor: "#d4d4d4",
19
+ padding: 8,
20
+ justifyContent: "flex-end",
21
+ },
22
+ swatchSelected: { borderColor: "#2563eb" },
23
+ card: { height: "45%", borderRadius: 6, borderWidth: 1 },
24
+ check: {
25
+ position: "absolute",
26
+ top: 4,
27
+ right: 4,
28
+ width: 22,
29
+ height: 22,
30
+ borderRadius: 11,
31
+ backgroundColor: "#2563eb",
32
+ alignItems: "center",
33
+ justifyContent: "center",
34
+ },
35
+ label: {
36
+ color: "#6b7280",
37
+ fontSize: 13,
38
+ fontWeight: "600",
39
+ },
40
+ labelSelected: { color: "#111827" },
41
+ };
42
+ /**
43
+ * Radio group of background swatches: each one draws the page and a card in
44
+ * the colours on offer, the chosen one outlined with a check mark. Pair it
45
+ * with `findBackground` (`./findBackground`) to read the saved id, and check each palette's
46
+ * contrast with `contrastRatio` from `@forthtilliath/ts-kit/color`.
47
+ *
48
+ * @example
49
+ * ```tsx
50
+ * <BackgroundPicker
51
+ * options={[
52
+ * { id: "night", label: "Bleu nuit", page: "#0f172a", card: "#1e293b" },
53
+ * { id: "felt", label: "Tapis vert", page: "#0b1f15", card: "#112c1e" },
54
+ * ]}
55
+ * value={settings.background}
56
+ * onChange={(option) => { selectFeedback(); save(option.id); }}
57
+ * />
58
+ * ```
59
+ */
60
+ export function BackgroundPicker({ options, value, onChange, styles, }) {
61
+ const merged = mergeSlotStyles(defaultStyles, styles);
62
+ const checkIconColor = styles?.checkIconColor ?? "#ffffff";
63
+ return (_jsx(View, { style: merged.row, accessibilityRole: "radiogroup", children: options.map((option) => {
64
+ const selected = option.id === value;
65
+ return (_jsxs(Pressable, { style: merged.option, onPress: () => {
66
+ onChange(option);
67
+ }, accessibilityRole: "radio", accessibilityState: { checked: selected }, accessibilityLabel: option.label, children: [_jsxs(View, { style: [
68
+ merged.swatch,
69
+ { backgroundColor: option.page },
70
+ selected && merged.swatchSelected,
71
+ ], children: [_jsx(View, { style: [
72
+ merged.card,
73
+ {
74
+ backgroundColor: option.card,
75
+ borderColor: option.cardBorder ?? option.card,
76
+ },
77
+ ] }), selected && (_jsx(View, { style: merged.check, children: _jsx(Ionicons, { name: "checkmark", size: 14, color: checkIconColor }) }))] }), _jsx(Text, { style: [merged.label, selected && merged.labelSelected], numberOfLines: 1, children: option.label })] }, option.id));
78
+ }) }));
79
+ }
@@ -0,0 +1,20 @@
1
+ /** One background on offer to `BackgroundPicker`, drawn as a page with a card on it. */
2
+ export interface BackgroundOption {
3
+ id: string;
4
+ /** Shown under the swatch and read by screen readers. */
5
+ label: string;
6
+ /** The page colour. */
7
+ page: string;
8
+ /** The card drawn on the page. */
9
+ card: string;
10
+ /** The card's border. Defaults to `card`. */
11
+ cardBorder?: string;
12
+ }
13
+ /**
14
+ * The option with this id, or the first one (the original background) if the
15
+ * id is unknown — a background removed since it was saved, or none saved yet.
16
+ *
17
+ * @throws {RangeError} If `options` is empty.
18
+ */
19
+ export declare function findBackground<T extends BackgroundOption>(options: readonly T[], id: string | null | undefined): T;
20
+ //# sourceMappingURL=findBackground.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findBackground.d.ts","sourceRoot":"","sources":["../../../src/components/theme/findBackground.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,gBAAgB,EACvD,OAAO,EAAE,SAAS,CAAC,EAAE,EACrB,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC5B,CAAC,CAIH"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The option with this id, or the first one (the original background) if the
3
+ * id is unknown — a background removed since it was saved, or none saved yet.
4
+ *
5
+ * @throws {RangeError} If `options` is empty.
6
+ */
7
+ export function findBackground(options, id) {
8
+ const found = options.find((option) => option.id === id) ?? options[0];
9
+ if (!found)
10
+ throw new RangeError("findBackground needs at least one option");
11
+ return found;
12
+ }
@@ -1,3 +1,5 @@
1
+ export * from "./BackgroundPicker.js";
2
+ export * from "./findBackground.js";
1
3
  export * from "./ThemeOptionList.js";
2
4
  export * from "./themePreferenceContext.js";
3
5
  export * from "./ThemePreferenceProvider.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/theme/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/theme/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kBAAkB,CAAC"}
@@ -1,3 +1,5 @@
1
+ export * from "./BackgroundPicker.js";
2
+ export * from "./findBackground.js";
1
3
  export * from "./ThemeOptionList.js";
2
4
  export * from "./themePreferenceContext.js";
3
5
  export * from "./ThemePreferenceProvider.js";
@@ -2,4 +2,5 @@ export * from "./useDebouncedChange.js";
2
2
  export * from "./useEffectiveColorScheme.js";
3
3
  export * from "./usePressRepeat.js";
4
4
  export * from "./useSubmitGuard.js";
5
+ export * from "./useViewShare.js";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC"}
@@ -2,3 +2,4 @@ export * from "./useDebouncedChange.js";
2
2
  export * from "./useEffectiveColorScheme.js";
3
3
  export * from "./usePressRepeat.js";
4
4
  export * from "./useSubmitGuard.js";
5
+ export * from "./useViewShare.js";
@@ -0,0 +1,39 @@
1
+ import { type View } from "react-native";
2
+ import { type ShareViewAsImageOptions } from "../utils/helpers/shareViewAsImage.js";
3
+ export interface UseViewShareOptions extends ShareViewAsImageOptions {
4
+ /**
5
+ * Called when the capture or the share fails (capture missing from this
6
+ * binary, no share sheet…). Unset, an alert shows the error's message.
7
+ */
8
+ onError?: (error: unknown) => void;
9
+ /** Title of the default error alert. */
10
+ errorTitle?: string;
11
+ /** Message of the default error alert, for an error without a message. */
12
+ errorMessage?: string;
13
+ }
14
+ /**
15
+ * Shares as an image a view rendered on demand (a scoreboard, a summary card
16
+ * that isn't on screen): `capturing` is `true` while the capture runs — the
17
+ * screen mounts the view attached to `ref` (off screen, `collapsable={false}`)
18
+ * only meanwhile — and `share()` waits two frames for it to be drawn before
19
+ * capturing it with {@link shareViewAsImage}. A second `share()` while one is
20
+ * running is ignored.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * const ranking = useViewShare({ dialogTitle: "Partager le classement" });
25
+ *
26
+ * <Button title="Partager" onPress={() => void ranking.share()} />
27
+ * {ranking.capturing ? (
28
+ * <View ref={ranking.ref} collapsable={false} style={offScreen}>
29
+ * <RankingCard />
30
+ * </View>
31
+ * ) : null}
32
+ * ```
33
+ */
34
+ export declare function useViewShare({ onError, errorTitle, errorMessage, ...options }?: UseViewShareOptions): {
35
+ ref: import("react").RefObject<View | null>;
36
+ capturing: boolean;
37
+ share: () => Promise<void>;
38
+ };
39
+ //# sourceMappingURL=useViewShare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useViewShare.d.ts","sourceRoot":"","sources":["../../src/hooks/useViewShare.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,KAAK,IAAI,EAAE,MAAM,cAAc,CAAC;AAGhD,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,sCAAsC,CAAC;AAE9C,MAAM,WAAW,mBAAoB,SAAQ,uBAAuB;IAClE;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA2BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,UAAU,EACV,YAAY,EACZ,GAAG,OAAO,EACX,GAAE,mBAAwB;;;;EA+B1B"}