@dloizides/ui-forms 1.21.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/index.d.mts +116 -8
- package/dist/index.d.ts +116 -8
- package/dist/index.js +537 -93
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +535 -97
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -550,9 +550,55 @@ var controlStyles = reactNative.StyleSheet.create({
|
|
|
550
550
|
optionText: { fontSize: OPTION_FONT },
|
|
551
551
|
error: { fontSize: ERROR_FONT, marginTop: ERROR_GAP }
|
|
552
552
|
});
|
|
553
|
+
var ACTIVE_WEIGHT = "700";
|
|
554
|
+
var IDLE_WEIGHT = "400";
|
|
555
|
+
var DETAIL_FONT = 12;
|
|
556
|
+
var DETAIL_GAP = 12;
|
|
557
|
+
var STACK_GAP = 2;
|
|
558
|
+
var DETAIL_BESIDE_MIN_MENU_WIDTH = 400;
|
|
559
|
+
var styles7 = reactNative.StyleSheet.create({
|
|
560
|
+
beside: { alignItems: "baseline", flexDirection: "row", gap: DETAIL_GAP, justifyContent: "space-between" },
|
|
561
|
+
stacked: { gap: STACK_GAP },
|
|
562
|
+
shrink: { flexShrink: 1 },
|
|
563
|
+
detail: { flexShrink: 1, fontSize: DETAIL_FONT }
|
|
564
|
+
});
|
|
565
|
+
function optionDomId(testID, value) {
|
|
566
|
+
return `${testID}-option-${value}`.replace(/\s+/g, "_");
|
|
567
|
+
}
|
|
568
|
+
function listboxDomId(testID) {
|
|
569
|
+
return `${testID}-listbox`;
|
|
570
|
+
}
|
|
571
|
+
function isDetailBesideTitle(menuWidth) {
|
|
572
|
+
return menuWidth >= DETAIL_BESIDE_MIN_MENU_WIDTH;
|
|
573
|
+
}
|
|
574
|
+
function OptionRow({ option, isActive, isHighlighted, isListboxRow, menuWidth, hint, colors, testID, onSelect }) {
|
|
575
|
+
const hasDetail = option.detail !== void 0 && option.detail !== "";
|
|
576
|
+
const label = hasDetail ? `${option.label}, ${option.detail}` : option.label;
|
|
577
|
+
const roleProps = isListboxRow ? { role: "option", tabIndex: -1, id: optionDomId(testID, option.value), "aria-selected": isHighlighted } : { accessibilityRole: "menuitem", "aria-selected": isActive };
|
|
578
|
+
const isBold = isActive || isHighlighted;
|
|
579
|
+
const titleColor = isActive ? colors.brand : colors.text;
|
|
580
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
581
|
+
reactNative.Pressable,
|
|
582
|
+
{
|
|
583
|
+
...roleProps,
|
|
584
|
+
accessibilityLabel: label,
|
|
585
|
+
accessibilityHint: hint,
|
|
586
|
+
accessibilityState: { selected: isActive },
|
|
587
|
+
onPress: () => onSelect(option.value),
|
|
588
|
+
style: [controlStyles.option, isHighlighted ? { backgroundColor: colors.border } : null],
|
|
589
|
+
testID: `${testID}-option-${option.value}`,
|
|
590
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: isDetailBesideTitle(menuWidth) ? styles7.beside : styles7.stacked, children: [
|
|
591
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [controlStyles.optionText, styles7.shrink, { color: titleColor, fontWeight: isBold ? ACTIVE_WEIGHT : IDLE_WEIGHT }], children: option.label }),
|
|
592
|
+
hasDetail ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.detail, { color: colors.textSecondary ?? colors.text }], children: option.detail }) : null
|
|
593
|
+
] })
|
|
594
|
+
}
|
|
595
|
+
);
|
|
596
|
+
}
|
|
553
597
|
var IS_WEB3 = reactNative.Platform.OS === "web";
|
|
554
598
|
var MENU_PORTAL_Z_INDEX = designTokens.Z_INDEX.popover;
|
|
555
599
|
var MENU_TOP_GAP2 = 4;
|
|
600
|
+
var MENU_MIN_HEIGHT = 96;
|
|
601
|
+
var VIEWPORT_MARGIN = 8;
|
|
556
602
|
function toDomNode(ref) {
|
|
557
603
|
const node = ref.current;
|
|
558
604
|
if (node === null || typeof node.getBoundingClientRect !== "function") return null;
|
|
@@ -565,8 +611,9 @@ function readAnchorRect(ref) {
|
|
|
565
611
|
const r = node.getBoundingClientRect();
|
|
566
612
|
return { top: r.top, left: r.left, width: r.width, bottom: r.bottom };
|
|
567
613
|
}
|
|
568
|
-
function buildPortalPopoverStyle(rect) {
|
|
614
|
+
function buildPortalPopoverStyle(rect, viewportBottom) {
|
|
569
615
|
return {
|
|
616
|
+
...viewportBottom !== void 0 ? { maxHeight: menuMaxHeight(rect.bottom, viewportBottom) } : null,
|
|
570
617
|
// RN's ViewStyle union omits 'fixed', but react-native-web honours it at runtime.
|
|
571
618
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- web-only position value
|
|
572
619
|
position: "fixed",
|
|
@@ -613,10 +660,64 @@ function useAnchorRect(ref, enabled) {
|
|
|
613
660
|
}, [ref, enabled]);
|
|
614
661
|
return IS_WEB3 && enabled ? rect : null;
|
|
615
662
|
}
|
|
663
|
+
function roomUnderAnchor(anchorBottom, viewportBottom) {
|
|
664
|
+
return viewportBottom - anchorBottom - MENU_TOP_GAP2 - VIEWPORT_MARGIN;
|
|
665
|
+
}
|
|
666
|
+
function menuMaxHeight(anchorBottom, viewportBottom) {
|
|
667
|
+
return Math.min(MENU_MAX_HEIGHT, Math.max(MENU_MIN_HEIGHT, roomUnderAnchor(anchorBottom, viewportBottom)));
|
|
668
|
+
}
|
|
669
|
+
function anchorScrollBlock(anchorBottom, viewportBottom) {
|
|
670
|
+
return roomUnderAnchor(anchorBottom, viewportBottom) < MENU_MAX_HEIGHT ? "start" : "nearest";
|
|
671
|
+
}
|
|
672
|
+
function readViewportBottom() {
|
|
673
|
+
const viewport = window.visualViewport ?? null;
|
|
674
|
+
return viewport === null ? window.innerHeight : viewport.offsetTop + viewport.height;
|
|
675
|
+
}
|
|
676
|
+
function useVisualViewportBottom(enabled) {
|
|
677
|
+
const [bottom, setBottom] = React2.useState(null);
|
|
678
|
+
React2.useLayoutEffect(() => {
|
|
679
|
+
if (!IS_WEB3 || !enabled) {
|
|
680
|
+
setBottom(null);
|
|
681
|
+
return void 0;
|
|
682
|
+
}
|
|
683
|
+
const update = () => setBottom(readViewportBottom());
|
|
684
|
+
update();
|
|
685
|
+
const viewport = window.visualViewport ?? null;
|
|
686
|
+
viewport?.addEventListener("resize", update);
|
|
687
|
+
viewport?.addEventListener("scroll", update);
|
|
688
|
+
window.addEventListener("resize", update);
|
|
689
|
+
return () => {
|
|
690
|
+
viewport?.removeEventListener("resize", update);
|
|
691
|
+
viewport?.removeEventListener("scroll", update);
|
|
692
|
+
window.removeEventListener("resize", update);
|
|
693
|
+
};
|
|
694
|
+
}, [enabled]);
|
|
695
|
+
return bottom;
|
|
696
|
+
}
|
|
697
|
+
function scrollNodeIntoView(node, block) {
|
|
698
|
+
if (node !== null && typeof node.scrollIntoView === "function") node.scrollIntoView({ block });
|
|
699
|
+
}
|
|
700
|
+
function scrollAnchorIntoView(ref, marginTop) {
|
|
701
|
+
if (!IS_WEB3) return;
|
|
702
|
+
const node = toDomNode(ref);
|
|
703
|
+
if (node === null) return;
|
|
704
|
+
node.style.scrollMarginTop = `${marginTop}px`;
|
|
705
|
+
scrollNodeIntoView(node, anchorScrollBlock(node.getBoundingClientRect().bottom, readViewportBottom()));
|
|
706
|
+
}
|
|
707
|
+
function useKeepAnchorInRoom(ref, marginTop, enabled) {
|
|
708
|
+
React2.useLayoutEffect(() => {
|
|
709
|
+
if (!IS_WEB3 || !enabled) return void 0;
|
|
710
|
+
const place = () => scrollAnchorIntoView(ref, marginTop);
|
|
711
|
+
place();
|
|
712
|
+
const viewport = window.visualViewport ?? null;
|
|
713
|
+
viewport?.addEventListener("resize", place);
|
|
714
|
+
return () => viewport?.removeEventListener("resize", place);
|
|
715
|
+
}, [ref, marginTop, enabled]);
|
|
716
|
+
}
|
|
616
717
|
var IS_WEB4 = typeof document !== "undefined";
|
|
617
718
|
var ESCAPE_KEY = "Escape";
|
|
618
|
-
var
|
|
619
|
-
var
|
|
719
|
+
var LISTBOX_ROLE = { role: "listbox" };
|
|
720
|
+
var KEEP_FIELD_FOCUS = { onMouseDown: (event) => event.preventDefault() };
|
|
620
721
|
function useDismiss(anchorRef, menuRef, onDismiss) {
|
|
621
722
|
React2.useEffect(() => {
|
|
622
723
|
if (!IS_WEB4) return void 0;
|
|
@@ -639,6 +740,12 @@ function useDismiss(anchorRef, menuRef, onDismiss) {
|
|
|
639
740
|
};
|
|
640
741
|
}, [anchorRef, menuRef, onDismiss]);
|
|
641
742
|
}
|
|
743
|
+
function useKeepHighlightInView(testID, highlightedValue) {
|
|
744
|
+
React2.useEffect(() => {
|
|
745
|
+
if (!IS_WEB4 || highlightedValue === void 0 || highlightedValue === "") return;
|
|
746
|
+
scrollNodeIntoView(document.getElementById(optionDomId(testID, highlightedValue)), "nearest");
|
|
747
|
+
}, [testID, highlightedValue]);
|
|
748
|
+
}
|
|
642
749
|
function AnchoredMenu({
|
|
643
750
|
options,
|
|
644
751
|
selectedValue,
|
|
@@ -649,38 +756,48 @@ function AnchoredMenu({
|
|
|
649
756
|
accessibilityLabel,
|
|
650
757
|
testID,
|
|
651
758
|
anchorRef,
|
|
652
|
-
usePortal = true
|
|
759
|
+
usePortal = true,
|
|
760
|
+
listbox
|
|
653
761
|
}) {
|
|
654
762
|
const menuRef = React2__default.default.useRef(null);
|
|
655
763
|
useDismiss(anchorRef, menuRef, onDismiss);
|
|
764
|
+
useKeepHighlightInView(testID, listbox?.highlightedValue);
|
|
656
765
|
const rect = useAnchorRect(anchorRef, usePortal);
|
|
657
|
-
const
|
|
658
|
-
const
|
|
766
|
+
const viewportBottom = useVisualViewportBottom(usePortal);
|
|
767
|
+
const portalStyle = rect !== null ? buildPortalPopoverStyle(rect, viewportBottom ?? void 0) : null;
|
|
768
|
+
const { width: windowWidth } = reactNative.useWindowDimensions();
|
|
769
|
+
const menuWidth = rect?.width ?? windowWidth;
|
|
770
|
+
const isListbox = listbox !== void 0;
|
|
771
|
+
const statusText = listbox?.statusText;
|
|
772
|
+
const rows = options.map((option) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
773
|
+
OptionRow,
|
|
774
|
+
{
|
|
775
|
+
colors,
|
|
776
|
+
hint: optionHint,
|
|
777
|
+
isActive: option.value === selectedValue,
|
|
778
|
+
isHighlighted: option.value === listbox?.highlightedValue,
|
|
779
|
+
isListboxRow: isListbox,
|
|
780
|
+
menuWidth,
|
|
781
|
+
option,
|
|
782
|
+
testID,
|
|
783
|
+
onSelect
|
|
784
|
+
},
|
|
785
|
+
option.value
|
|
786
|
+
));
|
|
787
|
+
const listboxBody = options.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { accessibilityLabel, id: listboxDomId(testID), ...LISTBOX_ROLE, testID: listboxDomId(testID), children: rows }) : null;
|
|
788
|
+
const menu = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
659
789
|
reactNative.View,
|
|
660
790
|
{
|
|
791
|
+
...isListbox ? KEEP_FIELD_FOCUS : null,
|
|
661
792
|
ref: menuRef,
|
|
662
|
-
accessibilityLabel,
|
|
663
|
-
accessibilityRole: "menu",
|
|
793
|
+
accessibilityLabel: isListbox ? void 0 : accessibilityLabel,
|
|
794
|
+
accessibilityRole: isListbox ? void 0 : "menu",
|
|
664
795
|
style: [controlStyles.menu, { borderColor: colors.border, backgroundColor: colors.surface }, portalStyle],
|
|
665
796
|
testID: `${testID}-menu`,
|
|
666
|
-
children:
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
{
|
|
671
|
-
accessibilityRole: "menuitem",
|
|
672
|
-
accessibilityLabel: opt.label,
|
|
673
|
-
accessibilityHint: optionHint,
|
|
674
|
-
accessibilityState: { selected: active },
|
|
675
|
-
"aria-selected": active,
|
|
676
|
-
onPress: () => onSelect(opt.value),
|
|
677
|
-
style: controlStyles.option,
|
|
678
|
-
testID: `${testID}-option-${opt.value}`,
|
|
679
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [controlStyles.optionText, { color: active ? colors.brand : colors.text, fontWeight: active ? ACTIVE_WEIGHT : IDLE_WEIGHT }], children: opt.label })
|
|
680
|
-
},
|
|
681
|
-
opt.value
|
|
682
|
-
);
|
|
683
|
-
}) })
|
|
797
|
+
children: [
|
|
798
|
+
isListbox ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { role: "status", style: statusText !== void 0 ? [controlStyles.option, controlStyles.optionText, { color: colors.text }] : void 0, children: statusText ?? "" }) : null,
|
|
799
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.ScrollView, { keyboardShouldPersistTaps: "handled", nestedScrollEnabled: true, children: isListbox ? listboxBody : rows })
|
|
800
|
+
]
|
|
684
801
|
}
|
|
685
802
|
);
|
|
686
803
|
if (usePortal && IS_WEB4) return reactDom.createPortal(menu, document.body);
|
|
@@ -1255,7 +1372,7 @@ function chipIdentity(option, selected, config) {
|
|
|
1255
1372
|
ariaChecked: isRadio ? selected : void 0
|
|
1256
1373
|
};
|
|
1257
1374
|
}
|
|
1258
|
-
var
|
|
1375
|
+
var styles8 = reactNative.StyleSheet.create({
|
|
1259
1376
|
chipContainer: {
|
|
1260
1377
|
flexDirection: "row",
|
|
1261
1378
|
flexWrap: "wrap",
|
|
@@ -1341,7 +1458,7 @@ var ChipSelector = ({
|
|
|
1341
1458
|
reactNative.View,
|
|
1342
1459
|
{
|
|
1343
1460
|
accessibilityRole: isRadioGroup ? "radiogroup" : void 0,
|
|
1344
|
-
style:
|
|
1461
|
+
style: styles8.chipContainer,
|
|
1345
1462
|
...webFieldA11y({ describedById, hasError }),
|
|
1346
1463
|
...radio.groupProps,
|
|
1347
1464
|
children: options.map(renderChip)
|
|
@@ -1351,7 +1468,7 @@ var ChipSelector = ({
|
|
|
1351
1468
|
);
|
|
1352
1469
|
};
|
|
1353
1470
|
var DESCRIPTION_FONT_SIZE2 = 12;
|
|
1354
|
-
var
|
|
1471
|
+
var styles9 = reactNative.StyleSheet.create({
|
|
1355
1472
|
container: {
|
|
1356
1473
|
flexDirection: "row",
|
|
1357
1474
|
justifyContent: "space-between",
|
|
@@ -1412,15 +1529,15 @@ var FormSwitch = ({
|
|
|
1412
1529
|
accessible: false,
|
|
1413
1530
|
disabled,
|
|
1414
1531
|
onPress: toggle,
|
|
1415
|
-
style: [
|
|
1532
|
+
style: [styles9.container, containerStyle],
|
|
1416
1533
|
tabIndex: -1,
|
|
1417
1534
|
testID: `${testID}-row`,
|
|
1418
1535
|
children: [
|
|
1419
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
1420
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
1421
|
-
hasDescription ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
1536
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles9.textColumn, children: [
|
|
1537
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.label, themeStyles.label], children: label }),
|
|
1538
|
+
hasDescription ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.description, themeStyles.description], children: description }) : null
|
|
1422
1539
|
] }),
|
|
1423
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { pointerEvents: "none", style:
|
|
1540
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { pointerEvents: "none", style: styles9.switchSlot, testID: `${testID}-hitbox`, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1424
1541
|
reactNative.Switch,
|
|
1425
1542
|
{
|
|
1426
1543
|
accessibilityHint: accessibilityHint ?? label,
|
|
@@ -1449,7 +1566,7 @@ var DISABLED_OPACITY = 0.5;
|
|
|
1449
1566
|
var FOCUS_RING_WIDTH = 2;
|
|
1450
1567
|
var WEB_NO_DEFAULT_OUTLINE = { outlineStyle: "none" };
|
|
1451
1568
|
var FOCUS_RING_INSET = -6;
|
|
1452
|
-
var
|
|
1569
|
+
var styles10 = reactNative.StyleSheet.create({
|
|
1453
1570
|
container: {
|
|
1454
1571
|
flexDirection: "row",
|
|
1455
1572
|
alignItems: "flex-start",
|
|
@@ -1554,8 +1671,8 @@ var FormCheckbox = ({
|
|
|
1554
1671
|
onPress: toggle,
|
|
1555
1672
|
role: "checkbox",
|
|
1556
1673
|
style: [
|
|
1557
|
-
|
|
1558
|
-
hasHint ? null :
|
|
1674
|
+
styles10.container,
|
|
1675
|
+
hasHint ? null : styles10.centered,
|
|
1559
1676
|
containerStyle,
|
|
1560
1677
|
disabled ? { opacity: DISABLED_OPACITY } : null,
|
|
1561
1678
|
reactNative.Platform.OS === "web" ? WEB_NO_DEFAULT_OUTLINE : null
|
|
@@ -1563,13 +1680,13 @@ var FormCheckbox = ({
|
|
|
1563
1680
|
testID,
|
|
1564
1681
|
...keyboardProps,
|
|
1565
1682
|
children: [
|
|
1566
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [
|
|
1567
|
-
focused ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { pointerEvents: "none", style: [
|
|
1568
|
-
value ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
1683
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles10.box, value ? themeStyles.boxOn : themeStyles.boxOff], testID: `${testID}-box`, children: [
|
|
1684
|
+
focused ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { pointerEvents: "none", style: [styles10.focusRing, themeStyles.ring], testID: `${testID}-focus-ring` }) : null,
|
|
1685
|
+
value ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.check, { color: colors.surface }], accessibilityElementsHidden: true, children: "\u2713" }) : null
|
|
1569
1686
|
] }),
|
|
1570
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
1571
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
1572
|
-
hasHint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { nativeID: hintId, style: [
|
|
1687
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles10.textColumn, children: [
|
|
1688
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.label, themeStyles.label], children: label }),
|
|
1689
|
+
hasHint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { nativeID: hintId, style: [styles10.hint, themeStyles.hint], children: hintText }) : null
|
|
1573
1690
|
] })
|
|
1574
1691
|
]
|
|
1575
1692
|
}
|
|
@@ -1676,94 +1793,212 @@ function isUnmatched(options, text) {
|
|
|
1676
1793
|
if (needle === "") return false;
|
|
1677
1794
|
return !options.some((o) => o.value.toLowerCase() === needle || o.label.toLowerCase() === needle);
|
|
1678
1795
|
}
|
|
1679
|
-
var
|
|
1680
|
-
var
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1796
|
+
var DEFAULT_TYPEAHEAD_DEBOUNCE_MS = 250;
|
|
1797
|
+
var IDLE = { results: [], isLoading: false, hasFailed: false, hasSettled: false };
|
|
1798
|
+
function useTypeaheadSearch(load, query, enabled, debounceMs) {
|
|
1799
|
+
const [state, setState] = React2.useState(IDLE);
|
|
1800
|
+
const loadRef = React2.useRef(load);
|
|
1801
|
+
loadRef.current = load;
|
|
1802
|
+
React2.useEffect(() => {
|
|
1803
|
+
if (!enabled) {
|
|
1804
|
+
setState(IDLE);
|
|
1805
|
+
return void 0;
|
|
1806
|
+
}
|
|
1807
|
+
setState((current) => ({ ...current, isLoading: true, hasFailed: false }));
|
|
1808
|
+
const controller = new AbortController();
|
|
1809
|
+
const settle = (next) => {
|
|
1810
|
+
if (!controller.signal.aborted) setState(next);
|
|
1811
|
+
};
|
|
1812
|
+
const timer = setTimeout(() => {
|
|
1813
|
+
Promise.resolve().then(() => loadRef.current?.(query, controller.signal) ?? []).then(
|
|
1814
|
+
(results) => settle({ results, isLoading: false, hasFailed: false, hasSettled: true }),
|
|
1815
|
+
() => settle({ results: [], isLoading: false, hasFailed: true, hasSettled: true })
|
|
1816
|
+
);
|
|
1817
|
+
}, debounceMs);
|
|
1818
|
+
return () => {
|
|
1819
|
+
clearTimeout(timer);
|
|
1820
|
+
controller.abort();
|
|
1821
|
+
};
|
|
1822
|
+
}, [query, enabled, debounceMs]);
|
|
1823
|
+
return state;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
// src/TypeaheadControl/hooks/useTypeaheadState.ts
|
|
1827
|
+
var NO_HIGHLIGHT = -1;
|
|
1828
|
+
var FORWARD = 1;
|
|
1829
|
+
var BACKWARD = -1;
|
|
1830
|
+
var NO_ROWS = [];
|
|
1831
|
+
function nextHighlight(current, count, delta) {
|
|
1832
|
+
if (count === 0) return NO_HIGHLIGHT;
|
|
1833
|
+
if (current === NO_HIGHLIGHT) return delta > 0 ? 0 : count - 1;
|
|
1834
|
+
return (current + delta + count) % count;
|
|
1835
|
+
}
|
|
1836
|
+
function searchStatusText(found, search) {
|
|
1837
|
+
if (found.hasFailed) return search.errorText;
|
|
1838
|
+
if (found.results.length > 0) return void 0;
|
|
1839
|
+
if (found.isLoading) return search.loadingText;
|
|
1840
|
+
return found.hasSettled ? search.emptyText : void 0;
|
|
1841
|
+
}
|
|
1842
|
+
function useTypeaheadRows(input, isOpen, isPicked) {
|
|
1843
|
+
const { options, search, value, minChars, maxSuggestions } = input;
|
|
1844
|
+
const query = value.trim();
|
|
1845
|
+
const isSearching = search !== void 0 && isOpen && !isPicked && query.length >= minChars;
|
|
1846
|
+
const found = useTypeaheadSearch(search?.load, query, isSearching, search?.debounceMs ?? DEFAULT_TYPEAHEAD_DEBOUNCE_MS);
|
|
1847
|
+
const staticRows = React2.useMemo(() => {
|
|
1848
|
+
if (options === void 0 || options.some((o) => o.label === value)) return NO_ROWS;
|
|
1849
|
+
return suggestOptions(options, value, minChars, maxSuggestions);
|
|
1850
|
+
}, [options, value, minChars, maxSuggestions]);
|
|
1851
|
+
const foundRows = React2.useMemo(() => found.results.slice(0, maxSuggestions), [found.results, maxSuggestions]);
|
|
1852
|
+
if (search === void 0) return { rows: staticRows, statusText: void 0 };
|
|
1853
|
+
return { rows: foundRows, statusText: searchStatusText(found, search) };
|
|
1854
|
+
}
|
|
1855
|
+
function useTypeaheadState(input) {
|
|
1856
|
+
const { search, value, onChange, onSubmit } = input;
|
|
1700
1857
|
const [isOpen, setIsOpen] = React2.useState(false);
|
|
1701
|
-
const
|
|
1702
|
-
const
|
|
1703
|
-
|
|
1704
|
-
|
|
1858
|
+
const [picked, setPicked] = React2.useState(null);
|
|
1859
|
+
const [highlightIndex, setHighlightIndex] = React2.useState(NO_HIGHLIGHT);
|
|
1860
|
+
const { rows, statusText } = useTypeaheadRows(input, isOpen, picked !== null && picked.label === value);
|
|
1861
|
+
const rowKey = rows.map((row) => row.value).join("\n");
|
|
1862
|
+
const highlighted = isOpen ? rows[highlightIndex] : void 0;
|
|
1863
|
+
React2.useEffect(() => setHighlightIndex(NO_HIGHLIGHT), [rowKey]);
|
|
1864
|
+
React2.useEffect(() => setPicked((current) => current !== null && current.label !== value ? null : current), [value]);
|
|
1865
|
+
const open = React2.useCallback(() => setIsOpen(true), []);
|
|
1866
|
+
const close = React2.useCallback(() => {
|
|
1867
|
+
setIsOpen(false);
|
|
1868
|
+
setHighlightIndex(NO_HIGHLIGHT);
|
|
1869
|
+
}, []);
|
|
1870
|
+
const pick = React2.useCallback(
|
|
1871
|
+
(option) => {
|
|
1872
|
+
setPicked(option);
|
|
1873
|
+
onChange(option.label);
|
|
1874
|
+
search?.onPick(option);
|
|
1875
|
+
close();
|
|
1876
|
+
},
|
|
1877
|
+
[onChange, search, close]
|
|
1878
|
+
);
|
|
1879
|
+
const selectValue = React2.useCallback(
|
|
1880
|
+
(optionValue) => {
|
|
1881
|
+
const option = rows.find((row) => row.value === optionValue);
|
|
1882
|
+
if (option !== void 0) pick(option);
|
|
1883
|
+
},
|
|
1884
|
+
[rows, pick]
|
|
1705
1885
|
);
|
|
1706
|
-
const showMenu = isOpen && suggestions.length > 0;
|
|
1707
1886
|
const handleChange = React2.useCallback(
|
|
1708
1887
|
(next) => {
|
|
1888
|
+
if (picked !== null) {
|
|
1889
|
+
setPicked(null);
|
|
1890
|
+
search?.onPick(null);
|
|
1891
|
+
}
|
|
1709
1892
|
onChange(next);
|
|
1710
1893
|
setIsOpen(true);
|
|
1894
|
+
setHighlightIndex(NO_HIGHLIGHT);
|
|
1711
1895
|
},
|
|
1712
|
-
[onChange]
|
|
1896
|
+
[picked, search, onChange]
|
|
1713
1897
|
);
|
|
1714
|
-
const
|
|
1715
|
-
(optionValue) => {
|
|
1716
|
-
const opt = options.find((o) => o.value === optionValue);
|
|
1717
|
-
onChange(opt?.label ?? optionValue);
|
|
1718
|
-
setIsOpen(false);
|
|
1719
|
-
},
|
|
1720
|
-
[options, onChange]
|
|
1721
|
-
);
|
|
1722
|
-
const handleBlur = React2.useCallback(() => {
|
|
1723
|
-
setTimeout(() => setIsOpen(false), CLOSE_DELAY_MS2);
|
|
1724
|
-
}, []);
|
|
1898
|
+
const handleKeyPress = useTypeaheadKeys({ rowCount: rows.length, isOpen, highlighted, setIsOpen, setHighlightIndex, close, pick });
|
|
1725
1899
|
const handleSubmit = React2.useCallback(() => {
|
|
1726
|
-
|
|
1900
|
+
if (highlighted !== void 0) {
|
|
1901
|
+
pick(highlighted);
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
close();
|
|
1727
1905
|
onSubmit?.();
|
|
1728
|
-
}, [onSubmit]);
|
|
1906
|
+
}, [highlighted, pick, close, onSubmit]);
|
|
1907
|
+
return { rows, statusText, isOpen, picked, highlighted, open, close, selectValue, handleChange, handleKeyPress, handleSubmit };
|
|
1908
|
+
}
|
|
1909
|
+
function useTypeaheadKeys(input) {
|
|
1910
|
+
const { rowCount, isOpen, highlighted, setIsOpen, setHighlightIndex, close, pick } = input;
|
|
1911
|
+
return React2.useCallback(
|
|
1912
|
+
(event) => {
|
|
1913
|
+
const { key } = event.nativeEvent;
|
|
1914
|
+
if (key === "ArrowDown" || key === "ArrowUp") {
|
|
1915
|
+
event.preventDefault();
|
|
1916
|
+
setIsOpen(true);
|
|
1917
|
+
setHighlightIndex((current) => nextHighlight(current, rowCount, key === "ArrowDown" ? FORWARD : BACKWARD));
|
|
1918
|
+
} else if (key === "Escape" && isOpen) {
|
|
1919
|
+
event.preventDefault();
|
|
1920
|
+
close();
|
|
1921
|
+
} else if (key === "Enter" && highlighted !== void 0) {
|
|
1922
|
+
event.preventDefault();
|
|
1923
|
+
pick(highlighted);
|
|
1924
|
+
}
|
|
1925
|
+
},
|
|
1926
|
+
[rowCount, isOpen, highlighted, setIsOpen, setHighlightIndex, close, pick]
|
|
1927
|
+
);
|
|
1928
|
+
}
|
|
1929
|
+
var CLOSE_DELAY_MS2 = 140;
|
|
1930
|
+
var DEFAULT_TYPEAHEAD_MIN_CHARS = 1;
|
|
1931
|
+
var DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS = 8;
|
|
1932
|
+
function comboboxAria(testID, isExpanded, highlighted) {
|
|
1933
|
+
return {
|
|
1934
|
+
role: "combobox",
|
|
1935
|
+
"aria-autocomplete": "list",
|
|
1936
|
+
"aria-expanded": isExpanded,
|
|
1937
|
+
"aria-controls": isExpanded ? listboxDomId(testID) : void 0,
|
|
1938
|
+
"aria-activedescendant": highlighted !== void 0 ? optionDomId(testID, highlighted.value) : void 0
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
function TypeaheadControlImpl(props, ref) {
|
|
1942
|
+
const { value, onChange, onSubmit, placeholder, accessibilityLabel, accessibilityHint, optionHint = "", error, minChars = DEFAULT_TYPEAHEAD_MIN_CHARS, style, testID } = props;
|
|
1943
|
+
const { id, describedById } = props;
|
|
1944
|
+
const search = "search" in props ? props.search : void 0;
|
|
1945
|
+
const options = "options" in props ? props.options : void 0;
|
|
1946
|
+
const maxSuggestions = ("search" in props ? props.search.maxResults : props.maxSuggestions) ?? DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS;
|
|
1947
|
+
const { theme } = uiFeedback.useUi();
|
|
1948
|
+
const { colors, palette } = theme;
|
|
1949
|
+
const anchorRef = React2.useRef(null);
|
|
1950
|
+
const state = useTypeaheadState({ options, search, value, minChars, maxSuggestions, onChange, onSubmit });
|
|
1951
|
+
const showMenu = state.isOpen && (state.rows.length > 0 || state.statusText !== void 0);
|
|
1952
|
+
const isExpanded = state.isOpen && state.rows.length > 0;
|
|
1729
1953
|
const hasError = error !== void 0 && error !== "";
|
|
1954
|
+
useKeepAnchorInRoom(anchorRef, search?.scrollMarginTop ?? 0, search !== void 0 && state.isOpen);
|
|
1730
1955
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { ref: anchorRef, style: [controlStyles.anchor, style], children: [
|
|
1731
1956
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1732
1957
|
reactNative.TextInput,
|
|
1733
1958
|
{
|
|
1959
|
+
ref,
|
|
1960
|
+
id,
|
|
1961
|
+
...comboboxAria(testID, isExpanded, state.highlighted),
|
|
1962
|
+
...webFieldA11y({ describedById }),
|
|
1734
1963
|
accessibilityLabel,
|
|
1735
1964
|
accessibilityHint,
|
|
1736
1965
|
autoCapitalize: "none",
|
|
1966
|
+
autoComplete: "off",
|
|
1737
1967
|
autoCorrect: false,
|
|
1968
|
+
blurOnSubmit: search === void 0,
|
|
1738
1969
|
placeholder,
|
|
1739
1970
|
placeholderTextColor: colors.textSecondary,
|
|
1740
1971
|
spellCheck: false,
|
|
1741
1972
|
value,
|
|
1742
|
-
onChangeText: handleChange,
|
|
1743
|
-
onFocus:
|
|
1744
|
-
onBlur:
|
|
1745
|
-
|
|
1973
|
+
onChangeText: state.handleChange,
|
|
1974
|
+
onFocus: state.open,
|
|
1975
|
+
onBlur: () => setTimeout(state.close, CLOSE_DELAY_MS2),
|
|
1976
|
+
onKeyPress: state.handleKeyPress,
|
|
1977
|
+
onSubmitEditing: state.handleSubmit,
|
|
1746
1978
|
style: [controlStyles.input, { borderColor: hasError ? fieldErrorInk(theme) : colors.border, backgroundColor: colors.surface, color: colors.text }],
|
|
1747
|
-
testID: `${testID}-input`
|
|
1979
|
+
testID: search?.inputTestID ?? `${testID}-input`
|
|
1748
1980
|
}
|
|
1749
1981
|
),
|
|
1750
1982
|
showMenu ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1751
1983
|
AnchoredMenu,
|
|
1752
1984
|
{
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1985
|
+
accessibilityLabel,
|
|
1986
|
+
options: state.rows,
|
|
1987
|
+
selectedValue: search !== void 0 ? state.picked?.value ?? "" : value,
|
|
1988
|
+
onSelect: state.selectValue,
|
|
1989
|
+
onDismiss: state.close,
|
|
1990
|
+
colors: { text: colors.text, textSecondary: colors.textSecondary, border: colors.border, surface: colors.surface, brand: palette.primary["500"] },
|
|
1758
1991
|
optionHint,
|
|
1759
1992
|
testID,
|
|
1760
1993
|
anchorRef,
|
|
1994
|
+
listbox: { highlightedValue: state.highlighted?.value ?? "", statusText: state.statusText },
|
|
1761
1995
|
usePortal: true
|
|
1762
1996
|
}
|
|
1763
1997
|
) : null,
|
|
1764
1998
|
hasError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { role: "alert", style: [controlStyles.error, { color: fieldErrorInk(theme) }], testID: `${testID}-error`, children: error }) : null
|
|
1765
1999
|
] });
|
|
1766
2000
|
}
|
|
2001
|
+
var TypeaheadControl = React2.forwardRef(TypeaheadControlImpl);
|
|
1767
2002
|
var WEB_DATE_PROPS = typeof document !== "undefined" ? { type: "date" } : {};
|
|
1768
2003
|
function DateInput(p) {
|
|
1769
2004
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: controlStyles.dateCol, children: [
|
|
@@ -1860,6 +2095,209 @@ function toggleGroupSelection(selected, groupKeys, order) {
|
|
|
1860
2095
|
});
|
|
1861
2096
|
return order === void 0 ? merged : order.filter((key) => merged.includes(key));
|
|
1862
2097
|
}
|
|
2098
|
+
var ROW_GAP = 4;
|
|
2099
|
+
var TAB_PADDING = 4;
|
|
2100
|
+
var INDICATOR_WIDTH = 2;
|
|
2101
|
+
var TAB_FONT_SIZE = 12;
|
|
2102
|
+
var styles11 = reactNative.StyleSheet.create({
|
|
2103
|
+
row: { flexDirection: "row", flexWrap: "nowrap", gap: ROW_GAP },
|
|
2104
|
+
tab: {
|
|
2105
|
+
borderBottomWidth: INDICATOR_WIDTH,
|
|
2106
|
+
flexBasis: 0,
|
|
2107
|
+
flexGrow: 1,
|
|
2108
|
+
flexShrink: 1,
|
|
2109
|
+
justifyContent: "center",
|
|
2110
|
+
minHeight: a11y.MIN_TOUCH_TARGET_PX,
|
|
2111
|
+
minWidth: 0,
|
|
2112
|
+
paddingHorizontal: TAB_PADDING
|
|
2113
|
+
},
|
|
2114
|
+
text: { fontSize: TAB_FONT_SIZE, textAlign: "center" },
|
|
2115
|
+
textSelected: { fontWeight: "700" }
|
|
2116
|
+
});
|
|
2117
|
+
var StepperTab = ({
|
|
2118
|
+
step,
|
|
2119
|
+
selected,
|
|
2120
|
+
hint,
|
|
2121
|
+
onStep,
|
|
2122
|
+
testID
|
|
2123
|
+
}) => {
|
|
2124
|
+
const { theme } = uiFeedback.useUi();
|
|
2125
|
+
const ring = uiButtons.useFocusRing();
|
|
2126
|
+
const indicator = selected ? theme.palette.primary["500"] : theme.colors.border;
|
|
2127
|
+
const webKeyProps = {
|
|
2128
|
+
onKeyDown: (event) => {
|
|
2129
|
+
if (event.key !== " ") return;
|
|
2130
|
+
event.preventDefault?.();
|
|
2131
|
+
onStep(step.key);
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2135
|
+
reactNative.Pressable,
|
|
2136
|
+
{
|
|
2137
|
+
accessibilityHint: hint,
|
|
2138
|
+
accessibilityLabel: step.label,
|
|
2139
|
+
accessibilityRole: "tab",
|
|
2140
|
+
accessibilityState: { selected },
|
|
2141
|
+
"aria-selected": selected,
|
|
2142
|
+
style: [styles11.tab, { borderBottomColor: indicator }, ring.ringStyle],
|
|
2143
|
+
testID,
|
|
2144
|
+
onBlur: ring.onBlur,
|
|
2145
|
+
onFocus: ring.onFocus,
|
|
2146
|
+
onPress: () => onStep(step.key),
|
|
2147
|
+
...webKeyProps,
|
|
2148
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles11.text, { color: theme.colors.text }, selected ? styles11.textSelected : void 0], children: step.label })
|
|
2149
|
+
}
|
|
2150
|
+
);
|
|
2151
|
+
};
|
|
2152
|
+
var StepperTabs = ({
|
|
2153
|
+
steps,
|
|
2154
|
+
current,
|
|
2155
|
+
onStep,
|
|
2156
|
+
stepHint,
|
|
2157
|
+
testID
|
|
2158
|
+
}) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { accessibilityRole: "tablist", style: styles11.row, testID, children: steps.map((step) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2159
|
+
StepperTab,
|
|
2160
|
+
{
|
|
2161
|
+
hint: stepHint,
|
|
2162
|
+
selected: step.key === current,
|
|
2163
|
+
step,
|
|
2164
|
+
testID: `${testID}-${step.key}`,
|
|
2165
|
+
onStep
|
|
2166
|
+
},
|
|
2167
|
+
step.key
|
|
2168
|
+
)) });
|
|
2169
|
+
|
|
2170
|
+
// src/Stepper/stepperModel.ts
|
|
2171
|
+
function resolveStepNeighbours(steps, current) {
|
|
2172
|
+
const index = steps.findIndex((step) => step.key === current);
|
|
2173
|
+
if (index < 0) return { previous: null, next: null };
|
|
2174
|
+
return { previous: steps[index - 1] ?? null, next: steps[index + 1] ?? null };
|
|
2175
|
+
}
|
|
2176
|
+
var ROW_GAP2 = 8;
|
|
2177
|
+
var styles12 = reactNative.StyleSheet.create({
|
|
2178
|
+
cell: { flex: 1, minWidth: 0 },
|
|
2179
|
+
row: { flexDirection: "row", gap: ROW_GAP2 }
|
|
2180
|
+
});
|
|
2181
|
+
var StepperNav = ({
|
|
2182
|
+
steps,
|
|
2183
|
+
current,
|
|
2184
|
+
onStep,
|
|
2185
|
+
labels,
|
|
2186
|
+
testID
|
|
2187
|
+
}) => {
|
|
2188
|
+
const { previous, next } = resolveStepNeighbours(steps, current);
|
|
2189
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles12.row, testID, children: [
|
|
2190
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles12.cell, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2191
|
+
uiButtons.Button,
|
|
2192
|
+
{
|
|
2193
|
+
fullWidth: true,
|
|
2194
|
+
accessibilityHint: labels.backHint,
|
|
2195
|
+
accessibilityLabel: labels.back,
|
|
2196
|
+
autoLoading: false,
|
|
2197
|
+
disabled: previous === null,
|
|
2198
|
+
label: labels.back,
|
|
2199
|
+
testID: `${testID}-back`,
|
|
2200
|
+
variant: "outline",
|
|
2201
|
+
onPress: () => {
|
|
2202
|
+
if (previous !== null) onStep(previous.key);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
) }),
|
|
2206
|
+
next === null ? null : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles12.cell, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2207
|
+
uiButtons.Button,
|
|
2208
|
+
{
|
|
2209
|
+
fullWidth: true,
|
|
2210
|
+
accessibilityHint: labels.nextHint,
|
|
2211
|
+
accessibilityLabel: labels.next(next),
|
|
2212
|
+
autoLoading: false,
|
|
2213
|
+
label: labels.next(next),
|
|
2214
|
+
testID: `${testID}-next`,
|
|
2215
|
+
onPress: () => onStep(next.key)
|
|
2216
|
+
}
|
|
2217
|
+
) })
|
|
2218
|
+
] });
|
|
2219
|
+
};
|
|
2220
|
+
var STATUS_FONT = 16;
|
|
2221
|
+
var styles13 = reactNative.StyleSheet.create({
|
|
2222
|
+
line: { fontSize: STATUS_FONT }
|
|
2223
|
+
});
|
|
2224
|
+
function FormStatusLine({ message, testID, style }) {
|
|
2225
|
+
const { theme } = uiFeedback.useUi();
|
|
2226
|
+
if (message === null || message === void 0 || message === "") return null;
|
|
2227
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { role: "alert", style: [styles13.line, { color: theme.colors.text }, style], testID, children: message });
|
|
2228
|
+
}
|
|
2229
|
+
function useFocusFirstInvalid(refs) {
|
|
2230
|
+
const latest = React2.useRef(refs);
|
|
2231
|
+
latest.current = refs;
|
|
2232
|
+
return React2.useCallback((invalid) => {
|
|
2233
|
+
const first = invalid.map((field) => latest.current[field].current).find((node) => node !== null);
|
|
2234
|
+
first?.focus();
|
|
2235
|
+
}, []);
|
|
2236
|
+
}
|
|
2237
|
+
var DEFAULT_PREVIEW_PX = 48;
|
|
2238
|
+
var PREVIEW_RADIUS = 8;
|
|
2239
|
+
var GAP = 8;
|
|
2240
|
+
var TITLE_FONT_SIZE = 14;
|
|
2241
|
+
var HELP_FONT_SIZE = 13;
|
|
2242
|
+
var styles14 = reactNative.StyleSheet.create({
|
|
2243
|
+
help: { flex: 1, fontSize: HELP_FONT_SIZE },
|
|
2244
|
+
root: { gap: GAP },
|
|
2245
|
+
tile: { alignItems: "center", flexDirection: "row", gap: GAP },
|
|
2246
|
+
title: { fontSize: TITLE_FONT_SIZE, fontWeight: "700" },
|
|
2247
|
+
toggle: { alignSelf: "flex-start", justifyContent: "center", minHeight: a11y.MIN_TOUCH_TARGET_PX },
|
|
2248
|
+
toggleText: { fontSize: TITLE_FONT_SIZE, fontWeight: "700", textDecorationLine: "underline" }
|
|
2249
|
+
});
|
|
2250
|
+
var UploadWithLinkField = ({
|
|
2251
|
+
value,
|
|
2252
|
+
onChange,
|
|
2253
|
+
labels,
|
|
2254
|
+
uploadControl,
|
|
2255
|
+
testID,
|
|
2256
|
+
disabled = false,
|
|
2257
|
+
previewSize = DEFAULT_PREVIEW_PX
|
|
2258
|
+
}) => {
|
|
2259
|
+
const { theme } = uiFeedback.useUi();
|
|
2260
|
+
const ring = uiButtons.useFocusRing();
|
|
2261
|
+
const url = value ?? "";
|
|
2262
|
+
const [pasting, setPasting] = React2.useState(url !== "");
|
|
2263
|
+
const previewBox = { borderRadius: PREVIEW_RADIUS, height: previewSize, width: previewSize };
|
|
2264
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles14.root, children: [
|
|
2265
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles14.title, { color: theme.colors.text }], children: labels.title }),
|
|
2266
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles14.tile, children: [
|
|
2267
|
+
url === "" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [previewBox, { backgroundColor: theme.colors.border }] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Image, { accessible: false, source: { uri: url }, style: previewBox, testID: `${testID}-preview` }),
|
|
2268
|
+
labels.help === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles14.help, { color: theme.colors.text }], children: labels.help })
|
|
2269
|
+
] }),
|
|
2270
|
+
uploadControl,
|
|
2271
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2272
|
+
reactNative.Pressable,
|
|
2273
|
+
{
|
|
2274
|
+
accessibilityHint: labels.pasteToggleHint,
|
|
2275
|
+
accessibilityLabel: labels.pasteToggle,
|
|
2276
|
+
accessibilityRole: "button",
|
|
2277
|
+
accessibilityState: { expanded: pasting },
|
|
2278
|
+
"aria-expanded": pasting,
|
|
2279
|
+
style: [styles14.toggle, ring.ringStyle],
|
|
2280
|
+
testID: `${testID}-paste`,
|
|
2281
|
+
onBlur: ring.onBlur,
|
|
2282
|
+
onFocus: ring.onFocus,
|
|
2283
|
+
onPress: () => setPasting(!pasting),
|
|
2284
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles14.toggleText, { color: theme.colors.text }], children: labels.pasteToggle })
|
|
2285
|
+
}
|
|
2286
|
+
),
|
|
2287
|
+
pasting ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2288
|
+
FormField,
|
|
2289
|
+
{
|
|
2290
|
+
accessibilityHint: labels.linkHint,
|
|
2291
|
+
editable: !disabled,
|
|
2292
|
+
label: labels.link,
|
|
2293
|
+
placeholder: labels.linkPlaceholder,
|
|
2294
|
+
testID,
|
|
2295
|
+
value: url,
|
|
2296
|
+
onChangeText: onChange
|
|
2297
|
+
}
|
|
2298
|
+
) : null
|
|
2299
|
+
] });
|
|
2300
|
+
};
|
|
1863
2301
|
|
|
1864
2302
|
exports.AlignToControl = AlignToControl;
|
|
1865
2303
|
exports.AnchoredMenu = AnchoredMenu;
|
|
@@ -1876,19 +2314,25 @@ exports.FormCheckbox = FormCheckbox;
|
|
|
1876
2314
|
exports.FormField = FormField;
|
|
1877
2315
|
exports.FormGrid = FormGrid;
|
|
1878
2316
|
exports.FormSection = FormSection;
|
|
2317
|
+
exports.FormStatusLine = FormStatusLine;
|
|
1879
2318
|
exports.FormSwitch = FormSwitch;
|
|
1880
2319
|
exports.INLINE_FIELD_ROW_MIN_WIDTH = INLINE_FIELD_ROW_MIN_WIDTH;
|
|
1881
2320
|
exports.InlineFieldRow = InlineFieldRow;
|
|
1882
2321
|
exports.SelectControl = SelectControl;
|
|
2322
|
+
exports.StepperNav = StepperNav;
|
|
2323
|
+
exports.StepperTabs = StepperTabs;
|
|
1883
2324
|
exports.ThemedTextInput = ThemedTextInput;
|
|
1884
2325
|
exports.TypeaheadControl = TypeaheadControl;
|
|
2326
|
+
exports.UploadWithLinkField = UploadWithLinkField;
|
|
1885
2327
|
exports.controlStyles = controlStyles;
|
|
1886
2328
|
exports.groupAriaChecked = groupAriaChecked;
|
|
1887
2329
|
exports.inlineFieldRowLayout = inlineFieldRowLayout;
|
|
1888
2330
|
exports.isUnmatched = isUnmatched;
|
|
1889
2331
|
exports.resolveGroupSelection = resolveGroupSelection;
|
|
2332
|
+
exports.resolveStepNeighbours = resolveStepNeighbours;
|
|
1890
2333
|
exports.suggestOptions = suggestOptions;
|
|
1891
2334
|
exports.toggleGroupSelection = toggleGroupSelection;
|
|
2335
|
+
exports.useFocusFirstInvalid = useFocusFirstInvalid;
|
|
1892
2336
|
exports.useRecentValues = useRecentValues;
|
|
1893
2337
|
exports.useThemedInput = useThemedInput;
|
|
1894
2338
|
//# sourceMappingURL=index.js.map
|