@applicaster/zapp-react-native-utils 15.0.0-alpha.1202274105 → 15.0.0-alpha.1257410812
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/appUtils/contextKeysManager/__tests__/getKeys/failure.test.ts +7 -2
- package/appUtils/contextKeysManager/__tests__/getKeys/success.test.ts +48 -0
- package/appUtils/contextKeysManager/contextResolver.ts +51 -22
- package/appUtils/contextKeysManager/index.ts +65 -10
- package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +38 -0
- package/configurationUtils/index.ts +17 -11
- package/manifestUtils/defaultManifestConfigurations/player.js +29 -0
- package/package.json +2 -2
- package/reactHooks/state/useRefWithInitialValue.ts +10 -0
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { NativeModules } from "react-native";
|
|
1
2
|
import { ContextKeysManager } from "../..";
|
|
2
3
|
|
|
3
4
|
describe("Context Keys Manager - getKeys", () => {
|
|
4
|
-
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
delete NativeModules.ContextResolverBridge;
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("returns null if all keys are invalid 1", async () => {
|
|
5
10
|
// setup
|
|
6
11
|
const keys = [null];
|
|
7
12
|
|
|
@@ -24,7 +29,7 @@ describe("Context Keys Manager - getKeys", () => {
|
|
|
24
29
|
expect(getKey.mock.calls).toEqual([[null]]);
|
|
25
30
|
});
|
|
26
31
|
|
|
27
|
-
it("returns null if all keys are invalid", async () => {
|
|
32
|
+
it("returns null if all keys are invalid 2", async () => {
|
|
28
33
|
// setup
|
|
29
34
|
const keys = [null, undefined];
|
|
30
35
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { NativeModules } from "react-native";
|
|
1
2
|
import { ContextKeysManager } from "../..";
|
|
2
3
|
|
|
3
4
|
describe("Context Keys Manager - getKeys", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
delete NativeModules.ContextResolverBridge;
|
|
7
|
+
});
|
|
8
|
+
|
|
4
9
|
it("returns value if found by getKey", async () => {
|
|
5
10
|
// setup
|
|
6
11
|
const keys = ["namespace.key"];
|
|
@@ -165,4 +170,47 @@ describe("Context Keys Manager - getKeys", () => {
|
|
|
165
170
|
expect(getKey).toHaveBeenCalledTimes(2);
|
|
166
171
|
expect(getKey.mock.calls).toEqual([[keys[0]], [keys[1]]]);
|
|
167
172
|
});
|
|
173
|
+
|
|
174
|
+
it("returns values from native bridge when available", async () => {
|
|
175
|
+
// setup
|
|
176
|
+
const keys = ["namespace.key1", "namespace.key2"];
|
|
177
|
+
|
|
178
|
+
const contextObj = [
|
|
179
|
+
{ key: keys[0], required: true },
|
|
180
|
+
{ key: keys[1], required: false },
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
const resolved = {
|
|
184
|
+
[keys[0]]: "value1",
|
|
185
|
+
[keys[1]]: "value2",
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const reactNative = require("react-native");
|
|
189
|
+
|
|
190
|
+
const bridgeMock = {
|
|
191
|
+
resolveContextKeys: jest.fn().mockResolvedValue(resolved),
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
reactNative.__setContextResolverBridgeMock(bridgeMock);
|
|
195
|
+
|
|
196
|
+
const contextManager = new ContextKeysManager({});
|
|
197
|
+
const getKey = jest.spyOn(contextManager, "getKey");
|
|
198
|
+
|
|
199
|
+
// run
|
|
200
|
+
const result = await contextManager.getKeys(keys, contextObj);
|
|
201
|
+
|
|
202
|
+
// verify
|
|
203
|
+
const map = new Map();
|
|
204
|
+
map.set(keys[0], "value1");
|
|
205
|
+
map.set(keys[1], "value2");
|
|
206
|
+
|
|
207
|
+
expect(result).toEqual(map);
|
|
208
|
+
|
|
209
|
+
expect(bridgeMock.resolveContextKeys).toHaveBeenCalledWith({
|
|
210
|
+
[keys[0]]: true,
|
|
211
|
+
[keys[1]]: false,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
expect(getKey).not.toHaveBeenCalled();
|
|
215
|
+
});
|
|
168
216
|
});
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { ContextKeysManager } from "./index";
|
|
2
|
-
import
|
|
3
|
-
import * as _ from "lodash";
|
|
2
|
+
import { get, values } from "@applicaster/zapp-react-native-utils/utils";
|
|
4
3
|
import { useScreenStateStore } from "../../reactHooks/navigation/useScreenStateStore";
|
|
5
4
|
|
|
5
|
+
const contextKeyPattern = /@{([^/]+)\/([^}]*)}/;
|
|
6
|
+
const contextVariablePattern = /@\{([^}]*)\}/g;
|
|
7
|
+
|
|
6
8
|
export interface IResolver {
|
|
7
9
|
resolve: (string) => Promise<string | number | object>;
|
|
8
10
|
}
|
|
@@ -20,7 +22,7 @@ export class EntryResolver implements IResolver {
|
|
|
20
22
|
return this.entry;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
return
|
|
25
|
+
return get(this.entry, key.split("."));
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -39,7 +41,7 @@ export class ScreenStateResolver implements IResolver {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
if (key.includes(".")) {
|
|
42
|
-
return
|
|
44
|
+
return get(screenState, key.split("."));
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
return screenState?.[key];
|
|
@@ -61,21 +63,16 @@ export const resolveObjectValues = async (
|
|
|
61
63
|
|
|
62
64
|
const resolvedEntries = await Promise.all(
|
|
63
65
|
entries.map(async ([key, value]) => {
|
|
64
|
-
if (typeof value !== "string") {
|
|
66
|
+
if (typeof value !== "string" || !value.startsWith("@")) {
|
|
65
67
|
return [key, value];
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
return [key, value];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const regex = /@{([^/]+)\/([^}]*)}/;
|
|
70
|
+
const regex = contextKeyPattern;
|
|
73
71
|
|
|
74
72
|
const match = (value as string).match(regex);
|
|
75
73
|
|
|
76
74
|
if (match) {
|
|
77
|
-
const contextResolverName = match
|
|
78
|
-
const compositeKey = match[2];
|
|
75
|
+
const [_, contextResolverName, compositeKey] = match;
|
|
79
76
|
|
|
80
77
|
const resolver = exResolvers[contextResolverName] || exResolvers.ctx;
|
|
81
78
|
const resolvedValue = await resolver.resolve(compositeKey);
|
|
@@ -90,18 +87,50 @@ export const resolveObjectValues = async (
|
|
|
90
87
|
return Object.fromEntries(resolvedEntries);
|
|
91
88
|
};
|
|
92
89
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
// Simple memoization cache
|
|
91
|
+
const extractAtValuesCache = new Map<any, string[]>();
|
|
92
|
+
|
|
93
|
+
const flatMapDeep = <T, U>(arr: T[], fn: (value: T) => U | U[]): U[] => {
|
|
94
|
+
const result: U[] = [];
|
|
97
95
|
|
|
98
|
-
|
|
96
|
+
const flatten = (items: any) => {
|
|
97
|
+
for (const item of items) {
|
|
98
|
+
if (Array.isArray(item)) {
|
|
99
|
+
flatten(item);
|
|
100
|
+
} else {
|
|
101
|
+
result.push(item);
|
|
102
|
+
}
|
|
99
103
|
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
flatten(arr.map(fn));
|
|
100
107
|
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
return result;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const extractAtValues = (input: any): string[] => {
|
|
112
|
+
if (extractAtValuesCache.has(input)) {
|
|
113
|
+
return extractAtValuesCache.get(input)!;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const result = flatMapDeep(
|
|
117
|
+
Array.isArray(input) ? input : [input],
|
|
118
|
+
(value: any) => {
|
|
119
|
+
if (typeof value === "string") {
|
|
120
|
+
const matches = value.match(contextVariablePattern);
|
|
121
|
+
|
|
122
|
+
return matches ? matches.map((match) => match.slice(2, -1)) : [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (typeof value === "object" && value !== null) {
|
|
126
|
+
return extractAtValues(values(value));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return [];
|
|
103
130
|
}
|
|
131
|
+
);
|
|
104
132
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
133
|
+
extractAtValuesCache.set(input, result);
|
|
134
|
+
|
|
135
|
+
return result;
|
|
136
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { isNil } from "@applicaster/zapp-react-native-utils/utils";
|
|
2
2
|
|
|
3
3
|
import { StorageLevel, StorageType } from "./consts";
|
|
4
4
|
import {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
localStorage as LocalStorage,
|
|
14
14
|
secureStorage as SecureStorage,
|
|
15
15
|
} from "./storage";
|
|
16
|
+
import { NativeModules } from "react-native";
|
|
16
17
|
|
|
17
18
|
export { StorageLevel };
|
|
18
19
|
|
|
@@ -154,11 +155,65 @@ export class ContextKeysManager {
|
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
public async getKeys(
|
|
157
|
-
keys: Array<KeyName
|
|
158
|
+
keys: Array<KeyName>,
|
|
159
|
+
contextObj?: { key: string; required?: boolean }[]
|
|
158
160
|
): Promise<Map<KeyName, ValueOrNothing>> {
|
|
159
|
-
|
|
161
|
+
if (NativeModules.ContextResolverBridge) {
|
|
162
|
+
const keysObj: Record<string, boolean> = {};
|
|
160
163
|
|
|
161
|
-
|
|
164
|
+
const requiredByKey = new Map(
|
|
165
|
+
contextObj?.map((item) => [item.key, item.required ?? false]) ?? []
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
let hasResolvableKeys = false;
|
|
169
|
+
|
|
170
|
+
const normalizedKeys: Array<{
|
|
171
|
+
original: KeyName;
|
|
172
|
+
normalized: string | null;
|
|
173
|
+
}> = [];
|
|
174
|
+
|
|
175
|
+
for (const key of keys) {
|
|
176
|
+
const {
|
|
177
|
+
isValid: isValidKey,
|
|
178
|
+
key: parsedKey,
|
|
179
|
+
namespace: parsedNamespace,
|
|
180
|
+
} = this.parseKey(key);
|
|
181
|
+
|
|
182
|
+
if (!isValidKey) {
|
|
183
|
+
normalizedKeys.push({ original: key, normalized: null });
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const normalized = buildNamespaceKey(parsedKey, parsedNamespace);
|
|
188
|
+
|
|
189
|
+
keysObj[normalized] = requiredByKey.get(normalized) ?? false;
|
|
190
|
+
hasResolvableKeys = true;
|
|
191
|
+
normalizedKeys.push({ original: key, normalized });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!hasResolvableKeys) {
|
|
195
|
+
return new Map(
|
|
196
|
+
normalizedKeys.map(({ original }) => [original, NOTHING])
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const res =
|
|
201
|
+
await NativeModules.ContextResolverBridge.resolveContextKeys(keysObj);
|
|
202
|
+
|
|
203
|
+
const resolved = res ?? {};
|
|
204
|
+
|
|
205
|
+
return new Map(
|
|
206
|
+
normalizedKeys.map(({ original, normalized }) => {
|
|
207
|
+
if (!normalized) return [original, NOTHING];
|
|
208
|
+
|
|
209
|
+
return [original, resolved[normalized] ?? NOTHING];
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
const values = await Promise.all(keys.map((key) => this.getKey(key)));
|
|
214
|
+
|
|
215
|
+
return new Map(keys.map((key, index) => [key, values[index]]));
|
|
216
|
+
}
|
|
162
217
|
}
|
|
163
218
|
|
|
164
219
|
public async getKey(key: KeyName): Promise<ValueOrNothing> {
|
|
@@ -181,7 +236,7 @@ export class ContextKeysManager {
|
|
|
181
236
|
parsedNamespace
|
|
182
237
|
);
|
|
183
238
|
|
|
184
|
-
if (!
|
|
239
|
+
if (!isNil(resultByReference)) {
|
|
185
240
|
return resultByReference;
|
|
186
241
|
}
|
|
187
242
|
|
|
@@ -192,7 +247,7 @@ export class ContextKeysManager {
|
|
|
192
247
|
parsedNamespace
|
|
193
248
|
);
|
|
194
249
|
|
|
195
|
-
if (!
|
|
250
|
+
if (!isNil(resultFromSessionStorage)) {
|
|
196
251
|
return resultFromSessionStorage;
|
|
197
252
|
}
|
|
198
253
|
|
|
@@ -201,7 +256,7 @@ export class ContextKeysManager {
|
|
|
201
256
|
parsedNamespace
|
|
202
257
|
);
|
|
203
258
|
|
|
204
|
-
if (!
|
|
259
|
+
if (!isNil(resultFromLocalStorage)) {
|
|
205
260
|
return resultFromLocalStorage;
|
|
206
261
|
}
|
|
207
262
|
|
|
@@ -210,7 +265,7 @@ export class ContextKeysManager {
|
|
|
210
265
|
parsedNamespace
|
|
211
266
|
);
|
|
212
267
|
|
|
213
|
-
if (!
|
|
268
|
+
if (!isNil(resultFromSecureStorage)) {
|
|
214
269
|
return resultFromSecureStorage;
|
|
215
270
|
}
|
|
216
271
|
|
|
@@ -275,7 +330,7 @@ export class ContextKeysManager {
|
|
|
275
330
|
keysProp.map((keyProp) => this.setKey(keyProp))
|
|
276
331
|
);
|
|
277
332
|
|
|
278
|
-
return new Map(
|
|
333
|
+
return new Map(keys.map((key, index) => [key, values[index]]));
|
|
279
334
|
}
|
|
280
335
|
|
|
281
336
|
// when succeed saving - return true
|
|
@@ -384,7 +439,7 @@ export class ContextKeysManager {
|
|
|
384
439
|
public async removeKeys(keys: KeyName[]): Promise<Map<KeyName, boolean>> {
|
|
385
440
|
const values = await Promise.all(keys.map((key) => this.removeKey(key)));
|
|
386
441
|
|
|
387
|
-
return new Map(
|
|
442
|
+
return new Map(keys.map((key, index) => [key, values[index]]));
|
|
388
443
|
}
|
|
389
444
|
// REMOVE
|
|
390
445
|
}
|
|
@@ -31,4 +31,42 @@ describe("imageSrcFromMediaItem", () => {
|
|
|
31
31
|
|
|
32
32
|
expect(imageSrcFromMediaItem(badEntry, ["image_base"])).toBeUndefined();
|
|
33
33
|
});
|
|
34
|
+
|
|
35
|
+
it("returns undefined when fallback is false and key is not found", () => {
|
|
36
|
+
const result = imageSrcFromMediaItem(entry as ZappEntry, [
|
|
37
|
+
"does_not_exist",
|
|
38
|
+
false,
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
expect(result).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("returns src when fallback is false and key is found", () => {
|
|
45
|
+
const result = imageSrcFromMediaItem(entry as ZappEntry, [
|
|
46
|
+
"logo_thumbnail",
|
|
47
|
+
false,
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
expect(result).toEqual(entry.media_group[1].media_item[0].src);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("returns image_base as fallback when fallback is explicitly true and key is not found", () => {
|
|
54
|
+
const result = imageSrcFromMediaItem(entry as ZappEntry, [
|
|
55
|
+
"does_not_exist",
|
|
56
|
+
true,
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
const fallback = entry.media_group[0].media_item[0];
|
|
60
|
+
expect(result).toEqual(fallback.src);
|
|
61
|
+
expect(fallback.key).toBe("image_base");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("returns src when fallback is explicitly true and key is found", () => {
|
|
65
|
+
const result = imageSrcFromMediaItem(entry as ZappEntry, [
|
|
66
|
+
"logo_thumbnail",
|
|
67
|
+
true,
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
expect(result).toEqual(entry.media_group[1].media_item[0].src);
|
|
71
|
+
});
|
|
34
72
|
});
|
|
@@ -155,9 +155,9 @@ export function getMediaItems(entry: ZappEntry): Option<ZappMediaItem[]> {
|
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
157
|
* Retrieves the "src" value from a media item in the entry's media group,
|
|
158
|
-
* based on a provided key, with fallback logic.
|
|
158
|
+
* based on a provided key, with optional fallback logic.
|
|
159
159
|
*
|
|
160
|
-
* Fallback order:
|
|
160
|
+
* Fallback order (when enabled):
|
|
161
161
|
* 1. Attempts to find a media item with the specified key (or "image_base" if none provided).
|
|
162
162
|
* 2. If not found, attempts to find a media item with the key "image_base".
|
|
163
163
|
* 3. If still not found, falls back to the first available media item.
|
|
@@ -166,15 +166,19 @@ export function getMediaItems(entry: ZappEntry): Option<ZappMediaItem[]> {
|
|
|
166
166
|
* since empty URIs are invalid in some platforms (e.g., React Native).
|
|
167
167
|
*
|
|
168
168
|
* @param {ZappEntry} entry - The entry object containing a media group.
|
|
169
|
-
* @param {string[] | unknown} arg -
|
|
169
|
+
* @param {string[] | unknown} arg - Can be an array or any other value (treated as empty array).
|
|
170
|
+
* When an array:
|
|
171
|
+
* - First element: The key to look up. If omitted or undefined, defaults to "image_base".
|
|
172
|
+
* - Second element: Boolean to enable/disable fallback logic. If omitted or undefined, defaults to true.
|
|
170
173
|
* @returns {?string} The "src" URI from the matched media item, or undefined if not found or empty.
|
|
171
174
|
*/
|
|
172
175
|
export function imageSrcFromMediaItem(
|
|
173
176
|
entry: ZappEntry,
|
|
174
177
|
arg: string[] | unknown
|
|
175
178
|
): Option<string> {
|
|
176
|
-
const args:
|
|
179
|
+
const args: any = R.unless(Array.isArray, Array)(arg || []);
|
|
177
180
|
const imageKey: string = args?.[0] || "image_base"; // always a single key in this function
|
|
181
|
+
const fallback: boolean = args?.[1] !== false;
|
|
178
182
|
|
|
179
183
|
const mediaItems = getMediaItems(entry);
|
|
180
184
|
|
|
@@ -185,14 +189,16 @@ export function imageSrcFromMediaItem(
|
|
|
185
189
|
// Try to find the item with the given key
|
|
186
190
|
let foundItem = mediaItems.find((item) => item.key === imageKey);
|
|
187
191
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
+
if (fallback) {
|
|
193
|
+
// If not found and key was not "image_base", try to find "image_base"
|
|
194
|
+
if (!foundItem && imageKey !== "image_base") {
|
|
195
|
+
foundItem = mediaItems.find((item) => item.key === "image_base");
|
|
196
|
+
}
|
|
192
197
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
// If still not found, default to first item
|
|
199
|
+
if (!foundItem) {
|
|
200
|
+
foundItem = mediaItems[0];
|
|
201
|
+
}
|
|
196
202
|
}
|
|
197
203
|
|
|
198
204
|
const src = foundItem?.src;
|
|
@@ -331,6 +331,35 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
331
331
|
label_tooltip: "Hint for skip intro button accessibility",
|
|
332
332
|
type: "text_input",
|
|
333
333
|
},
|
|
334
|
+
{
|
|
335
|
+
type: "text_input",
|
|
336
|
+
label: "Text Tracks Label",
|
|
337
|
+
key: "text_tracks_label",
|
|
338
|
+
initial_value: "Subtitles",
|
|
339
|
+
label_tooltip: "Label for the text tracks list",
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
type: "text_input",
|
|
343
|
+
label: "Audio Tracks Label",
|
|
344
|
+
key: "audio_tracks_label",
|
|
345
|
+
initial_value: "Audio",
|
|
346
|
+
label_tooltip: "Label for the audio tracks list",
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
type: "text_input",
|
|
350
|
+
label: "Off Track Label",
|
|
351
|
+
key: "off_track_label",
|
|
352
|
+
initial_value: "Off",
|
|
353
|
+
label_tooltip: "Label for the 'Off' option in text tracks list",
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
type: "text_input",
|
|
357
|
+
label: "Default Track Label",
|
|
358
|
+
key: "default_track_label",
|
|
359
|
+
initial_value: "Default",
|
|
360
|
+
label_tooltip:
|
|
361
|
+
"Label for the 'Default' audio track in audio tracks list",
|
|
362
|
+
},
|
|
334
363
|
],
|
|
335
364
|
};
|
|
336
365
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "15.0.0-alpha.
|
|
3
|
+
"version": "15.0.0-alpha.1257410812",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@applicaster/applicaster-types": "15.0.0-alpha.
|
|
30
|
+
"@applicaster/applicaster-types": "15.0.0-alpha.1257410812",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
+
type WithOptionalCleanup = {
|
|
4
|
+
cleanup?: () => void;
|
|
5
|
+
};
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* returns a ref with an initial value that is lazily evaluated once
|
|
5
9
|
* */
|
|
@@ -10,5 +14,11 @@ export const useRefWithInitialValue = <T>(initialValueGetter: () => T) => {
|
|
|
10
14
|
ref.current = initialValueGetter();
|
|
11
15
|
}
|
|
12
16
|
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
return () => {
|
|
19
|
+
(ref.current as unknown as WithOptionalCleanup)?.cleanup?.();
|
|
20
|
+
};
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
13
23
|
return ref;
|
|
14
24
|
};
|