@applicaster/zapp-react-native-utils 16.0.0-alpha.6088600543 → 16.0.0-alpha.6409114856
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/actionsExecutor/ActionExecutor.ts +8 -10
- package/actionsExecutor/ActionExecutorContext.tsx +40 -345
- package/actionsExecutor/actions/appRestart.ts +24 -0
- package/actionsExecutor/actions/confirmDialog.ts +53 -0
- package/actionsExecutor/actions/index.ts +30 -0
- package/actionsExecutor/actions/localStorageRemove.ts +27 -0
- package/actionsExecutor/actions/localStorageSet.ts +28 -0
- package/actionsExecutor/actions/localStorageToggleFlag.ts +28 -0
- package/actionsExecutor/actions/navigateToScreen.ts +123 -0
- package/actionsExecutor/actions/refreshComponent.ts +79 -0
- package/actionsExecutor/actions/screenSetVariable.ts +35 -0
- package/actionsExecutor/actions/screenToggleFlag.ts +38 -0
- package/actionsExecutor/actions/sendCloudEvent.ts +88 -0
- package/actionsExecutor/actions/sessionStorageRemove.ts +19 -0
- package/actionsExecutor/actions/sessionStorageSet.ts +20 -0
- package/actionsExecutor/actions/sessionStorageToggleFlag.ts +15 -0
- package/actionsExecutor/actions/showToast.ts +31 -0
- package/actionsExecutor/actions/switchLayout.ts +40 -0
- package/actionsExecutor/types.ts +59 -0
- package/analyticsUtils/PlayerAnalyticsManager.ts +1 -1
- package/appUtils/contextKeysManager/__tests__/getKey/failure.test.ts +1 -1
- package/appUtils/contextKeysManager/__tests__/removeKey/failure.test.ts +1 -1
- package/appUtils/contextKeysManager/__tests__/setKey/failure/invalidKey.test.ts +4 -4
- package/appUtils/contextKeysManager/index.ts +1 -1
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +12 -6
- package/appUtils/playerManager/OverlayObserver/__tests__/utils.test.ts +41 -0
- package/appUtils/playerManager/OverlayObserver/utils.ts +3 -0
- package/appUtils/playerManager/__tests__/playerFactory.test.ts +1 -1
- package/appUtils/playerManager/{conts.ts → const.ts} +20 -0
- package/appUtils/playerManager/index.ts +1 -1
- package/appUtils/playerManager/player.ts +1 -1
- package/appUtils/playerManager/playerFactory.ts +1 -1
- package/appUtils/playerManager/playerNative.ts +1 -1
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +1 -1
- package/appUtils/playerManager/userLanguagePreference.ts +1 -1
- package/arrayUtils/__tests__/{anyThruthy.test.ts → anyTruthy.test.ts} +8 -0
- package/arrayUtils/__tests__/arrayUtils.test.ts +165 -108
- package/arrayUtils/__tests__/isEmptyArray.test.ts +11 -0
- package/arrayUtils/__tests__/isFilledArray.test.ts +12 -0
- package/arrayUtils/__tests__/isFirst.test.ts +17 -0
- package/arrayUtils/__tests__/isIndexInRange.test.ts +14 -0
- package/arrayUtils/__tests__/isLast.test.ts +31 -0
- package/arrayUtils/__tests__/makeListOf.test.ts +31 -0
- package/arrayUtils/__tests__/makeListOfIndexes.test.ts +20 -0
- package/arrayUtils/__tests__/sample.test.ts +61 -0
- package/arrayUtils/index.ts +57 -20
- package/bottomSheet/__tests__/scrollContext.test.tsx +35 -0
- package/bottomSheet/index.ts +17 -0
- package/manifestUtils/__tests__/player.audioControls.test.js +158 -0
- package/manifestUtils/_internals/index.js +6 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +161 -19
- package/manifestUtils/fieldUtils/__tests__/fieldUtils.test.js +84 -0
- package/manifestUtils/fieldUtils/index.js +125 -0
- package/manifestUtils/keys.js +8 -0
- package/manifestUtils/mobileAction/button/index.js +16 -0
- package/manifestUtils/mobileAction/container/index.js +3 -1
- package/manifestUtils/mobileAction/groups/defaults.js +3 -1
- package/numberUtils/__tests__/isMinusZero.test.ts +20 -0
- package/numberUtils/__tests__/isZero.test.ts +27 -0
- package/numberUtils/__tests__/requireNumber.test.ts +50 -0
- package/numberUtils/__tests__/toNumber.test.ts +27 -0
- package/numberUtils/__tests__/toNumberWithDefault.test.ts +64 -0
- package/numberUtils/__tests__/toNumberWithDefaultZero.test.ts +48 -0
- package/numberUtils/index.ts +27 -8
- package/package.json +2 -2
- package/reactHooks/actions/index.ts +51 -1
- package/reactHooks/cell-click/index.ts +3 -3
- package/reactHooks/feed/__tests__/useInflatedUrl.test.tsx +25 -0
- package/reactHooks/layout/index.ts +1 -1
- package/reactHooks/navigation/__mocks__/index.ts +4 -0
- package/reactHooks/navigation/__tests__/useIsScreenActive.test.ts +42 -0
- package/reactHooks/navigation/index.ts +1 -1
- package/reactHooks/navigation/useIsScreenActive.ts +29 -8
- package/reactHooks/state/useComponentScreenState.ts +1 -1
- package/stringUtils/__tests__/stringUtils.test.js +42 -0
- package/stringUtils/index.ts +2 -2
- package/uiActionsRegistrator/index.ts +203 -0
- package/zappFrameworkUtils/__tests__/localStorageHelper.test.ts +146 -0
- package/zappFrameworkUtils/localStorageHelper.ts +9 -13
|
@@ -383,7 +383,45 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
383
383
|
initial_value: "Start Over",
|
|
384
384
|
type: "text_input",
|
|
385
385
|
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (isMobile(platform)) {
|
|
389
|
+
// Screen reader strings for the audio player's playlist controls. Mobile
|
|
390
|
+
// only, because the audio player layout itself is mobile only.
|
|
391
|
+
localizations.fields.push({
|
|
392
|
+
key: "accessibility_previous_track_label",
|
|
393
|
+
label: "Accessibility previous track label",
|
|
394
|
+
initial_value: "Previous track button",
|
|
395
|
+
label_tooltip: "Label for previous track button accessibility",
|
|
396
|
+
type: "text_input",
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
localizations.fields.push({
|
|
400
|
+
key: "accessibility_previous_track_hint",
|
|
401
|
+
label: "Accessibility previous track hint",
|
|
402
|
+
initial_value: "Press to play the previous track",
|
|
403
|
+
label_tooltip: "Hint for previous track button accessibility",
|
|
404
|
+
type: "text_input",
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
localizations.fields.push({
|
|
408
|
+
key: "accessibility_next_track_label",
|
|
409
|
+
label: "Accessibility next track label",
|
|
410
|
+
initial_value: "Next track button",
|
|
411
|
+
label_tooltip: "Label for next track button accessibility",
|
|
412
|
+
type: "text_input",
|
|
413
|
+
});
|
|
386
414
|
|
|
415
|
+
localizations.fields.push({
|
|
416
|
+
key: "accessibility_next_track_hint",
|
|
417
|
+
label: "Accessibility next track hint",
|
|
418
|
+
initial_value: "Press to play the next track",
|
|
419
|
+
label_tooltip: "Hint for next track button accessibility",
|
|
420
|
+
type: "text_input",
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (isTV(platform)) {
|
|
387
425
|
styles.fields.push(
|
|
388
426
|
fieldsGroup("Always Show Scrub Bar & Timestamp", "", [
|
|
389
427
|
{
|
|
@@ -781,21 +819,6 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
781
819
|
}
|
|
782
820
|
);
|
|
783
821
|
|
|
784
|
-
if (
|
|
785
|
-
platform.includes("android") ||
|
|
786
|
-
platform.includes("amazon") ||
|
|
787
|
-
platform.includes("ios") ||
|
|
788
|
-
platform.includes("tvos")
|
|
789
|
-
) {
|
|
790
|
-
general.fields.push({
|
|
791
|
-
key: "user_agent",
|
|
792
|
-
label: "User agent",
|
|
793
|
-
initial_value: "",
|
|
794
|
-
type: "text_input",
|
|
795
|
-
label_tooltip: "Override default user agent string",
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
|
|
799
822
|
if (platform === "android_for_quickbrick") {
|
|
800
823
|
general.fields.push({
|
|
801
824
|
key: "stop_playback_on_task_removal",
|
|
@@ -1236,6 +1259,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1236
1259
|
type: "uploader",
|
|
1237
1260
|
label_tooltip: "Playback Speed 0.8x",
|
|
1238
1261
|
initial_value: null,
|
|
1262
|
+
rules: "conditional",
|
|
1263
|
+
conditional_fields: [
|
|
1264
|
+
{
|
|
1265
|
+
key: "styles/player_controls_layout",
|
|
1266
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1267
|
+
},
|
|
1268
|
+
],
|
|
1239
1269
|
},
|
|
1240
1270
|
{
|
|
1241
1271
|
key: "speed_1",
|
|
@@ -1243,6 +1273,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1243
1273
|
type: "uploader",
|
|
1244
1274
|
label_tooltip: "Playback Speed 1x",
|
|
1245
1275
|
initial_value: null,
|
|
1276
|
+
rules: "conditional",
|
|
1277
|
+
conditional_fields: [
|
|
1278
|
+
{
|
|
1279
|
+
key: "styles/player_controls_layout",
|
|
1280
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1281
|
+
},
|
|
1282
|
+
],
|
|
1246
1283
|
},
|
|
1247
1284
|
{
|
|
1248
1285
|
key: "speed_1_2",
|
|
@@ -1250,6 +1287,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1250
1287
|
type: "uploader",
|
|
1251
1288
|
label_tooltip: "Playback Speed 1.2x",
|
|
1252
1289
|
initial_value: null,
|
|
1290
|
+
rules: "conditional",
|
|
1291
|
+
conditional_fields: [
|
|
1292
|
+
{
|
|
1293
|
+
key: "styles/player_controls_layout",
|
|
1294
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1295
|
+
},
|
|
1296
|
+
],
|
|
1253
1297
|
},
|
|
1254
1298
|
{
|
|
1255
1299
|
key: "speed_1_5",
|
|
@@ -1257,6 +1301,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1257
1301
|
type: "uploader",
|
|
1258
1302
|
label_tooltip: "Playback Speed 1.5x",
|
|
1259
1303
|
initial_value: null,
|
|
1304
|
+
rules: "conditional",
|
|
1305
|
+
conditional_fields: [
|
|
1306
|
+
{
|
|
1307
|
+
key: "styles/player_controls_layout",
|
|
1308
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1309
|
+
},
|
|
1310
|
+
],
|
|
1260
1311
|
},
|
|
1261
1312
|
{
|
|
1262
1313
|
key: "speed_2",
|
|
@@ -1264,6 +1315,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1264
1315
|
type: "uploader",
|
|
1265
1316
|
label_tooltip: "Playback Speed 2x",
|
|
1266
1317
|
initial_value: null,
|
|
1318
|
+
rules: "conditional",
|
|
1319
|
+
conditional_fields: [
|
|
1320
|
+
{
|
|
1321
|
+
key: "styles/player_controls_layout",
|
|
1322
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1323
|
+
},
|
|
1324
|
+
],
|
|
1267
1325
|
},
|
|
1268
1326
|
{
|
|
1269
1327
|
key: "sleep_timer",
|
|
@@ -1271,6 +1329,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1271
1329
|
type: "uploader",
|
|
1272
1330
|
label_tooltip: "Sleep Timer Default",
|
|
1273
1331
|
initial_value: null,
|
|
1332
|
+
rules: "conditional",
|
|
1333
|
+
conditional_fields: [
|
|
1334
|
+
{
|
|
1335
|
+
key: "styles/player_controls_layout",
|
|
1336
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1337
|
+
},
|
|
1338
|
+
],
|
|
1274
1339
|
},
|
|
1275
1340
|
{
|
|
1276
1341
|
key: "sleep_timer_active",
|
|
@@ -1278,6 +1343,43 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1278
1343
|
type: "uploader",
|
|
1279
1344
|
label_tooltip: "Sleep Timer Active",
|
|
1280
1345
|
initial_value: null,
|
|
1346
|
+
rules: "conditional",
|
|
1347
|
+
conditional_fields: [
|
|
1348
|
+
{
|
|
1349
|
+
key: "styles/player_controls_layout",
|
|
1350
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1351
|
+
},
|
|
1352
|
+
],
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
key: "previous_track",
|
|
1356
|
+
label: "Previous Track Control Icon",
|
|
1357
|
+
type: "uploader",
|
|
1358
|
+
label_tooltip:
|
|
1359
|
+
"Override the default Previous Track icon. Recommended size 120x120px (@3x).",
|
|
1360
|
+
initial_value: null,
|
|
1361
|
+
rules: "conditional",
|
|
1362
|
+
conditional_fields: [
|
|
1363
|
+
{
|
|
1364
|
+
key: "styles/player_controls_layout",
|
|
1365
|
+
condition_value: "playlist_controls",
|
|
1366
|
+
},
|
|
1367
|
+
],
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
key: "next_track",
|
|
1371
|
+
label: "Next Track Control Icon",
|
|
1372
|
+
type: "uploader",
|
|
1373
|
+
label_tooltip:
|
|
1374
|
+
"Override the default Next Track icon. Recommended size 120x120px (@3x).",
|
|
1375
|
+
initial_value: null,
|
|
1376
|
+
rules: "conditional",
|
|
1377
|
+
conditional_fields: [
|
|
1378
|
+
{
|
|
1379
|
+
key: "styles/player_controls_layout",
|
|
1380
|
+
condition_value: "playlist_controls",
|
|
1381
|
+
},
|
|
1382
|
+
],
|
|
1281
1383
|
},
|
|
1282
1384
|
]
|
|
1283
1385
|
),
|
|
@@ -2415,6 +2517,21 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
2415
2517
|
);
|
|
2416
2518
|
}
|
|
2417
2519
|
|
|
2520
|
+
if (
|
|
2521
|
+
platform.includes("android") ||
|
|
2522
|
+
platform.includes("amazon") ||
|
|
2523
|
+
platform.includes("ios") ||
|
|
2524
|
+
platform.includes("tvos")
|
|
2525
|
+
) {
|
|
2526
|
+
general.fields.push({
|
|
2527
|
+
key: "user_agent",
|
|
2528
|
+
label: "User agent",
|
|
2529
|
+
initial_value: "",
|
|
2530
|
+
type: "text_input",
|
|
2531
|
+
label_tooltip: "Override default user agent string",
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2418
2535
|
compact([
|
|
2419
2536
|
{
|
|
2420
2537
|
key: "liveCatchUpEnabled",
|
|
@@ -3451,6 +3568,31 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
3451
3568
|
},
|
|
3452
3569
|
],
|
|
3453
3570
|
},
|
|
3571
|
+
{
|
|
3572
|
+
key: "player_controls_layout",
|
|
3573
|
+
label: "Player Controls Layout",
|
|
3574
|
+
type: "select",
|
|
3575
|
+
initial_value: "playback_speed_and_sleep_timer",
|
|
3576
|
+
label_tooltip:
|
|
3577
|
+
"Choose which secondary controls appear in the audio player transport row.",
|
|
3578
|
+
rules: "conditional",
|
|
3579
|
+
conditional_fields: [
|
|
3580
|
+
{
|
|
3581
|
+
key: "styles/full_screen_audio_player",
|
|
3582
|
+
condition_value: true,
|
|
3583
|
+
},
|
|
3584
|
+
],
|
|
3585
|
+
options: [
|
|
3586
|
+
{
|
|
3587
|
+
text: "Playback Speed & Sleep Timer (Default)",
|
|
3588
|
+
value: "playback_speed_and_sleep_timer",
|
|
3589
|
+
},
|
|
3590
|
+
{
|
|
3591
|
+
text: "Playlist Controls (Previous & Next)",
|
|
3592
|
+
value: "playlist_controls",
|
|
3593
|
+
},
|
|
3594
|
+
],
|
|
3595
|
+
},
|
|
3454
3596
|
{
|
|
3455
3597
|
key: "audio_player_artwork_shadow_enabled",
|
|
3456
3598
|
label: "Image Shadow Enabled",
|
|
@@ -3522,13 +3664,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
3522
3664
|
label: "Advanced Player Styles",
|
|
3523
3665
|
type: "text_input",
|
|
3524
3666
|
multiline: true,
|
|
3525
|
-
initial_value: `.vjs-text-track-cue {
|
|
3526
|
-
|
|
3527
|
-
top: 94% !important;
|
|
3667
|
+
initial_value: `.vjs-text-track-cue {
|
|
3668
|
+
top: auto !important;
|
|
3528
3669
|
bottom: 0 !important;
|
|
3529
3670
|
left: 0 !important;
|
|
3530
|
-
right:
|
|
3671
|
+
right: auto !important;
|
|
3531
3672
|
width: 100% !important;
|
|
3673
|
+
height: auto !important;
|
|
3532
3674
|
}`,
|
|
3533
3675
|
label_tooltip:
|
|
3534
3676
|
"This field allows you to inject custom CSS styles to override default player styles. The styles will be injected into the document head and can be used to customize the appearance of the player. Leave empty to use only default styles.",
|
|
@@ -1,9 +1,93 @@
|
|
|
1
1
|
const {
|
|
2
|
+
parseShorthand,
|
|
3
|
+
margin,
|
|
4
|
+
padding,
|
|
2
5
|
withConditional,
|
|
3
6
|
getConditionalKey,
|
|
4
7
|
createConditionalField,
|
|
5
8
|
} = require("..");
|
|
6
9
|
|
|
10
|
+
describe("parseShorthand", () => {
|
|
11
|
+
it("1 value → all four sides equal", () => {
|
|
12
|
+
expect(parseShorthand("8")).toEqual([8, 8, 8, 8]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("2 values → top/bottom, right/left", () => {
|
|
16
|
+
expect(parseShorthand("0 20")).toEqual([0, 20, 0, 20]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("3 values → top, right/left, bottom", () => {
|
|
20
|
+
expect(parseShorthand("0 0 20")).toEqual([0, 0, 20, 0]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("4 values → top right bottom left", () => {
|
|
24
|
+
expect(parseShorthand("1 2 3 4")).toEqual([1, 2, 3, 4]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("throws on invalid count", () => {
|
|
28
|
+
expect(() => parseShorthand("1 2 3 4 5")).toThrow();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("margin", () => {
|
|
33
|
+
it("returns 4 field descriptors with correct suffixes", () => {
|
|
34
|
+
const fields = margin("margin", { mobile: "0" });
|
|
35
|
+
|
|
36
|
+
expect(fields.map((f) => f.suffix)).toEqual([
|
|
37
|
+
"margin top",
|
|
38
|
+
"margin right",
|
|
39
|
+
"margin bottom",
|
|
40
|
+
"margin left",
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("sets type to number_input", () => {
|
|
45
|
+
margin("margin", { mobile: "0" }).forEach((f) => {
|
|
46
|
+
expect(f.type).toBe("number_input");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("expands shorthand per platform", () => {
|
|
51
|
+
const fields = margin("margin", { mobile: "0 0 20", tv: "0 0 90" });
|
|
52
|
+
const byKey = Object.fromEntries(fields.map((f) => [f.suffix, f]));
|
|
53
|
+
expect(byKey["margin top"].initialValue).toEqual({ mobile: 0, tv: 0 });
|
|
54
|
+
expect(byKey["margin bottom"].initialValue).toEqual({ mobile: 20, tv: 90 });
|
|
55
|
+
expect(byKey["margin left"].initialValue).toEqual({ mobile: 0, tv: 0 });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("supports a custom suffix prefix", () => {
|
|
59
|
+
const fields = margin("input margin", { mobile: "4" });
|
|
60
|
+
expect(fields[0].suffix).toBe("input margin top");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("padding", () => {
|
|
65
|
+
it("returns 4 field descriptors with correct suffixes", () => {
|
|
66
|
+
const fields = padding("padding", { mobile: "0" });
|
|
67
|
+
|
|
68
|
+
expect(fields.map((f) => f.suffix)).toEqual([
|
|
69
|
+
"padding top",
|
|
70
|
+
"padding right",
|
|
71
|
+
"padding bottom",
|
|
72
|
+
"padding left",
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("expands 2-value shorthand symmetrically", () => {
|
|
77
|
+
const fields = padding("padding", { mobile: "0 20", tv: "0 124" });
|
|
78
|
+
const byKey = Object.fromEntries(fields.map((f) => [f.suffix, f]));
|
|
79
|
+
|
|
80
|
+
expect(byKey["padding right"].initialValue).toEqual({
|
|
81
|
+
mobile: 20,
|
|
82
|
+
tv: 124,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(byKey["padding left"].initialValue).toEqual({ mobile: 20, tv: 124 });
|
|
86
|
+
expect(byKey["padding top"].initialValue).toEqual({ mobile: 0, tv: 0 });
|
|
87
|
+
expect(byKey["padding bottom"].initialValue).toEqual({ mobile: 0, tv: 0 });
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
7
91
|
describe("manifestUtils/fieldUtils", () => {
|
|
8
92
|
it("appends conditions and adds all_conditions when there is more than one condition", () => {
|
|
9
93
|
const config = {
|
|
@@ -1,3 +1,121 @@
|
|
|
1
|
+
const { ZAPPIFEST_FIELDS } = require("../keys");
|
|
2
|
+
const { toCamelCase } = require("../_internals");
|
|
3
|
+
|
|
4
|
+
const SIDES = ["top", "right", "bottom", "left"];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parses a CSS margin/padding shorthand string into [top, right, bottom, left].
|
|
8
|
+
*
|
|
9
|
+
* 1 value → all four sides
|
|
10
|
+
* 2 values → top/bottom, right/left
|
|
11
|
+
* 3 values → top, right/left, bottom
|
|
12
|
+
* 4 values → top, right, bottom, left
|
|
13
|
+
*
|
|
14
|
+
* @param {string} str
|
|
15
|
+
* @returns {[number, number, number, number]}
|
|
16
|
+
*/
|
|
17
|
+
function parseShorthand(str) {
|
|
18
|
+
const parts = String(str).trim().split(/\s+/).map(Number);
|
|
19
|
+
|
|
20
|
+
switch (parts.length) {
|
|
21
|
+
case 1:
|
|
22
|
+
return [parts[0], parts[0], parts[0], parts[0]];
|
|
23
|
+
case 2:
|
|
24
|
+
return [parts[0], parts[1], parts[0], parts[1]];
|
|
25
|
+
case 3:
|
|
26
|
+
return [parts[0], parts[1], parts[2], parts[1]];
|
|
27
|
+
case 4:
|
|
28
|
+
return parts;
|
|
29
|
+
default:
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Shorthand expects 1–4 space-separated values, got ${parts.length}: "${str}"`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function spacingFields(prefix, initialValues) {
|
|
37
|
+
const platforms = Object.keys(initialValues);
|
|
38
|
+
|
|
39
|
+
const parsed = Object.fromEntries(
|
|
40
|
+
platforms.map((p) => [p, parseShorthand(initialValues[p])])
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return SIDES.map((side, i) => ({
|
|
44
|
+
suffix: `${prefix} ${side}`,
|
|
45
|
+
type: ZAPPIFEST_FIELDS.number_input,
|
|
46
|
+
initialValue: Object.fromEntries(platforms.map((p) => [p, parsed[p][i]])),
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generates four margin field descriptors (top/right/bottom/left) from a
|
|
52
|
+
* per-platform CSS shorthand string.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} suffix - prefix word for generated field names, e.g. "margin"
|
|
55
|
+
* @param {Record<string, string>} initialValues - e.g. { mobile: "0 0 20", tv: "0 0 90" }
|
|
56
|
+
* @returns {object[]}
|
|
57
|
+
*/
|
|
58
|
+
function margin(suffix, initialValues) {
|
|
59
|
+
return spacingFields(suffix, initialValues);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generates four padding field descriptors (top/right/bottom/left) from a
|
|
64
|
+
* per-platform CSS shorthand string.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} suffix - prefix word for generated field names, e.g. "padding"
|
|
67
|
+
* @param {Record<string, string>} initialValues - e.g. { mobile: "0 20", tv: "0 124" }
|
|
68
|
+
* @returns {object[]}
|
|
69
|
+
*/
|
|
70
|
+
function padding(suffix, initialValues) {
|
|
71
|
+
return spacingFields(suffix, initialValues);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns a flat camelCase defaults object for a spacing shorthand.
|
|
76
|
+
* e.g. marginDefaults("margin", "0 0 20") → { marginTop: 0, marginRight: 0, marginBottom: 20, marginLeft: 0 }
|
|
77
|
+
*
|
|
78
|
+
* @param {string} suffix - e.g. "margin" or "padding"
|
|
79
|
+
* @param {string} shorthand - CSS-like shorthand string
|
|
80
|
+
* @returns {Record<string, number>}
|
|
81
|
+
*/
|
|
82
|
+
function spacingDefaults(suffix, shorthand) {
|
|
83
|
+
const values = parseShorthand(shorthand);
|
|
84
|
+
|
|
85
|
+
return Object.fromEntries(
|
|
86
|
+
SIDES.map((side, i) => [toCamelCase(`${suffix} ${side}`), values[i]])
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function marginDefaults(suffix, shorthand) {
|
|
91
|
+
return spacingDefaults(suffix, shorthand);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function paddingDefaults(suffix, shorthand) {
|
|
95
|
+
return spacingDefaults(suffix, shorthand);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Returns four field descriptors (top/right/bottom/left) with only suffix and type — no initialValue.
|
|
100
|
+
* Used when defaults are stored separately in the subgroup spec.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} prefix - e.g. "margin" or "padding"
|
|
103
|
+
* @returns {object[]}
|
|
104
|
+
*/
|
|
105
|
+
function marginFields(prefix) {
|
|
106
|
+
return SIDES.map((side) => ({
|
|
107
|
+
suffix: `${prefix} ${side}`,
|
|
108
|
+
type: ZAPPIFEST_FIELDS.number_input,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function paddingFields(prefix) {
|
|
113
|
+
return SIDES.map((side) => ({
|
|
114
|
+
suffix: `${prefix} ${side}`,
|
|
115
|
+
type: ZAPPIFEST_FIELDS.number_input,
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
|
|
1
119
|
/**
|
|
2
120
|
* Appends new conditional_fields from conditions array
|
|
3
121
|
* to config.conditional_fields
|
|
@@ -48,6 +166,13 @@ function createConditionalField(key, condition_value, category = "styles") {
|
|
|
48
166
|
}
|
|
49
167
|
|
|
50
168
|
module.exports = {
|
|
169
|
+
parseShorthand,
|
|
170
|
+
margin,
|
|
171
|
+
padding,
|
|
172
|
+
marginDefaults,
|
|
173
|
+
paddingDefaults,
|
|
174
|
+
marginFields,
|
|
175
|
+
paddingFields,
|
|
51
176
|
withConditional,
|
|
52
177
|
getConditionalKey,
|
|
53
178
|
createConditionalField,
|
package/manifestUtils/keys.js
CHANGED
|
@@ -631,6 +631,14 @@ const MOBILE_ACTION_BUTTON_FIELDS = [
|
|
|
631
631
|
type: ZAPPIFEST_FIELDS.number_input,
|
|
632
632
|
suffix: "margin left",
|
|
633
633
|
},
|
|
634
|
+
{
|
|
635
|
+
type: ZAPPIFEST_FIELDS.number_input,
|
|
636
|
+
suffix: "horizontal gutter",
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
type: ZAPPIFEST_FIELDS.number_input,
|
|
640
|
+
suffix: "vertical gutter",
|
|
641
|
+
},
|
|
634
642
|
];
|
|
635
643
|
|
|
636
644
|
const TV_MENU_LABEL_FIELDS = [
|
|
@@ -129,6 +129,22 @@ function mobileActionButton({ label, description, defaults, isFirstButton }) {
|
|
|
129
129
|
conditions.push(createConditionalField(labelEnabledKey, true));
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
const assetAlignmentKey = keyPrefixGenerator("asset_alignment");
|
|
133
|
+
|
|
134
|
+
// horizontal_gutter fields depends on [asset_alignment: left or asset_alignment: right]
|
|
135
|
+
if (isKeyHasSuffix("horizontal_gutter", key)) {
|
|
136
|
+
conditions.push(
|
|
137
|
+
createConditionalField(assetAlignmentKey, ["left", "right"])
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// vertical_gutter fields depends on [asset_alignment: above or asset_alignment: below]
|
|
142
|
+
if (isKeyHasSuffix("vertical_gutter", key)) {
|
|
143
|
+
conditions.push(
|
|
144
|
+
createConditionalField(assetAlignmentKey, ["above", "below"])
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
132
148
|
return withConditional(conditions)(field);
|
|
133
149
|
});
|
|
134
150
|
|
|
@@ -49,7 +49,9 @@ function mobileActionButtonsContainer({ label, description, defaults }) {
|
|
|
49
49
|
|
|
50
50
|
// over_image_position depends on [positionKey: over_image]
|
|
51
51
|
if (isKeyHasSuffix("over_image_position", key)) {
|
|
52
|
-
conditions.push(
|
|
52
|
+
conditions.push(
|
|
53
|
+
createConditionalField(positionKey, defaults.position[0])
|
|
54
|
+
);
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
// horizontal_gutter depends on [stackingKey: horizontal]
|
|
@@ -33,7 +33,7 @@ const DEFAULT_MOBILE_ACTION_BUTTON_SHARED_DEFAULTS = {
|
|
|
33
33
|
assetHeight: 24,
|
|
34
34
|
assetWidth: 24,
|
|
35
35
|
assetMarginTop: 0,
|
|
36
|
-
assetMarginRight:
|
|
36
|
+
assetMarginRight: 0,
|
|
37
37
|
assetMarginBottom: 0,
|
|
38
38
|
assetMarginLeft: 0,
|
|
39
39
|
labelEnabled: true,
|
|
@@ -50,6 +50,8 @@ const DEFAULT_MOBILE_ACTION_BUTTON_SHARED_DEFAULTS = {
|
|
|
50
50
|
marginRight: 0,
|
|
51
51
|
marginBottom: 0,
|
|
52
52
|
marginLeft: 0,
|
|
53
|
+
horizontalGutter: 8,
|
|
54
|
+
verticalGutter: 8,
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
const DEFAULT_MOBILE_ACTION_BUTTON_PRESETS = {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { isMinusZero } from "..";
|
|
2
|
+
|
|
3
|
+
describe("isMinusZero", () => {
|
|
4
|
+
it("return true for minus zero", () => {
|
|
5
|
+
expect(isMinusZero(-0)).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it("return false for positive zero", () => {
|
|
9
|
+
expect(isMinusZero(0)).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("return false for non-zero numbers", () => {
|
|
13
|
+
const inputs = [-1, 1, 100, 0.1, -0.1, Infinity, -Infinity];
|
|
14
|
+
expect.assertions(inputs.length);
|
|
15
|
+
|
|
16
|
+
inputs.forEach((input) => {
|
|
17
|
+
expect(isMinusZero(input)).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isZero } from "..";
|
|
2
|
+
|
|
3
|
+
describe("isZero", () => {
|
|
4
|
+
it("return true for zero and minus zero", () => {
|
|
5
|
+
expect(isZero(0)).toBe(true);
|
|
6
|
+
expect(isZero(-0)).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("return false for non-zero numbers", () => {
|
|
10
|
+
const inputs = [-1, 1, 100, 0.1, -0.1, Infinity, -Infinity, NaN];
|
|
11
|
+
expect.assertions(inputs.length);
|
|
12
|
+
|
|
13
|
+
inputs.forEach((input) => {
|
|
14
|
+
expect(isZero(input)).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("return false for non-number values", () => {
|
|
19
|
+
const inputs = ["0", "-0", null, undefined, "", {}, [], false, true];
|
|
20
|
+
|
|
21
|
+
expect.assertions(inputs.length);
|
|
22
|
+
|
|
23
|
+
inputs.forEach((input) => {
|
|
24
|
+
expect(isZero(input)).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { requireNumber } from "..";
|
|
2
|
+
|
|
3
|
+
describe("requireNumber", () => {
|
|
4
|
+
it("return number if object key holds a number", () => {
|
|
5
|
+
const inputs = [-1, 1, 100, 0.2];
|
|
6
|
+
expect.assertions(inputs.length);
|
|
7
|
+
|
|
8
|
+
inputs.forEach((input) => {
|
|
9
|
+
const output = requireNumber({ value: input }, "value");
|
|
10
|
+
expect(output).toBe(input);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("return number if object key holds a string as number", () => {
|
|
15
|
+
const inputs = ["-1", "1", "100", "0.2"];
|
|
16
|
+
expect.assertions(inputs.length);
|
|
17
|
+
|
|
18
|
+
inputs.forEach((input) => {
|
|
19
|
+
const output = requireNumber({ value: input }, "value");
|
|
20
|
+
expect(output).toBe(Number(input));
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("return null if object key holds zero", () => {
|
|
25
|
+
expect(requireNumber({ value: 0 }, "value")).toBeNull();
|
|
26
|
+
expect(requireNumber({ value: "0" }, "value")).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("return null if object key is missing or not a number", () => {
|
|
30
|
+
const cases = [
|
|
31
|
+
[{}, "value"],
|
|
32
|
+
[{ value: null }, "value"],
|
|
33
|
+
[{ value: undefined }, "value"],
|
|
34
|
+
[{ value: NaN }, "value"],
|
|
35
|
+
[{ value: "" }, "value"],
|
|
36
|
+
[{ value: "vfdvf" }, "value"],
|
|
37
|
+
[{ value: {} }, "value"],
|
|
38
|
+
[{ value: [] }, "value"],
|
|
39
|
+
[null, "value"],
|
|
40
|
+
[undefined, "value"],
|
|
41
|
+
] as const;
|
|
42
|
+
|
|
43
|
+
expect.assertions(cases.length);
|
|
44
|
+
|
|
45
|
+
cases.forEach(([obj, key]) => {
|
|
46
|
+
const output = requireNumber(obj, key);
|
|
47
|
+
expect(output).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -21,6 +21,33 @@ describe("toNumber", () => {
|
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
it("return number if input is boxed string as number", () => {
|
|
25
|
+
const inputs = [
|
|
26
|
+
new String("-1"),
|
|
27
|
+
new String("0"),
|
|
28
|
+
new String("1"),
|
|
29
|
+
Object("100"),
|
|
30
|
+
Object("0.2"),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
expect.assertions(inputs.length);
|
|
34
|
+
|
|
35
|
+
inputs.forEach((input) => {
|
|
36
|
+
const output = toNumber(input);
|
|
37
|
+
expect(output).toBe(Number(input));
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("return undefined if input is invalid boxed string", () => {
|
|
42
|
+
const inputs = [new String("vfdvf"), new String("5n"), Object("")];
|
|
43
|
+
expect.assertions(inputs.length);
|
|
44
|
+
|
|
45
|
+
inputs.forEach((input) => {
|
|
46
|
+
const output = toNumber(input);
|
|
47
|
+
expect(output).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
24
51
|
it("return undefined if input is not a number", () => {
|
|
25
52
|
const inputs = [
|
|
26
53
|
"vfdvf",
|