@cxpinsight/react-native 0.17.6 → 0.17.8
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/lib/CXPinsightProvider.d.ts.map +1 -1
- package/lib/CXPinsightProvider.js +47 -7
- package/lib/CXPinsightProvider.js.map +1 -1
- package/lib/core/triggerEngine.d.ts +13 -0
- package/lib/core/triggerEngine.d.ts.map +1 -1
- package/lib/core/triggerEngine.js +32 -1
- package/lib/core/triggerEngine.js.map +1 -1
- package/lib/survey/SurveyModal.d.ts +31 -0
- package/lib/survey/SurveyModal.d.ts.map +1 -1
- package/lib/survey/SurveyModal.js +354 -40
- package/lib/survey/SurveyModal.js.map +1 -1
- package/lib/survey/styleContract.d.ts +5 -0
- package/lib/survey/styleContract.d.ts.map +1 -1
- package/lib/survey/styleContract.js +2 -0
- package/lib/survey/styleContract.js.map +1 -1
- package/lib/types.d.ts +26 -0
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -33,6 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.stylePlatformSlice = stylePlatformSlice;
|
|
37
|
+
exports.topInsetForHeight = topInsetForHeight;
|
|
38
|
+
exports.resolvePopupTop = resolvePopupTop;
|
|
36
39
|
exports.SurveyModal = SurveyModal;
|
|
37
40
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
41
|
const react_1 = __importStar(require("react"));
|
|
@@ -94,6 +97,54 @@ function stripHtml(input) {
|
|
|
94
97
|
.replace(/\n{3,}/g, '\n\n')
|
|
95
98
|
.trim();
|
|
96
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* `branding.style` flattened for THIS platform: shared keys first, the `mobile`
|
|
102
|
+
* slice on top. Mirrors how `settings.display` is sliced — the two are written
|
|
103
|
+
* by the same studio controls and have to be read the same way. `web` is
|
|
104
|
+
* dropped rather than merged; it describes a different form factor.
|
|
105
|
+
*/
|
|
106
|
+
function stylePlatformSlice(style) {
|
|
107
|
+
if (!style || typeof style !== 'object')
|
|
108
|
+
return {};
|
|
109
|
+
const out = {};
|
|
110
|
+
for (const [k, v] of Object.entries(style)) {
|
|
111
|
+
if (k === 'web' || k === 'mobile' || k === 'link')
|
|
112
|
+
continue;
|
|
113
|
+
out[k] = v;
|
|
114
|
+
}
|
|
115
|
+
const m = style.mobile;
|
|
116
|
+
if (m && typeof m === 'object')
|
|
117
|
+
Object.assign(out, m);
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* iOS top safe-area inset, derived from the screen height.
|
|
122
|
+
*
|
|
123
|
+
* This package declares no dependencies, so react-native-safe-area-context is
|
|
124
|
+
* not available and the height is the only signal. 852/932pt are the Dynamic
|
|
125
|
+
* Island sizes (59pt), 812-896 the notch generation (44pt), the rest pre-notch.
|
|
126
|
+
*/
|
|
127
|
+
function topInsetForHeight(screenH) {
|
|
128
|
+
return screenH >= 852 ? 59 : screenH >= 812 ? 44 : 20;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Resolve an authored `y` (percent of screen, marking the card's CENTRE — the
|
|
132
|
+
* web SDK's `top:y%` + `translate(-50%,-50%)`) to a paddingTop in points, with
|
|
133
|
+
* the whole card kept inside the safe area.
|
|
134
|
+
*
|
|
135
|
+
* Returns null when there is no authored coordinate, so the caller falls back
|
|
136
|
+
* to flex placement.
|
|
137
|
+
*/
|
|
138
|
+
function resolvePopupTop(opts) {
|
|
139
|
+
const { y, screenH, cardH, topInset, bottomInset, gutter } = opts;
|
|
140
|
+
if (!Number.isFinite(y))
|
|
141
|
+
return null;
|
|
142
|
+
const minTop = topInset + gutter;
|
|
143
|
+
const maxTop = screenH - bottomInset - gutter - cardH;
|
|
144
|
+
const wanted = (y / 100) * screenH - cardH / 2;
|
|
145
|
+
// A card taller than the safe band pins to the top rather than going negative.
|
|
146
|
+
return Math.max(minTop, maxTop > minTop ? Math.min(wanted, maxTop) : minTop);
|
|
147
|
+
}
|
|
97
148
|
function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
98
149
|
const [currentPage, setCurrentPage] = (0, react_1.useState)(0);
|
|
99
150
|
const [responses, setResponses] = (0, react_1.useState)({});
|
|
@@ -605,7 +656,12 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
605
656
|
setForkStack([]);
|
|
606
657
|
onDismiss();
|
|
607
658
|
};
|
|
608
|
-
handleDismissRef.current =
|
|
659
|
+
handleDismissRef.current = (() => {
|
|
660
|
+
if (__DEV__) { /* eslint-disable-next-line no-console */
|
|
661
|
+
console.log('[CXP-SW] handleDismiss INVOKED');
|
|
662
|
+
}
|
|
663
|
+
return handleDismiss();
|
|
664
|
+
});
|
|
609
665
|
// Normalize the v1.1 options[] {value,label} and the legacy
|
|
610
666
|
// choices[] (string | {value,text}) into a single shape that
|
|
611
667
|
// MultipleChoiceQuestion accepts. hasOther appends a synthetic
|
|
@@ -1249,7 +1305,12 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1249
1305
|
// radius set once applies in both themes, exactly as it does on web.
|
|
1250
1306
|
surveyOverride: {
|
|
1251
1307
|
...(0, styleContract_1.fromAuthoredPalette)(surveyPaletteOverride),
|
|
1252
|
-
|
|
1308
|
+
// PER PLATFORM, like `display` below. branding.style carries web/mobile
|
|
1309
|
+
// slices — the studio writes a size dragged on the phone frame into
|
|
1310
|
+
// style.mobile — and this spread was flat, so mobile silently used the
|
|
1311
|
+
// SHARED values. A notification authored with style.mobile.imageSize 53
|
|
1312
|
+
// and cardPadding 17 rendered at the root's 101 and 36.
|
|
1313
|
+
...stylePlatformSlice(survey.branding?.style),
|
|
1253
1314
|
},
|
|
1254
1315
|
initOverride: initPaletteOverride,
|
|
1255
1316
|
}),
|
|
@@ -1320,6 +1381,26 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1320
1381
|
const animationType = display?.animation === 'fade' ? 'fade' : display?.animation === 'none' ? 'none' : 'slide';
|
|
1321
1382
|
const isFullscreen = display?.mode === 'fullscreen';
|
|
1322
1383
|
const isDrawer = display?.mode === 'modal';
|
|
1384
|
+
/**
|
|
1385
|
+
* A popup does NOT dim the app, and does not take its touches.
|
|
1386
|
+
*
|
|
1387
|
+
* Web has said so for a while — the overlay is `transparent` with
|
|
1388
|
+
* `pointer-events:none` for a popup, and only a modal gets rgba(0,0,0,0.45).
|
|
1389
|
+
* Mobile painted `c.ground` (rgba(0,0,0,0.6) dark, 0.4 light) over the whole
|
|
1390
|
+
* screen for EVERY mode, so a push-style banner dimmed the app behind it and
|
|
1391
|
+
* blocked every tap that was not on the card.
|
|
1392
|
+
*
|
|
1393
|
+
* Visible in the recordings: the same login screen captured 47 seconds apart
|
|
1394
|
+
* is bright at `start` and washed out once a card is up — the lime button
|
|
1395
|
+
* renders dull olive. That darkened frame is what the replay shows, so the
|
|
1396
|
+
* "grey composite" in the timeline and this are one bug.
|
|
1397
|
+
*
|
|
1398
|
+
* box-none rather than none: the container stops capturing touches, the card
|
|
1399
|
+
* inside still receives them. `none` here would make the survey itself
|
|
1400
|
+
* untouchable, which is how web pairs `pointer-events:none` on the overlay
|
|
1401
|
+
* with `auto` on the card.
|
|
1402
|
+
*/
|
|
1403
|
+
const isPopupOverlay = !isFullscreen && !isDrawer;
|
|
1323
1404
|
// Position — previously unread here, which is why a phone could never show a
|
|
1324
1405
|
// bottom sheet, the one presentation users actually expect on mobile.
|
|
1325
1406
|
//
|
|
@@ -1327,6 +1408,58 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1327
1408
|
// 420px of horizontal room a handset does not have, and the sheet is the
|
|
1328
1409
|
// native equivalent. A drawer with nothing set anchors to the BOTTOM (the
|
|
1329
1410
|
// platform convention); anything else centres, as before.
|
|
1411
|
+
// A NOTIFICATION IS NOT A FORM.
|
|
1412
|
+
//
|
|
1413
|
+
// Web composes one: `.cxp-notify-row` puts a small thumbnail beside the title
|
|
1414
|
+
// and description, hides the implied question that carries the action, and
|
|
1415
|
+
// puts that action full-width underneath. Mobile had none of it — `notify`
|
|
1416
|
+
// appeared nowhere in this file — so the same interaction rendered as a plain
|
|
1417
|
+
// question list: the avatar as a full-bleed image question, and the implied
|
|
1418
|
+
// question's `text` as a visible prompt above it (the reported stray "tte").
|
|
1419
|
+
const interactionKind = String(survey.interactionType || '').toLowerCase();
|
|
1420
|
+
// BOTH shapes: the wire payload carries `questions`, and the provider sets
|
|
1421
|
+
// `rawQuestions` only when it re-paginates. Reading one alone missed the
|
|
1422
|
+
// notification entirely — the composition below silently never engaged.
|
|
1423
|
+
const allQuestions = (survey.rawQuestions
|
|
1424
|
+
|| survey.questions
|
|
1425
|
+
|| []);
|
|
1426
|
+
const notifyImageQ = allQuestions.find((q) => String(q?.id ?? '') === 'notify_image' && String(q?.type ?? '').toLowerCase() === 'image');
|
|
1427
|
+
// Keyed off the composed question, not the type alone: an Ask that happens to
|
|
1428
|
+
// carry an image question must keep rendering it as a question.
|
|
1429
|
+
const isNotifyCard = interactionKind === 'notify' && !!notifyImageQ?.imageUrl;
|
|
1430
|
+
// A NOTIFICATION'S TYPE SITS ONE SIZE DOWN from a survey's — web says so
|
|
1431
|
+
// explicitly (notifyFsHeading/notifyFsDesc, defaulting to 15/13), and mobile
|
|
1432
|
+
// had no equivalent: it used the survey title and a hardcoded 14 description,
|
|
1433
|
+
// so an unstyled notification rendered at full survey type here and banner
|
|
1434
|
+
// type there. An authored size still wins, on both.
|
|
1435
|
+
const _authoredStyle = stylePlatformSlice(survey.branding?.style);
|
|
1436
|
+
const _num = (v) => (typeof v === 'number' && v > 0 ? v : undefined);
|
|
1437
|
+
// SHARED VALUE, PROPORTIONATE RENDERING.
|
|
1438
|
+
//
|
|
1439
|
+
// `fontSizeHeading` is stored once for every surface, so a size chosen while
|
|
1440
|
+
// looking at the desktop frame is the size the phone gets too. 24 sits
|
|
1441
|
+
// comfortably in a 485px desktop card and overflows a ~354pt phone one, which
|
|
1442
|
+
// is why the headline wrapped here and not in the preview.
|
|
1443
|
+
//
|
|
1444
|
+
// The number stays shared — one value, as authored — and each surface renders
|
|
1445
|
+
// it in proportion to the card it actually has. Web is unchanged (its card is
|
|
1446
|
+
// at or above the reference), and only a card narrower than the reference
|
|
1447
|
+
// scales down. An unstyled notification keeps web's own banner default of 15
|
|
1448
|
+
// rather than being scaled, because 15 is already a banner size.
|
|
1449
|
+
const REFERENCE_CARD_PT = 480;
|
|
1450
|
+
const notifyCardW = Math.min(SCREEN_WIDTH * CARD_VIEWPORT_FRACTION, typeof maxWidth === 'number' ? maxWidth : SCREEN_WIDTH);
|
|
1451
|
+
const authoredHeadingPt = _num(_authoredStyle.fontSizeHeading);
|
|
1452
|
+
const notifyTitleFS = isNotifyCard
|
|
1453
|
+
? {
|
|
1454
|
+
fontSize: authoredHeadingPt != null
|
|
1455
|
+
// Floored so a very narrow card cannot shrink the headline below the
|
|
1456
|
+
// body text and invert the hierarchy.
|
|
1457
|
+
? Math.max(14, Math.round(authoredHeadingPt * Math.min(1, notifyCardW / REFERENCE_CARD_PT)))
|
|
1458
|
+
: 15,
|
|
1459
|
+
}
|
|
1460
|
+
: null;
|
|
1461
|
+
const notifyDescFS = isNotifyCard
|
|
1462
|
+
? { fontSize: _num(_authoredStyle.fontSizeDescription) ?? 13, lineHeight: 18 } : null;
|
|
1330
1463
|
const rawPos = display?.position;
|
|
1331
1464
|
const placement = rawPos === 'top' ? 'top'
|
|
1332
1465
|
: rawPos === 'bottom' ? 'bottom'
|
|
@@ -1334,12 +1467,32 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1334
1467
|
: rawPos === 'center' ? 'center'
|
|
1335
1468
|
: isDrawer ? 'bottom' : 'center';
|
|
1336
1469
|
const isSheet = isDrawer && (placement === 'top' || placement === 'bottom');
|
|
1337
|
-
// Popup coordinate.
|
|
1338
|
-
// —
|
|
1339
|
-
//
|
|
1340
|
-
//
|
|
1470
|
+
// Popup coordinate. `y` is a PERCENTAGE OF THE SCREEN and marks the CARD'S
|
|
1471
|
+
// CENTRE — the web SDK's `top:y%` + `translate(-50%,-50%)`. This used to be
|
|
1472
|
+
// bucketed into three placements (<33 top, >67 bottom, else centre) on the
|
|
1473
|
+
// belief that RN could not express it: translateY takes points, not
|
|
1474
|
+
// percentages, and percentage margins resolve against the parent WIDTH.
|
|
1475
|
+
//
|
|
1476
|
+
// Both are true and neither is binding. The screen dimensions are known here,
|
|
1477
|
+
// so the percentage resolves to POINTS and rides in as paddingTop on a
|
|
1478
|
+
// flex-start overlay — no absolute positioning, so KeyboardAvoidingView keeps
|
|
1479
|
+
// working. The card's own height comes from onLayout; until that first pass
|
|
1480
|
+
// the authored height (or a reasonable default) stands in, and the safe-area
|
|
1481
|
+
// clamp below usually decides the result anyway.
|
|
1482
|
+
//
|
|
1483
|
+
// The bucketing was a real mismatch, not a rounding one: an author who
|
|
1484
|
+
// dragged the card to y=17 in the studio got it flush against the top of the
|
|
1485
|
+
// device — 20dp instead of the ~145dp they placed it at on an 852dp screen.
|
|
1486
|
+
//
|
|
1487
|
+
// x stays ignored: the studio disables horizontal dragging on the mobile
|
|
1488
|
+
// frame (`moveX: platformForFrame(frame) !== 'mobile'`), so a phone centres.
|
|
1341
1489
|
const popupY = !isDrawer && !isFullscreen && display?.y != null ? parseFloat(String(display.y)) : NaN;
|
|
1342
|
-
const
|
|
1490
|
+
const hasPopupY = !isNaN(popupY);
|
|
1491
|
+
const effPlacement = hasPopupY ? 'top' : placement;
|
|
1492
|
+
// Measured card height; the authored height is the first-frame stand-in.
|
|
1493
|
+
const [measuredCardH, setMeasuredCardH] = (0, react_1.useState)(0);
|
|
1494
|
+
const authoredH = Number.parseFloat(String(display?.height ?? ''));
|
|
1495
|
+
const estCardH = measuredCardH || (Number.isFinite(authoredH) && authoredH > 0 ? authoredH : 220);
|
|
1343
1496
|
const overlayPlacement = {
|
|
1344
1497
|
justifyContent: effPlacement === 'top' ? 'flex-start'
|
|
1345
1498
|
: effPlacement === 'bottom' ? 'flex-end'
|
|
@@ -1353,7 +1506,15 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1353
1506
|
// a bottom sheet and a centred popup both rise, and fade has no direction.
|
|
1354
1507
|
// So the Modal animation is handed over ONLY for a top sheet, and the card
|
|
1355
1508
|
// animates itself down from above instead.
|
|
1356
|
-
|
|
1509
|
+
//
|
|
1510
|
+
// A POPUP resolved to the top has exactly the same problem and was not
|
|
1511
|
+
// covered: the gate asked for `isSheet`, which a popup never is. So a
|
|
1512
|
+
// notification authored near the top of the screen flew up from the BOTTOM
|
|
1513
|
+
// bezel, crossed the whole display, and settled backwards into place — the
|
|
1514
|
+
// reported "the animation is wrong". Keyed off the RESOLVED placement, which
|
|
1515
|
+
// is what the card actually uses.
|
|
1516
|
+
const needsTopSlide = animationType === 'slide'
|
|
1517
|
+
&& ((isSheet && placement === 'top') || (!isDrawer && !isFullscreen && effPlacement === 'top'));
|
|
1357
1518
|
const modalAnimation = needsTopSlide ? 'none' : animationType;
|
|
1358
1519
|
const enterAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(1)).current;
|
|
1359
1520
|
(0, react_1.useEffect)(() => {
|
|
@@ -1383,12 +1544,60 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1383
1544
|
// keeps a compliance takeover — showClose:false for the OPPOSITE reason —
|
|
1384
1545
|
// from ever becoming flickable.
|
|
1385
1546
|
const swipeable = (0, survey_spec_1.canSwipeToDismiss)(display, survey.delivery);
|
|
1547
|
+
// TEMP-DIAG
|
|
1548
|
+
if (__DEV__) {
|
|
1549
|
+
// eslint-disable-next-line no-console
|
|
1550
|
+
console.log('[CXP-SW]', JSON.stringify({
|
|
1551
|
+
swipeable,
|
|
1552
|
+
mode: display?.mode, showClose: display?.showClose,
|
|
1553
|
+
mustShow: survey.delivery?.mustShow,
|
|
1554
|
+
isNotifyCard,
|
|
1555
|
+
}));
|
|
1556
|
+
}
|
|
1386
1557
|
const swipeXY = (0, react_1.useRef)(new react_native_1.Animated.ValueXY({ x: 0, y: 0 })).current;
|
|
1387
1558
|
const swipeResponder = (0, react_1.useMemo)(() => react_native_1.PanResponder.create({
|
|
1388
1559
|
onStartShouldSetPanResponder: () => false,
|
|
1389
1560
|
// Claim the gesture only once it is clearly a drag, so a tap on the action
|
|
1390
1561
|
// button still presses the button.
|
|
1391
|
-
|
|
1562
|
+
//
|
|
1563
|
+
// CAPTURE, not bubble. The card's content sits in a ScrollView, and a
|
|
1564
|
+
// ScrollView claims a vertical drag first — so the flick that is supposed
|
|
1565
|
+
// to dismiss a notification was being consumed as a scroll of content that
|
|
1566
|
+
// does not even overflow, and the banner could not be swiped away at all.
|
|
1567
|
+
// The 8px threshold still protects the button: a tap never reaches here.
|
|
1568
|
+
// CAPTURE, so the flick works from ANYWHERE on the card.
|
|
1569
|
+
//
|
|
1570
|
+
// On the bubble phase the card is only asked once some descendant has
|
|
1571
|
+
// already become the responder — which on this card means the action
|
|
1572
|
+
// button and nothing else. Touch the title, the image or the body and no
|
|
1573
|
+
// view claims the touch, so the card was never consulted and the banner
|
|
1574
|
+
// could only be dragged by its button. Capture asks the card first, on
|
|
1575
|
+
// every touch, wherever it landed.
|
|
1576
|
+
//
|
|
1577
|
+
// The 8px threshold is what keeps the button a button: a press that does
|
|
1578
|
+
// not travel is never claimed here, so it reaches the button untouched. A
|
|
1579
|
+
// press that DOES travel drags the card, which is what a notification is
|
|
1580
|
+
// expected to do — the whole surface is the handle.
|
|
1581
|
+
onMoveShouldSetPanResponderCapture: (_e, g) => {
|
|
1582
|
+
const claim = swipeable && (Math.abs(g.dx) > 8 || Math.abs(g.dy) > 8);
|
|
1583
|
+
if (__DEV__) { /* eslint-disable-next-line no-console */
|
|
1584
|
+
console.log('[CXP-SW] move', JSON.stringify({ dx: Math.round(g.dx), dy: Math.round(g.dy), claim }));
|
|
1585
|
+
}
|
|
1586
|
+
return claim;
|
|
1587
|
+
},
|
|
1588
|
+
// ONCE THE CARD HAS THE FLICK, IT KEEPS IT.
|
|
1589
|
+
//
|
|
1590
|
+
// The default is to grant a termination request, so a view underneath could
|
|
1591
|
+
// take the gesture back mid-drag: the card followed the finger and then
|
|
1592
|
+
// simply stopped, with onPanResponderRelease never firing and the dismissal
|
|
1593
|
+
// never running. That is the reported "it moves but it doesn't swipe away".
|
|
1594
|
+
onPanResponderTerminationRequest: () => false,
|
|
1595
|
+
onPanResponderTerminate: (_e, g) => {
|
|
1596
|
+
if (__DEV__) { /* eslint-disable-next-line no-console */
|
|
1597
|
+
console.log('[CXP-SW] TERMINATED mid-gesture', JSON.stringify({ dx: Math.round(g.dx), dy: Math.round(g.dy) }));
|
|
1598
|
+
}
|
|
1599
|
+
react_native_1.Animated.spring(swipeXY, { toValue: { x: 0, y: 0 }, useNativeDriver: true, bounciness: 6 }).start();
|
|
1600
|
+
},
|
|
1392
1601
|
onPanResponderMove: (_e, g) => {
|
|
1393
1602
|
const horizontal = Math.abs(g.dx) > Math.abs(g.dy);
|
|
1394
1603
|
// A top banner travels up, never down — dragging it down is a pull.
|
|
@@ -1396,15 +1605,27 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1396
1605
|
swipeXY.setValue(horizontal ? { x: g.dx, y: 0 } : { x: 0, y: dy });
|
|
1397
1606
|
},
|
|
1398
1607
|
onPanResponderRelease: (_e, g) => {
|
|
1608
|
+
if (__DEV__) { /* eslint-disable-next-line no-console */
|
|
1609
|
+
console.log('[CXP-SW] RELEASE', JSON.stringify({ dx: Math.round(g.dx), dy: Math.round(g.dy), vx: g.vx.toFixed(2), vy: g.vy.toFixed(2) }));
|
|
1610
|
+
}
|
|
1399
1611
|
const horizontal = Math.abs(g.dx) > Math.abs(g.dy);
|
|
1400
1612
|
const moved = horizontal ? g.dx : g.dy;
|
|
1401
1613
|
const fast = Math.abs(horizontal ? g.vx : g.vy) > 0.5;
|
|
1402
1614
|
if (Math.abs(moved) >= 56 || fast) {
|
|
1615
|
+
if (__DEV__) { /* eslint-disable-next-line no-console */
|
|
1616
|
+
console.log('[CXP-SW] threshold PASSED — animating out');
|
|
1617
|
+
}
|
|
1403
1618
|
const to = moved < 0 ? -SCREEN_HEIGHT : SCREEN_HEIGHT;
|
|
1404
1619
|
react_native_1.Animated.timing(swipeXY, {
|
|
1405
1620
|
toValue: horizontal ? { x: moved < 0 ? -SCREEN_WIDTH : SCREEN_WIDTH, y: 0 } : { x: 0, y: to },
|
|
1406
1621
|
duration: 180, useNativeDriver: true,
|
|
1407
|
-
}).start((
|
|
1622
|
+
}).start(({ finished }) => {
|
|
1623
|
+
if (__DEV__) { /* eslint-disable-next-line no-console */
|
|
1624
|
+
console.log('[CXP-SW] animation done, finished=' + finished + ' — calling dismiss');
|
|
1625
|
+
}
|
|
1626
|
+
swipeXY.setValue({ x: 0, y: 0 });
|
|
1627
|
+
handleDismissRef.current();
|
|
1628
|
+
});
|
|
1408
1629
|
return;
|
|
1409
1630
|
}
|
|
1410
1631
|
react_native_1.Animated.spring(swipeXY, { toValue: { x: 0, y: 0 }, useNativeDriver: true, bounciness: 6 }).start();
|
|
@@ -1442,10 +1663,16 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1442
1663
|
}, dismissMs);
|
|
1443
1664
|
return () => clearTimeout(t);
|
|
1444
1665
|
}, [visible, dismissMs]);
|
|
1666
|
+
// COMPOSED, not stacked. The swipe transform used to be a separate entry in
|
|
1667
|
+
// the card's style array, applied after this one — and a later `transform`
|
|
1668
|
+
// REPLACES an earlier one rather than merging. That was harmless while only
|
|
1669
|
+
// sheets animated themselves, but a notification is both top-placed and
|
|
1670
|
+
// swipeable, so the entrance translate would have been silently dropped.
|
|
1445
1671
|
const enterStyle = needsTopSlide
|
|
1446
1672
|
? {
|
|
1447
1673
|
opacity: enterAnim,
|
|
1448
|
-
transform: [
|
|
1674
|
+
transform: [
|
|
1675
|
+
{
|
|
1449
1676
|
translateY: enterAnim.interpolate({
|
|
1450
1677
|
inputRange: [0, 1],
|
|
1451
1678
|
// Its own height is not known before layout, so it travels a screen
|
|
@@ -1453,7 +1680,9 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1453
1680
|
// enough that the easing still lands crisply.
|
|
1454
1681
|
outputRange: [-SCREEN_HEIGHT / 3, 0],
|
|
1455
1682
|
}),
|
|
1456
|
-
}
|
|
1683
|
+
},
|
|
1684
|
+
...(swipeable ? swipeXY.getTranslateTransform() : []),
|
|
1685
|
+
],
|
|
1457
1686
|
}
|
|
1458
1687
|
: null;
|
|
1459
1688
|
const sheetRadius = cornerRadius !== undefined ? cornerRadius : 16;
|
|
@@ -1468,13 +1697,28 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1468
1697
|
// Safe areas. Deliberately built from react-native primitives rather than
|
|
1469
1698
|
// react-native-safe-area-context: this package declares no dependencies, and
|
|
1470
1699
|
// adding one would force every host app to install it.
|
|
1471
|
-
|
|
1700
|
+
//
|
|
1701
|
+
// 44 was the notched-iPhone value and was applied to every iOS device, so on a
|
|
1702
|
+
// Dynamic Island phone (59pt) the card rode up under the island. Derived from
|
|
1703
|
+
// the screen height instead — the only signal available without the dependency:
|
|
1704
|
+
// 852pt/932pt are the Island sizes, 812-896 the notch generation, the rest pre-notch.
|
|
1705
|
+
const topInset = react_native_1.Platform.OS === 'android'
|
|
1706
|
+
? (react_native_1.StatusBar.currentHeight || 0)
|
|
1707
|
+
: topInsetForHeight(SCREEN_HEIGHT);
|
|
1472
1708
|
const bottomInset = react_native_1.Platform.OS === 'ios' ? 34 : 0;
|
|
1473
1709
|
const safePad = isFullscreen
|
|
1474
1710
|
? { paddingTop: topInset, paddingBottom: bottomInset }
|
|
1475
1711
|
: isSheet && placement === 'bottom' ? { paddingBottom: bottomInset }
|
|
1476
1712
|
: isSheet && placement === 'top' ? { paddingTop: topInset }
|
|
1477
1713
|
: null;
|
|
1714
|
+
// Resolve the authored `y` to a top offset in points, keeping the whole card
|
|
1715
|
+
// inside the safe area. GUTTER is the overlay's own padding, so the clamp is
|
|
1716
|
+
// expressed against the same edges the card would otherwise rest on.
|
|
1717
|
+
const GUTTER = 20;
|
|
1718
|
+
const popupTopPx = hasPopupY
|
|
1719
|
+
? resolvePopupTop({ y: popupY, screenH: SCREEN_HEIGHT, cardH: estCardH, topInset, bottomInset, gutter: GUTTER })
|
|
1720
|
+
: null;
|
|
1721
|
+
const popupTop = popupTopPx == null ? null : { paddingTop: popupTopPx };
|
|
1478
1722
|
// Action-button layout. UNSET keeps the native full-width split (default);
|
|
1479
1723
|
// 'full' is explicit full-width; right/center/left shrink to content + align.
|
|
1480
1724
|
const buttonAlign = display?.buttonAlign;
|
|
@@ -1532,8 +1776,17 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1532
1776
|
if (pct)
|
|
1533
1777
|
return { width: `${pct[1]}%` };
|
|
1534
1778
|
const px = /^(\d+(?:\.\d+)?)px$/.exec(String(w));
|
|
1779
|
+
// The authored width is a CAP, not a target — web renders the popup as
|
|
1780
|
+
// `width:90vw; max-width:<authored>`, so the card is 90% of the viewport
|
|
1781
|
+
// until the author's number is the smaller of the two.
|
|
1782
|
+
//
|
|
1783
|
+
// I briefly clamped this to the full SCREEN instead, reading "390px on a
|
|
1784
|
+
// 393pt frame" as a request for full bleed. It is not: the preview showed
|
|
1785
|
+
// 351px (90vw of 390) and the device then drew 390, so the SAME 53pt
|
|
1786
|
+
// thumbnail sat in a wider card and read as too small. Matching web's 90vw
|
|
1787
|
+
// is what keeps the proportions identical on both.
|
|
1535
1788
|
if (px)
|
|
1536
|
-
return { width: Math.min(SCREEN_WIDTH
|
|
1789
|
+
return { width: Math.min(SCREEN_WIDTH * CARD_VIEWPORT_FRACTION, Number(px[1])), maxWidth: Number(px[1]) };
|
|
1537
1790
|
return null;
|
|
1538
1791
|
})();
|
|
1539
1792
|
// Fullscreen ONLY: the card fills (survey bg), and these constrain the CONTENT
|
|
@@ -1624,18 +1877,30 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1624
1877
|
styles.progressBarFill,
|
|
1625
1878
|
{ width: `${((currentPage + 1) / totalPages) * 100}%`, backgroundColor: primaryColor },
|
|
1626
1879
|
] }) })] })) : null;
|
|
1627
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.Modal, { visible: visible, transparent: true, animationType: modalAnimation, onRequestClose: handleDismiss, children: (0, jsx_runtime_1.jsxs)(react_native_1.KeyboardAvoidingView, { style: [
|
|
1880
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.Modal, { visible: visible, transparent: true, animationType: modalAnimation, onRequestClose: handleDismiss, children: (0, jsx_runtime_1.jsxs)(react_native_1.KeyboardAvoidingView, { pointerEvents: isPopupOverlay ? 'box-none' : 'auto', style: [
|
|
1628
1881
|
styles.overlay,
|
|
1882
|
+
// Transparent for a popup — the app behind it is not being taken over.
|
|
1883
|
+
isPopupOverlay && { backgroundColor: 'transparent' },
|
|
1629
1884
|
overlayPlacement,
|
|
1885
|
+
// The authored coordinate, resolved to points. Before the sheet and
|
|
1886
|
+
// fullscreen overrides so those keep winning.
|
|
1887
|
+
popupTop,
|
|
1630
1888
|
// A sheet sits flush against its edge, so the overlay loses its gutter.
|
|
1631
1889
|
isSheet && { paddingHorizontal: 0, paddingTop: 0, paddingBottom: 0 },
|
|
1632
1890
|
isFullscreen && styles.overlayFullscreen,
|
|
1633
|
-
], behavior: react_native_1.Platform.OS === 'ios' ? 'padding' : 'height', children: [(0, jsx_runtime_1.jsxs)(react_native_1.Animated.View, { onTouchStart: () => { engagedRef.current = true; },
|
|
1891
|
+
], behavior: react_native_1.Platform.OS === 'ios' ? 'padding' : 'height', children: [(0, jsx_runtime_1.jsxs)(react_native_1.Animated.View, { onTouchStart: () => { engagedRef.current = true; },
|
|
1892
|
+
// Feeds the authored-coordinate maths above. Only tracked when a
|
|
1893
|
+
// coordinate is actually in play, and only on a real change, so the
|
|
1894
|
+
// common case costs nothing and this cannot loop.
|
|
1895
|
+
onLayout: hasPopupY ? (e) => {
|
|
1896
|
+
const h = Math.round(e.nativeEvent.layout.height);
|
|
1897
|
+
setMeasuredCardH((prev) => (Math.abs(prev - h) > 1 ? h : prev));
|
|
1898
|
+
} : undefined, ...(swipeable ? swipeResponder.panHandlers : {}), style: [
|
|
1634
1899
|
styles.card,
|
|
1635
1900
|
themeCard,
|
|
1636
1901
|
enterStyle,
|
|
1637
|
-
swipeable && { transform: swipeXY.getTranslateTransform() },
|
|
1638
|
-
maxWidth !== undefined && !isFullscreen && { width: Math.min(SCREEN_WIDTH
|
|
1902
|
+
swipeable && !needsTopSlide && { transform: swipeXY.getTranslateTransform() },
|
|
1903
|
+
maxWidth !== undefined && !isFullscreen && { width: Math.min(SCREEN_WIDTH * CARD_VIEWPORT_FRACTION, maxWidth), maxWidth },
|
|
1639
1904
|
cornerRadius !== undefined && !isFullscreen && { borderRadius: cornerRadius },
|
|
1640
1905
|
// cardFullscreen fills; an explicit content height/width below OVERRIDES
|
|
1641
1906
|
// the fill so a fullscreen survey can be contained to that size.
|
|
@@ -1695,6 +1960,8 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1695
1960
|
styles.header,
|
|
1696
1961
|
padHeader,
|
|
1697
1962
|
isRtl && { paddingLeft: 48, paddingRight: 24 },
|
|
1963
|
+
// Thumbnail beside the text, as `.cxp-notify-row` does on web.
|
|
1964
|
+
isNotifyCard && { flexDirection: 'row', alignItems: 'center' },
|
|
1698
1965
|
], children: [((survey.branding?.showLogo !== false && survey.branding?.logo)
|
|
1699
1966
|
|| survey.branding?.brandName) && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
1700
1967
|
styles.brandRow,
|
|
@@ -1704,20 +1971,32 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1704
1971
|
: survey.branding?.logoAlign === 'right'
|
|
1705
1972
|
? { justifyContent: 'flex-end' }
|
|
1706
1973
|
: null),
|
|
1707
|
-
], children: [survey.branding?.showLogo !== false && survey.branding?.logo ? ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: survey.branding.logo }, style: styles.brandLogo, resizeMode: "contain", accessible: !!survey.branding.brandName, accessibilityLabel: survey.branding.brandName })) : null, survey.branding?.brandName ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.brandName, dirText, fontStyle, themeTextSecondary], children: survey.branding.brandName })) : null] })), (0, jsx_runtime_1.jsx)(react_native_1.
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1974
|
+
], children: [survey.branding?.showLogo !== false && survey.branding?.logo ? ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: survey.branding.logo }, style: styles.brandLogo, resizeMode: "contain", accessible: !!survey.branding.brandName, accessibilityLabel: survey.branding.brandName })) : null, survey.branding?.brandName ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.brandName, dirText, fontStyle, themeTextSecondary], children: survey.branding.brandName })) : null] })), isNotifyCard ? ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: notifyImageQ?.imageUrl }, accessibilityLabel: notifyImageQ?.alt || undefined, accessible: !!notifyImageQ?.alt, style: {
|
|
1975
|
+
width: contract.imageSize,
|
|
1976
|
+
height: contract.imageSize,
|
|
1977
|
+
borderRadius: contract.imageRadius,
|
|
1978
|
+
marginRight: 12,
|
|
1979
|
+
flexShrink: 0,
|
|
1980
|
+
}, resizeMode: "cover" })) : null, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: isNotifyCard ? { flex: 1, minWidth: 0 } : undefined, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.title, dirText, fontStyle, themeTextPrimary, titleFS, notifyTitleFS,
|
|
1981
|
+
survey.titleAlign
|
|
1982
|
+
? { textAlign: survey.titleAlign }
|
|
1983
|
+
: null], children: (0, interpolate_1.interpolatePlain)(
|
|
1984
|
+
// Title is plain text; strip any stray rich-text markup +
|
|
1985
|
+
// decode so it never shows literal HTML on native.
|
|
1986
|
+
String((0, translationLookup_1.surveyText)(survey, currentLanguage, 'title', survey.title) || '')
|
|
1987
|
+
.replace(/<[^>]*>/g, '').replace(/ /gi, ' '), evalCtx) }), (() => {
|
|
1988
|
+
// Description is authored as rich HTML; native Text can't render
|
|
1989
|
+
// markup, so strip tags + decode to plain text.
|
|
1990
|
+
const descRaw = (0, translationLookup_1.surveyText)(survey, currentLanguage, 'description', survey.description);
|
|
1991
|
+
const desc = String(descRaw || '').replace(/<[^>]*>/g, '').replace(/ /gi, ' ').trim();
|
|
1992
|
+
return desc ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.description, dirText, fontStyle, themeTextSecondary, descFS, notifyDescFS], children: (0, interpolate_1.interpolatePlain)(desc, evalCtx) })) : null;
|
|
1993
|
+
})()] }), deadlineEl, showProgressTop && progressEl] }), (0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { style: [styles.body, padBody, fsContent], showsVerticalScrollIndicator: false, children: currentQuestions.filter((q) => {
|
|
1994
|
+
// The notification's thumbnail is drawn in the header above;
|
|
1995
|
+
// leaving it here too would render it twice, once full-bleed.
|
|
1996
|
+
if (isNotifyCard && String(q?.id ?? '') === 'notify_image')
|
|
1997
|
+
return false;
|
|
1998
|
+
return true;
|
|
1999
|
+
}).map((q) => {
|
|
1721
2000
|
// v1.1 uses `id`+`required`; legacy uses `name`+`isRequired`.
|
|
1722
2001
|
// The provider normalizes both onto the same object so
|
|
1723
2002
|
// either lookup works.
|
|
@@ -1727,13 +2006,35 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1727
2006
|
// marked with nothing at all.
|
|
1728
2007
|
const isRequired = !!forValidation(q).required
|
|
1729
2008
|
|| q.required || q.isRequired;
|
|
2009
|
+
// An implied question has no prompt: its ACTIONS are the UI,
|
|
2010
|
+
// and its `text` is bookkeeping the author never meant to
|
|
2011
|
+
// show. Web renders the whole block display:none and lifts
|
|
2012
|
+
// the buttons into the nav bar; here the buttons render in
|
|
2013
|
+
// place, so only the label and description are suppressed.
|
|
2014
|
+
// Without this the stray authoring text ("tte") appeared as a
|
|
2015
|
+
// question above the notification's own button.
|
|
2016
|
+
const isImplied = String(q?.type ?? '').toLowerCase() === 'implied';
|
|
1730
2017
|
const qLabel = (0, interpolate_1.interpolatePlain)((0, translationLookup_1.questionText)(survey, currentLanguage, rowKey, 'text', q.title), evalCtx);
|
|
1731
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { ref: (el) => { questionRefs.current[rowKey] = el; }, style: [
|
|
2018
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { ref: (el) => { questionRefs.current[rowKey] = el; }, style: [
|
|
2019
|
+
styles.questionBlock, gapQBlock,
|
|
2020
|
+
// A NOTIFICATION'S ACTION IS NOT A QUESTION IN A LIST.
|
|
2021
|
+
//
|
|
2022
|
+
// questionBlock carries `marginBottom: sectionGap` so
|
|
2023
|
+
// consecutive questions breathe. On a notification the
|
|
2024
|
+
// only "question" is the implied action, so that gap
|
|
2025
|
+
// landed UNDER the button as dead space at the bottom of
|
|
2026
|
+
// the card — the reported extra room the preview does not
|
|
2027
|
+
// have. Web spends 12px above the action and nothing
|
|
2028
|
+
// below it (`.cxp-notify-action-below`); this matches.
|
|
2029
|
+
isNotifyCard && { marginTop: 12, marginBottom: 0 },
|
|
2030
|
+
], children: [isImplied ? null : ((0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: [styles.questionTitle, dirText, fontStyle, themeTextPrimary, qTitleFS],
|
|
1732
2031
|
// The asterisk below is decoration: a screen reader
|
|
1733
2032
|
// reads it as "star", or skips it, so a required
|
|
1734
2033
|
// question sounded exactly like an optional one until
|
|
1735
2034
|
// submitting failed. The label says the word.
|
|
1736
|
-
accessibilityLabel: isRequired ? `${qLabel}, required` : qLabel, children: [questionNumberById[rowKey] ? `Q${questionNumberById[rowKey]}. ` : '', (0, interpolate_1.interpolatePlain)((0, translationLookup_1.questionText)(survey, currentLanguage, rowKey, 'text', q.title), evalCtx), isRequired && (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.required, children: " *" })] }), (() => {
|
|
2035
|
+
accessibilityLabel: isRequired ? `${qLabel}, required` : qLabel, children: [questionNumberById[rowKey] ? `Q${questionNumberById[rowKey]}. ` : '', (0, interpolate_1.interpolatePlain)((0, translationLookup_1.questionText)(survey, currentLanguage, rowKey, 'text', q.title), evalCtx), isRequired && (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.required, children: " *" })] })), (() => {
|
|
2036
|
+
if (isImplied)
|
|
2037
|
+
return null;
|
|
1737
2038
|
const qDesc = (0, translationLookup_1.questionText)(survey, currentLanguage, rowKey, 'description', q.description);
|
|
1738
2039
|
return qDesc ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.questionDesc, dirText, fontStyle, themeTextMuted, qDescFS], children: (0, interpolate_1.interpolatePlain)(qDesc, evalCtx) })) : null;
|
|
1739
2040
|
})(), renderQuestion(q), errors[rowKey] ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.errorText, dirText, fontStyle],
|
|
@@ -1792,6 +2093,8 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1792
2093
|
})()] })] }))] }), (0, jsx_runtime_1.jsx)(handsFreeVoice_1.VoiceOverlay, { visible: voiceActive, phase: voicePhase, transcript: voiceTranscript, onStop: () => (0, handsFreeVoice_1.stopVoice)() })] }) }));
|
|
1793
2094
|
}
|
|
1794
2095
|
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = react_native_1.Dimensions.get('window');
|
|
2096
|
+
/** Web draws a popup at `width:90vw`; the card matches so both read the same. */
|
|
2097
|
+
const CARD_VIEWPORT_FRACTION = 0.9;
|
|
1795
2098
|
/**
|
|
1796
2099
|
* Contract-driven styles. Was a static StyleSheet with 31 hardcoded colours;
|
|
1797
2100
|
* every one now resolves from the style contract, so a branded survey brands the
|
|
@@ -1884,7 +2187,12 @@ const makeStyles = (c) => react_native_1.StyleSheet.create({
|
|
|
1884
2187
|
title: {
|
|
1885
2188
|
color: c.headingColor,
|
|
1886
2189
|
fontSize: c.fontSizeHeading,
|
|
1887
|
-
|
|
2190
|
+
// 600, matching web's `.cxp-widget-title`. At 700 the same string is about
|
|
2191
|
+
// 5% wider, which is enough to tip a notification's headline from one line
|
|
2192
|
+
// to two — the device wrapped "Testing the notification" where the studio
|
|
2193
|
+
// preview kept it on one, and that single difference is what made the two
|
|
2194
|
+
// cards look unalike.
|
|
2195
|
+
fontWeight: '600',
|
|
1888
2196
|
},
|
|
1889
2197
|
description: {
|
|
1890
2198
|
color: c.descriptionColor,
|
|
@@ -1984,13 +2292,19 @@ const makeStyles = (c) => react_native_1.StyleSheet.create({
|
|
|
1984
2292
|
impliedActions: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginTop: 4 },
|
|
1985
2293
|
impliedBtn: {
|
|
1986
2294
|
flexGrow: 1, flexBasis: 140, paddingVertical: 13, paddingHorizontal: 18,
|
|
1987
|
-
|
|
2295
|
+
// From the contract, like every other button. A literal 8 here meant an
|
|
2296
|
+
// authored button radius applied to Next and Back but not to the action a
|
|
2297
|
+
// notification actually offers.
|
|
2298
|
+
borderRadius: c.buttonRadius, alignItems: 'center', borderWidth: 1, borderColor: 'transparent',
|
|
1988
2299
|
},
|
|
1989
|
-
|
|
1990
|
-
|
|
2300
|
+
// The brand accent, not a hardcoded blue. `#2b6cb0` ignored primaryColor
|
|
2301
|
+
// entirely, so a notification's only action was off-brand on every survey —
|
|
2302
|
+
// the same button renders in the authored accent on web.
|
|
2303
|
+
impliedPrimary: { backgroundColor: c.primaryBg },
|
|
2304
|
+
impliedSecondary: { backgroundColor: 'transparent', borderColor: c.inputBorder },
|
|
1991
2305
|
impliedChosen: { opacity: 0.75 },
|
|
1992
|
-
impliedPrimaryLabel: { color:
|
|
1993
|
-
impliedSecondaryLabel: { color:
|
|
2306
|
+
impliedPrimaryLabel: { color: c.primaryText, fontWeight: '600', fontSize: 15 },
|
|
2307
|
+
impliedSecondaryLabel: { color: c.textPrimary, fontWeight: '600', fontSize: 15 },
|
|
1994
2308
|
desktopHint: {
|
|
1995
2309
|
color: c.hintText,
|
|
1996
2310
|
fontSize: 13,
|