@dloizides/ui-forms 1.13.3 → 1.14.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 +22 -0
- package/dist/index.d.mts +68 -11
- package/dist/index.d.ts +68 -11
- package/dist/index.js +326 -147
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +327 -150
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ var reactNative = require('react-native');
|
|
|
5
5
|
var uiFeedback = require('@dloizides/ui-feedback');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
var uiButtons = require('@dloizides/ui-buttons');
|
|
8
|
-
var a11y = require('@dloizides/a11y');
|
|
9
8
|
var reactDom = require('react-dom');
|
|
9
|
+
var a11y = require('@dloizides/a11y');
|
|
10
10
|
|
|
11
11
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
12
|
|
|
@@ -465,6 +465,142 @@ var controlStyles = reactNative.StyleSheet.create({
|
|
|
465
465
|
error: { fontSize: ERROR_FONT, marginTop: ERROR_GAP }
|
|
466
466
|
});
|
|
467
467
|
var IS_WEB3 = reactNative.Platform.OS === "web";
|
|
468
|
+
var MENU_PORTAL_Z_INDEX = 1e3;
|
|
469
|
+
var MENU_TOP_GAP2 = 4;
|
|
470
|
+
function toDomNode(ref) {
|
|
471
|
+
const node = ref.current;
|
|
472
|
+
if (node === null || typeof node.getBoundingClientRect !== "function") return null;
|
|
473
|
+
return node;
|
|
474
|
+
}
|
|
475
|
+
function readAnchorRect(ref) {
|
|
476
|
+
if (!IS_WEB3) return null;
|
|
477
|
+
const node = toDomNode(ref);
|
|
478
|
+
if (node === null) return null;
|
|
479
|
+
const r = node.getBoundingClientRect();
|
|
480
|
+
return { top: r.top, left: r.left, width: r.width, bottom: r.bottom };
|
|
481
|
+
}
|
|
482
|
+
function buildPortalPopoverStyle(rect) {
|
|
483
|
+
return {
|
|
484
|
+
// RN's ViewStyle union omits 'fixed', but react-native-web honours it at runtime.
|
|
485
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- web-only position value
|
|
486
|
+
position: "fixed",
|
|
487
|
+
top: rect.bottom + MENU_TOP_GAP2,
|
|
488
|
+
left: rect.left,
|
|
489
|
+
width: rect.width,
|
|
490
|
+
zIndex: MENU_PORTAL_Z_INDEX
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
function useAnchorRect(ref, enabled) {
|
|
494
|
+
const [rect, setRect] = React2.useState(null);
|
|
495
|
+
React2.useLayoutEffect(() => {
|
|
496
|
+
if (!IS_WEB3 || !enabled) {
|
|
497
|
+
setRect(null);
|
|
498
|
+
return void 0;
|
|
499
|
+
}
|
|
500
|
+
let frame = null;
|
|
501
|
+
let disposed = false;
|
|
502
|
+
const apply = () => {
|
|
503
|
+
frame = null;
|
|
504
|
+
if (disposed) return;
|
|
505
|
+
setRect(readAnchorRect(ref));
|
|
506
|
+
};
|
|
507
|
+
const schedule = () => {
|
|
508
|
+
if (frame !== null) return;
|
|
509
|
+
frame = window.requestAnimationFrame(apply);
|
|
510
|
+
};
|
|
511
|
+
apply();
|
|
512
|
+
document.addEventListener("scroll", schedule, true);
|
|
513
|
+
window.addEventListener("resize", schedule);
|
|
514
|
+
const node = toDomNode(ref);
|
|
515
|
+
const observer = typeof ResizeObserver === "function" ? new ResizeObserver(schedule) : null;
|
|
516
|
+
if (observer !== null) {
|
|
517
|
+
if (node !== null) observer.observe(node);
|
|
518
|
+
observer.observe(document.body);
|
|
519
|
+
}
|
|
520
|
+
return () => {
|
|
521
|
+
disposed = true;
|
|
522
|
+
if (frame !== null) window.cancelAnimationFrame(frame);
|
|
523
|
+
document.removeEventListener("scroll", schedule, true);
|
|
524
|
+
window.removeEventListener("resize", schedule);
|
|
525
|
+
if (observer !== null) observer.disconnect();
|
|
526
|
+
};
|
|
527
|
+
}, [ref, enabled]);
|
|
528
|
+
return IS_WEB3 && enabled ? rect : null;
|
|
529
|
+
}
|
|
530
|
+
var IS_WEB4 = typeof document !== "undefined";
|
|
531
|
+
var ESCAPE_KEY = "Escape";
|
|
532
|
+
var ACTIVE_WEIGHT = "700";
|
|
533
|
+
var IDLE_WEIGHT = "400";
|
|
534
|
+
function useDismiss(anchorRef, menuRef, onDismiss) {
|
|
535
|
+
React2.useEffect(() => {
|
|
536
|
+
if (!IS_WEB4) return void 0;
|
|
537
|
+
const onMouseDown = (event) => {
|
|
538
|
+
const anchorNode = anchorRef.current;
|
|
539
|
+
const menuNode = menuRef.current;
|
|
540
|
+
const target = event.target instanceof Node ? event.target : null;
|
|
541
|
+
const insideAnchor = anchorNode !== null && target !== null && anchorNode.contains(target);
|
|
542
|
+
const insideMenu = menuNode !== null && target !== null && menuNode.contains(target);
|
|
543
|
+
if (!insideAnchor && !insideMenu) onDismiss();
|
|
544
|
+
};
|
|
545
|
+
const onKeyDown = (event) => {
|
|
546
|
+
if (event.key === ESCAPE_KEY) onDismiss();
|
|
547
|
+
};
|
|
548
|
+
document.addEventListener("mousedown", onMouseDown);
|
|
549
|
+
document.addEventListener("keydown", onKeyDown);
|
|
550
|
+
return () => {
|
|
551
|
+
document.removeEventListener("mousedown", onMouseDown);
|
|
552
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
553
|
+
};
|
|
554
|
+
}, [anchorRef, menuRef, onDismiss]);
|
|
555
|
+
}
|
|
556
|
+
function AnchoredMenu({
|
|
557
|
+
options,
|
|
558
|
+
selectedValue,
|
|
559
|
+
onSelect,
|
|
560
|
+
onDismiss,
|
|
561
|
+
colors,
|
|
562
|
+
optionHint,
|
|
563
|
+
accessibilityLabel,
|
|
564
|
+
testID,
|
|
565
|
+
anchorRef,
|
|
566
|
+
usePortal = true
|
|
567
|
+
}) {
|
|
568
|
+
const menuRef = React2__default.default.useRef(null);
|
|
569
|
+
useDismiss(anchorRef, menuRef, onDismiss);
|
|
570
|
+
const rect = useAnchorRect(anchorRef, usePortal);
|
|
571
|
+
const portalStyle = rect !== null ? buildPortalPopoverStyle(rect) : null;
|
|
572
|
+
const menu = /* @__PURE__ */ jsxRuntime.jsx(
|
|
573
|
+
reactNative.View,
|
|
574
|
+
{
|
|
575
|
+
ref: menuRef,
|
|
576
|
+
accessibilityLabel,
|
|
577
|
+
accessibilityRole: "menu",
|
|
578
|
+
style: [controlStyles.menu, { borderColor: colors.border, backgroundColor: colors.surface }, portalStyle],
|
|
579
|
+
testID: `${testID}-menu`,
|
|
580
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ScrollView, { keyboardShouldPersistTaps: "handled", nestedScrollEnabled: true, children: options.map((opt) => {
|
|
581
|
+
const active = opt.value === selectedValue;
|
|
582
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
583
|
+
reactNative.Pressable,
|
|
584
|
+
{
|
|
585
|
+
accessibilityRole: "menuitem",
|
|
586
|
+
accessibilityLabel: opt.label,
|
|
587
|
+
accessibilityHint: optionHint,
|
|
588
|
+
accessibilityState: { selected: active },
|
|
589
|
+
"aria-selected": active,
|
|
590
|
+
onPress: () => onSelect(opt.value),
|
|
591
|
+
style: controlStyles.option,
|
|
592
|
+
testID: `${testID}-option-${opt.value}`,
|
|
593
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [controlStyles.optionText, { color: active ? colors.brand : colors.text, fontWeight: active ? ACTIVE_WEIGHT : IDLE_WEIGHT }], children: opt.label })
|
|
594
|
+
},
|
|
595
|
+
opt.value
|
|
596
|
+
);
|
|
597
|
+
}) })
|
|
598
|
+
}
|
|
599
|
+
);
|
|
600
|
+
if (usePortal && IS_WEB4) return reactDom.createPortal(menu, document.body);
|
|
601
|
+
return menu;
|
|
602
|
+
}
|
|
603
|
+
var IS_WEB5 = reactNative.Platform.OS === "web";
|
|
468
604
|
var TRANSITION_PROPERTY = "border-color, background-color, box-shadow";
|
|
469
605
|
var TRANSITION_DURATION = "0.14s";
|
|
470
606
|
var TRANSITION_TIMING = "ease";
|
|
@@ -480,7 +616,7 @@ var GREEN_START = 3;
|
|
|
480
616
|
var BLUE_START = 5;
|
|
481
617
|
var CHANNEL_END_OFFSET = 2;
|
|
482
618
|
function prefersReducedMotion() {
|
|
483
|
-
if (!
|
|
619
|
+
if (!IS_WEB5 || typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
484
620
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
485
621
|
}
|
|
486
622
|
function withAlpha(color, alpha) {
|
|
@@ -523,7 +659,7 @@ function useThemedInput(options = {}) {
|
|
|
523
659
|
const borderColor = isFocused ? focusBorder : isHovered ? withAlpha(colors.text, HOVER_BORDER_ALPHA) : restBorder;
|
|
524
660
|
const backgroundColor = isFocused ? colors.surface : colors.surfaceElevated;
|
|
525
661
|
const base = { color: colors.text, borderColor, backgroundColor };
|
|
526
|
-
if (!
|
|
662
|
+
if (!IS_WEB5) return base;
|
|
527
663
|
const ringColor = hasError ? errorColor : primary;
|
|
528
664
|
const ring = isFocused ? `0 0 0 ${FOCUS_RING_WIDTH}px ${withAlpha(ringColor, FOCUS_RING_ALPHA)}` : "none";
|
|
529
665
|
const webStyle = {
|
|
@@ -548,7 +684,7 @@ function useThemedInput(options = {}) {
|
|
|
548
684
|
]);
|
|
549
685
|
const focusBind = React2.useMemo(() => ({ onFocus: handleFocus, onBlur: handleBlur }), [handleFocus, handleBlur]);
|
|
550
686
|
const hoverBind = React2.useMemo(
|
|
551
|
-
() =>
|
|
687
|
+
() => IS_WEB5 ? { onMouseEnter: handleHoverIn, onMouseLeave: handleHoverOut } : {},
|
|
552
688
|
[handleHoverIn, handleHoverOut]
|
|
553
689
|
);
|
|
554
690
|
return {
|
|
@@ -560,9 +696,9 @@ function useThemedInput(options = {}) {
|
|
|
560
696
|
hoverBind
|
|
561
697
|
};
|
|
562
698
|
}
|
|
563
|
-
var
|
|
699
|
+
var IS_WEB6 = reactNative.Platform.OS === "web";
|
|
564
700
|
function webFieldA11y({ describedById, hasError = false, required = false }) {
|
|
565
|
-
if (!
|
|
701
|
+
if (!IS_WEB6) {
|
|
566
702
|
return {};
|
|
567
703
|
}
|
|
568
704
|
return {
|
|
@@ -612,12 +748,187 @@ var ThemedTextInput = React2__default.default.forwardRef(function ThemedTextInpu
|
|
|
612
748
|
}
|
|
613
749
|
);
|
|
614
750
|
});
|
|
751
|
+
|
|
752
|
+
// src/useRecentValues/recentValuesCore.ts
|
|
753
|
+
var STORAGE_PREFIX = "@dloizides/ui-forms:recent:";
|
|
754
|
+
var DEFAULT_MAX_RECENTS = 10;
|
|
755
|
+
function recentStorageKey(key) {
|
|
756
|
+
return `${STORAGE_PREFIX}${key}`;
|
|
757
|
+
}
|
|
758
|
+
function getStorage() {
|
|
759
|
+
try {
|
|
760
|
+
if (typeof window === "undefined") return null;
|
|
761
|
+
const storage = window.localStorage;
|
|
762
|
+
return storage !== null && storage !== void 0 ? storage : null;
|
|
763
|
+
} catch {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
function readRecents(key) {
|
|
768
|
+
const storage = getStorage();
|
|
769
|
+
if (storage === null) return [];
|
|
770
|
+
try {
|
|
771
|
+
const raw = storage.getItem(recentStorageKey(key));
|
|
772
|
+
if (raw === null) return [];
|
|
773
|
+
const parsed = JSON.parse(raw);
|
|
774
|
+
if (!Array.isArray(parsed)) return [];
|
|
775
|
+
return parsed.filter((entry) => typeof entry === "string");
|
|
776
|
+
} catch {
|
|
777
|
+
return [];
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
function writeRecents(key, values) {
|
|
781
|
+
const storage = getStorage();
|
|
782
|
+
if (storage === null) return;
|
|
783
|
+
try {
|
|
784
|
+
storage.setItem(recentStorageKey(key), JSON.stringify(values));
|
|
785
|
+
} catch {
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
function clearRecents(key) {
|
|
789
|
+
const storage = getStorage();
|
|
790
|
+
if (storage === null) return;
|
|
791
|
+
try {
|
|
792
|
+
storage.removeItem(recentStorageKey(key));
|
|
793
|
+
} catch {
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
function computeNextRecents(current, value, max) {
|
|
797
|
+
const trimmed = value.trim();
|
|
798
|
+
if (trimmed === "") return [...current];
|
|
799
|
+
const lower = trimmed.toLowerCase();
|
|
800
|
+
const withoutMatch = current.filter((entry) => entry.toLowerCase() !== lower);
|
|
801
|
+
const promoted = [trimmed, ...withoutMatch];
|
|
802
|
+
const cap = max > 0 ? max : 0;
|
|
803
|
+
return promoted.slice(0, cap);
|
|
804
|
+
}
|
|
805
|
+
function recentsEqual(a, b) {
|
|
806
|
+
if (a.length !== b.length) return false;
|
|
807
|
+
return a.every((entry, index) => entry === b[index]);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/useRecentValues/useRecentValues.ts
|
|
811
|
+
function useRecentValues(key, max = DEFAULT_MAX_RECENTS) {
|
|
812
|
+
const [recents, setRecents] = React2.useState(() => readRecents(key).slice(0, max));
|
|
813
|
+
React2.useEffect(() => {
|
|
814
|
+
setRecents(readRecents(key).slice(0, max));
|
|
815
|
+
}, [key, max]);
|
|
816
|
+
const remember = React2.useCallback(
|
|
817
|
+
(value) => {
|
|
818
|
+
setRecents((current) => {
|
|
819
|
+
const next = computeNextRecents(current, value, max);
|
|
820
|
+
if (recentsEqual(next, current)) return current;
|
|
821
|
+
writeRecents(key, next);
|
|
822
|
+
return next;
|
|
823
|
+
});
|
|
824
|
+
},
|
|
825
|
+
[key, max]
|
|
826
|
+
);
|
|
827
|
+
const clear = React2.useCallback(() => {
|
|
828
|
+
clearRecents(key);
|
|
829
|
+
setRecents([]);
|
|
830
|
+
}, [key]);
|
|
831
|
+
return { recents, remember, clear };
|
|
832
|
+
}
|
|
833
|
+
var CLOSE_DELAY_MS = 140;
|
|
834
|
+
function RecentInputField({
|
|
835
|
+
recentKey,
|
|
836
|
+
recentMax,
|
|
837
|
+
label,
|
|
838
|
+
value,
|
|
839
|
+
testID,
|
|
840
|
+
onChangeText,
|
|
841
|
+
onSubmitEditing,
|
|
842
|
+
onFocus,
|
|
843
|
+
onBlur,
|
|
844
|
+
...rest
|
|
845
|
+
}) {
|
|
846
|
+
const { theme } = uiFeedback.useUi();
|
|
847
|
+
const { colors, palette } = theme;
|
|
848
|
+
const { recents, remember } = useRecentValues(recentKey, recentMax);
|
|
849
|
+
const anchorRef = React2.useRef(null);
|
|
850
|
+
const latestText = React2.useRef(typeof value === "string" ? value : "");
|
|
851
|
+
const [isOpen, setIsOpen] = React2.useState(false);
|
|
852
|
+
const handleChange = React2.useCallback(
|
|
853
|
+
(text) => {
|
|
854
|
+
latestText.current = text;
|
|
855
|
+
onChangeText?.(text);
|
|
856
|
+
},
|
|
857
|
+
[onChangeText]
|
|
858
|
+
);
|
|
859
|
+
const handleFocus = React2.useCallback(
|
|
860
|
+
(event) => {
|
|
861
|
+
setIsOpen(true);
|
|
862
|
+
onFocus?.(event);
|
|
863
|
+
},
|
|
864
|
+
[onFocus]
|
|
865
|
+
);
|
|
866
|
+
const handleBlur = React2.useCallback(
|
|
867
|
+
(event) => {
|
|
868
|
+
if (latestText.current.trim() !== "") remember(latestText.current);
|
|
869
|
+
setTimeout(() => setIsOpen(false), CLOSE_DELAY_MS);
|
|
870
|
+
onBlur?.(event);
|
|
871
|
+
},
|
|
872
|
+
[remember, onBlur]
|
|
873
|
+
);
|
|
874
|
+
const handleSubmit = React2.useCallback(
|
|
875
|
+
(event) => {
|
|
876
|
+
remember(latestText.current);
|
|
877
|
+
setIsOpen(false);
|
|
878
|
+
onSubmitEditing?.(event);
|
|
879
|
+
},
|
|
880
|
+
[remember, onSubmitEditing]
|
|
881
|
+
);
|
|
882
|
+
const pick = React2.useCallback(
|
|
883
|
+
(picked) => {
|
|
884
|
+
latestText.current = picked;
|
|
885
|
+
onChangeText?.(picked);
|
|
886
|
+
remember(picked);
|
|
887
|
+
setIsOpen(false);
|
|
888
|
+
},
|
|
889
|
+
[onChangeText, remember]
|
|
890
|
+
);
|
|
891
|
+
const options = recents.map((entry) => ({ label: entry, value: entry }));
|
|
892
|
+
const showMenu = isOpen && options.length > 0;
|
|
893
|
+
const recentTestID = testID !== void 0 ? `${testID}-recent` : "form-field-recent";
|
|
894
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { ref: anchorRef, style: controlStyles.anchor, children: [
|
|
895
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
896
|
+
ThemedTextInput,
|
|
897
|
+
{
|
|
898
|
+
...rest,
|
|
899
|
+
testID,
|
|
900
|
+
value,
|
|
901
|
+
onChangeText: handleChange,
|
|
902
|
+
onFocus: handleFocus,
|
|
903
|
+
onBlur: handleBlur,
|
|
904
|
+
onSubmitEditing: handleSubmit
|
|
905
|
+
}
|
|
906
|
+
),
|
|
907
|
+
showMenu ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
908
|
+
AnchoredMenu,
|
|
909
|
+
{
|
|
910
|
+
accessibilityLabel: `Recent ${label} values`,
|
|
911
|
+
anchorRef,
|
|
912
|
+
colors: { text: colors.text, border: colors.border, surface: colors.surface, brand: palette.primary["500"] },
|
|
913
|
+
onDismiss: () => setIsOpen(false),
|
|
914
|
+
onSelect: pick,
|
|
915
|
+
optionHint: `Fill ${label} with this recent value`,
|
|
916
|
+
options,
|
|
917
|
+
selectedValue: typeof value === "string" ? value : "",
|
|
918
|
+
testID: recentTestID,
|
|
919
|
+
usePortal: true
|
|
920
|
+
}
|
|
921
|
+
) : null
|
|
922
|
+
] });
|
|
923
|
+
}
|
|
615
924
|
var FormField = ({
|
|
616
925
|
label,
|
|
617
926
|
labelHidden = false,
|
|
618
927
|
required = false,
|
|
619
928
|
error,
|
|
620
929
|
containerStyle,
|
|
930
|
+
recentKey,
|
|
931
|
+
recentMax,
|
|
621
932
|
...textInputProps
|
|
622
933
|
}) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
623
934
|
Field,
|
|
@@ -626,9 +937,8 @@ var FormField = ({
|
|
|
626
937
|
error,
|
|
627
938
|
label: labelHidden ? void 0 : label,
|
|
628
939
|
required,
|
|
629
|
-
children: ({ describedById, hasError, controlId }) =>
|
|
630
|
-
|
|
631
|
-
{
|
|
940
|
+
children: ({ describedById, hasError, controlId }) => {
|
|
941
|
+
const inputProps = {
|
|
632
942
|
accessibilityHint: `Enter ${label}`,
|
|
633
943
|
accessibilityLabel: label,
|
|
634
944
|
describedById,
|
|
@@ -637,8 +947,9 @@ var FormField = ({
|
|
|
637
947
|
requiredField: required,
|
|
638
948
|
testID: "form-field-input",
|
|
639
949
|
...textInputProps
|
|
640
|
-
}
|
|
641
|
-
|
|
950
|
+
};
|
|
951
|
+
return recentKey !== void 0 && recentKey !== "" ? /* @__PURE__ */ jsxRuntime.jsx(RecentInputField, { label, recentKey, recentMax, ...inputProps }) : /* @__PURE__ */ jsxRuntime.jsx(ThemedTextInput, { ...inputProps });
|
|
952
|
+
}
|
|
642
953
|
}
|
|
643
954
|
);
|
|
644
955
|
var TRANSPARENT_COLOR = "transparent";
|
|
@@ -1009,140 +1320,6 @@ var FormCheckbox = ({
|
|
|
1009
1320
|
}
|
|
1010
1321
|
);
|
|
1011
1322
|
};
|
|
1012
|
-
var IS_WEB5 = reactNative.Platform.OS === "web";
|
|
1013
|
-
var MENU_PORTAL_Z_INDEX = 1e3;
|
|
1014
|
-
var MENU_TOP_GAP2 = 4;
|
|
1015
|
-
function toDomNode(ref) {
|
|
1016
|
-
const node = ref.current;
|
|
1017
|
-
if (node === null || typeof node.getBoundingClientRect !== "function") return null;
|
|
1018
|
-
return node;
|
|
1019
|
-
}
|
|
1020
|
-
function readAnchorRect(ref) {
|
|
1021
|
-
if (!IS_WEB5) return null;
|
|
1022
|
-
const node = toDomNode(ref);
|
|
1023
|
-
if (node === null) return null;
|
|
1024
|
-
const r = node.getBoundingClientRect();
|
|
1025
|
-
return { top: r.top, left: r.left, width: r.width, bottom: r.bottom };
|
|
1026
|
-
}
|
|
1027
|
-
function buildPortalPopoverStyle(rect) {
|
|
1028
|
-
return {
|
|
1029
|
-
// RN's ViewStyle union omits 'fixed', but react-native-web honours it at runtime.
|
|
1030
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- web-only position value
|
|
1031
|
-
position: "fixed",
|
|
1032
|
-
top: rect.bottom + MENU_TOP_GAP2,
|
|
1033
|
-
left: rect.left,
|
|
1034
|
-
width: rect.width,
|
|
1035
|
-
zIndex: MENU_PORTAL_Z_INDEX
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
|
-
function useAnchorRect(ref, enabled) {
|
|
1039
|
-
const [rect, setRect] = React2.useState(null);
|
|
1040
|
-
React2.useLayoutEffect(() => {
|
|
1041
|
-
if (!IS_WEB5 || !enabled) {
|
|
1042
|
-
setRect(null);
|
|
1043
|
-
return void 0;
|
|
1044
|
-
}
|
|
1045
|
-
let frame = null;
|
|
1046
|
-
let disposed = false;
|
|
1047
|
-
const apply = () => {
|
|
1048
|
-
frame = null;
|
|
1049
|
-
if (disposed) return;
|
|
1050
|
-
setRect(readAnchorRect(ref));
|
|
1051
|
-
};
|
|
1052
|
-
const schedule = () => {
|
|
1053
|
-
if (frame !== null) return;
|
|
1054
|
-
frame = window.requestAnimationFrame(apply);
|
|
1055
|
-
};
|
|
1056
|
-
apply();
|
|
1057
|
-
document.addEventListener("scroll", schedule, true);
|
|
1058
|
-
window.addEventListener("resize", schedule);
|
|
1059
|
-
const node = toDomNode(ref);
|
|
1060
|
-
const observer = typeof ResizeObserver === "function" ? new ResizeObserver(schedule) : null;
|
|
1061
|
-
if (observer !== null) {
|
|
1062
|
-
if (node !== null) observer.observe(node);
|
|
1063
|
-
observer.observe(document.body);
|
|
1064
|
-
}
|
|
1065
|
-
return () => {
|
|
1066
|
-
disposed = true;
|
|
1067
|
-
if (frame !== null) window.cancelAnimationFrame(frame);
|
|
1068
|
-
document.removeEventListener("scroll", schedule, true);
|
|
1069
|
-
window.removeEventListener("resize", schedule);
|
|
1070
|
-
if (observer !== null) observer.disconnect();
|
|
1071
|
-
};
|
|
1072
|
-
}, [ref, enabled]);
|
|
1073
|
-
return IS_WEB5 && enabled ? rect : null;
|
|
1074
|
-
}
|
|
1075
|
-
var IS_WEB6 = typeof document !== "undefined";
|
|
1076
|
-
var ESCAPE_KEY = "Escape";
|
|
1077
|
-
var ACTIVE_WEIGHT = "700";
|
|
1078
|
-
var IDLE_WEIGHT = "400";
|
|
1079
|
-
function useDismiss(anchorRef, menuRef, onDismiss) {
|
|
1080
|
-
React2.useEffect(() => {
|
|
1081
|
-
if (!IS_WEB6) return void 0;
|
|
1082
|
-
const onMouseDown = (event) => {
|
|
1083
|
-
const anchorNode = anchorRef.current;
|
|
1084
|
-
const menuNode = menuRef.current;
|
|
1085
|
-
const target = event.target instanceof Node ? event.target : null;
|
|
1086
|
-
const insideAnchor = anchorNode !== null && target !== null && anchorNode.contains(target);
|
|
1087
|
-
const insideMenu = menuNode !== null && target !== null && menuNode.contains(target);
|
|
1088
|
-
if (!insideAnchor && !insideMenu) onDismiss();
|
|
1089
|
-
};
|
|
1090
|
-
const onKeyDown = (event) => {
|
|
1091
|
-
if (event.key === ESCAPE_KEY) onDismiss();
|
|
1092
|
-
};
|
|
1093
|
-
document.addEventListener("mousedown", onMouseDown);
|
|
1094
|
-
document.addEventListener("keydown", onKeyDown);
|
|
1095
|
-
return () => {
|
|
1096
|
-
document.removeEventListener("mousedown", onMouseDown);
|
|
1097
|
-
document.removeEventListener("keydown", onKeyDown);
|
|
1098
|
-
};
|
|
1099
|
-
}, [anchorRef, menuRef, onDismiss]);
|
|
1100
|
-
}
|
|
1101
|
-
function AnchoredMenu({
|
|
1102
|
-
options,
|
|
1103
|
-
selectedValue,
|
|
1104
|
-
onSelect,
|
|
1105
|
-
onDismiss,
|
|
1106
|
-
colors,
|
|
1107
|
-
optionHint,
|
|
1108
|
-
testID,
|
|
1109
|
-
anchorRef,
|
|
1110
|
-
usePortal = false
|
|
1111
|
-
}) {
|
|
1112
|
-
const menuRef = React2__default.default.useRef(null);
|
|
1113
|
-
useDismiss(anchorRef, menuRef, onDismiss);
|
|
1114
|
-
const rect = useAnchorRect(anchorRef, usePortal);
|
|
1115
|
-
const portalStyle = rect !== null ? buildPortalPopoverStyle(rect) : null;
|
|
1116
|
-
const menu = /* @__PURE__ */ jsxRuntime.jsx(
|
|
1117
|
-
reactNative.View,
|
|
1118
|
-
{
|
|
1119
|
-
ref: menuRef,
|
|
1120
|
-
accessibilityRole: "menu",
|
|
1121
|
-
style: [controlStyles.menu, { borderColor: colors.border, backgroundColor: colors.surface }, portalStyle],
|
|
1122
|
-
testID: `${testID}-menu`,
|
|
1123
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ScrollView, { keyboardShouldPersistTaps: "handled", nestedScrollEnabled: true, children: options.map((opt) => {
|
|
1124
|
-
const active = opt.value === selectedValue;
|
|
1125
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1126
|
-
reactNative.Pressable,
|
|
1127
|
-
{
|
|
1128
|
-
accessibilityRole: "menuitem",
|
|
1129
|
-
accessibilityLabel: opt.label,
|
|
1130
|
-
accessibilityHint: optionHint,
|
|
1131
|
-
accessibilityState: { selected: active },
|
|
1132
|
-
"aria-selected": active,
|
|
1133
|
-
onPress: () => onSelect(opt.value),
|
|
1134
|
-
style: controlStyles.option,
|
|
1135
|
-
testID: `${testID}-option-${opt.value}`,
|
|
1136
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [controlStyles.optionText, { color: active ? colors.brand : colors.text, fontWeight: active ? ACTIVE_WEIGHT : IDLE_WEIGHT }], children: opt.label })
|
|
1137
|
-
},
|
|
1138
|
-
opt.value
|
|
1139
|
-
);
|
|
1140
|
-
}) })
|
|
1141
|
-
}
|
|
1142
|
-
);
|
|
1143
|
-
if (usePortal && IS_WEB6) return reactDom.createPortal(menu, document.body);
|
|
1144
|
-
return menu;
|
|
1145
|
-
}
|
|
1146
1323
|
var CHEVRON_DOWN = "\u25BE";
|
|
1147
1324
|
var CHEVRON_UP = "\u25B4";
|
|
1148
1325
|
function SelectControl({
|
|
@@ -1244,7 +1421,7 @@ function isUnmatched(options, text) {
|
|
|
1244
1421
|
if (needle === "") return false;
|
|
1245
1422
|
return !options.some((o) => o.value.toLowerCase() === needle || o.label.toLowerCase() === needle);
|
|
1246
1423
|
}
|
|
1247
|
-
var
|
|
1424
|
+
var CLOSE_DELAY_MS2 = 140;
|
|
1248
1425
|
var DEFAULT_TYPEAHEAD_MIN_CHARS = 1;
|
|
1249
1426
|
var DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS = 8;
|
|
1250
1427
|
function TypeaheadControl({
|
|
@@ -1288,7 +1465,7 @@ function TypeaheadControl({
|
|
|
1288
1465
|
[options, onChange]
|
|
1289
1466
|
);
|
|
1290
1467
|
const handleBlur = React2.useCallback(() => {
|
|
1291
|
-
setTimeout(() => setIsOpen(false),
|
|
1468
|
+
setTimeout(() => setIsOpen(false), CLOSE_DELAY_MS2);
|
|
1292
1469
|
}, []);
|
|
1293
1470
|
const handleSubmit = React2.useCallback(() => {
|
|
1294
1471
|
setIsOpen(false);
|
|
@@ -1407,6 +1584,7 @@ function DateRangeControl({
|
|
|
1407
1584
|
|
|
1408
1585
|
exports.AnchoredMenu = AnchoredMenu;
|
|
1409
1586
|
exports.ChipSelector = ChipSelector;
|
|
1587
|
+
exports.DEFAULT_MAX_RECENTS = DEFAULT_MAX_RECENTS;
|
|
1410
1588
|
exports.DEFAULT_OPTIONAL_LABEL = DEFAULT_OPTIONAL_LABEL;
|
|
1411
1589
|
exports.DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS = DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS;
|
|
1412
1590
|
exports.DEFAULT_TYPEAHEAD_MIN_CHARS = DEFAULT_TYPEAHEAD_MIN_CHARS;
|
|
@@ -1425,6 +1603,7 @@ exports.TypeaheadControl = TypeaheadControl;
|
|
|
1425
1603
|
exports.controlStyles = controlStyles;
|
|
1426
1604
|
exports.isUnmatched = isUnmatched;
|
|
1427
1605
|
exports.suggestOptions = suggestOptions;
|
|
1606
|
+
exports.useRecentValues = useRecentValues;
|
|
1428
1607
|
exports.useThemedInput = useThemedInput;
|
|
1429
1608
|
//# sourceMappingURL=index.js.map
|
|
1430
1609
|
//# sourceMappingURL=index.js.map
|