@dloizides/ui-forms 1.13.4 → 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 +54 -3
- package/dist/index.d.ts +54 -3
- 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.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import React2, { useState, useCallback, useMemo,
|
|
2
|
-
import { Platform, StyleSheet, TextInput, View, Text,
|
|
1
|
+
import React2, { useState, useCallback, useMemo, useEffect, useRef, useLayoutEffect } from 'react';
|
|
2
|
+
import { Platform, StyleSheet, TextInput, View, Text, ScrollView, Pressable, Switch, TouchableOpacity } from 'react-native';
|
|
3
3
|
import { useUi } from '@dloizides/ui-feedback';
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
import { Button } from '@dloizides/ui-buttons';
|
|
6
|
-
import { MIN_TOUCH_TARGET_PX } from '@dloizides/a11y';
|
|
7
6
|
import { createPortal } from 'react-dom';
|
|
7
|
+
import { MIN_TOUCH_TARGET_PX } from '@dloizides/a11y';
|
|
8
8
|
|
|
9
9
|
// src/Field/Field.tsx
|
|
10
10
|
function attachControlId(child, candidateId, describedById) {
|
|
@@ -459,6 +459,142 @@ var controlStyles = StyleSheet.create({
|
|
|
459
459
|
error: { fontSize: ERROR_FONT, marginTop: ERROR_GAP }
|
|
460
460
|
});
|
|
461
461
|
var IS_WEB3 = Platform.OS === "web";
|
|
462
|
+
var MENU_PORTAL_Z_INDEX = 1e3;
|
|
463
|
+
var MENU_TOP_GAP2 = 4;
|
|
464
|
+
function toDomNode(ref) {
|
|
465
|
+
const node = ref.current;
|
|
466
|
+
if (node === null || typeof node.getBoundingClientRect !== "function") return null;
|
|
467
|
+
return node;
|
|
468
|
+
}
|
|
469
|
+
function readAnchorRect(ref) {
|
|
470
|
+
if (!IS_WEB3) return null;
|
|
471
|
+
const node = toDomNode(ref);
|
|
472
|
+
if (node === null) return null;
|
|
473
|
+
const r = node.getBoundingClientRect();
|
|
474
|
+
return { top: r.top, left: r.left, width: r.width, bottom: r.bottom };
|
|
475
|
+
}
|
|
476
|
+
function buildPortalPopoverStyle(rect) {
|
|
477
|
+
return {
|
|
478
|
+
// RN's ViewStyle union omits 'fixed', but react-native-web honours it at runtime.
|
|
479
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- web-only position value
|
|
480
|
+
position: "fixed",
|
|
481
|
+
top: rect.bottom + MENU_TOP_GAP2,
|
|
482
|
+
left: rect.left,
|
|
483
|
+
width: rect.width,
|
|
484
|
+
zIndex: MENU_PORTAL_Z_INDEX
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function useAnchorRect(ref, enabled) {
|
|
488
|
+
const [rect, setRect] = useState(null);
|
|
489
|
+
useLayoutEffect(() => {
|
|
490
|
+
if (!IS_WEB3 || !enabled) {
|
|
491
|
+
setRect(null);
|
|
492
|
+
return void 0;
|
|
493
|
+
}
|
|
494
|
+
let frame = null;
|
|
495
|
+
let disposed = false;
|
|
496
|
+
const apply = () => {
|
|
497
|
+
frame = null;
|
|
498
|
+
if (disposed) return;
|
|
499
|
+
setRect(readAnchorRect(ref));
|
|
500
|
+
};
|
|
501
|
+
const schedule = () => {
|
|
502
|
+
if (frame !== null) return;
|
|
503
|
+
frame = window.requestAnimationFrame(apply);
|
|
504
|
+
};
|
|
505
|
+
apply();
|
|
506
|
+
document.addEventListener("scroll", schedule, true);
|
|
507
|
+
window.addEventListener("resize", schedule);
|
|
508
|
+
const node = toDomNode(ref);
|
|
509
|
+
const observer = typeof ResizeObserver === "function" ? new ResizeObserver(schedule) : null;
|
|
510
|
+
if (observer !== null) {
|
|
511
|
+
if (node !== null) observer.observe(node);
|
|
512
|
+
observer.observe(document.body);
|
|
513
|
+
}
|
|
514
|
+
return () => {
|
|
515
|
+
disposed = true;
|
|
516
|
+
if (frame !== null) window.cancelAnimationFrame(frame);
|
|
517
|
+
document.removeEventListener("scroll", schedule, true);
|
|
518
|
+
window.removeEventListener("resize", schedule);
|
|
519
|
+
if (observer !== null) observer.disconnect();
|
|
520
|
+
};
|
|
521
|
+
}, [ref, enabled]);
|
|
522
|
+
return IS_WEB3 && enabled ? rect : null;
|
|
523
|
+
}
|
|
524
|
+
var IS_WEB4 = typeof document !== "undefined";
|
|
525
|
+
var ESCAPE_KEY = "Escape";
|
|
526
|
+
var ACTIVE_WEIGHT = "700";
|
|
527
|
+
var IDLE_WEIGHT = "400";
|
|
528
|
+
function useDismiss(anchorRef, menuRef, onDismiss) {
|
|
529
|
+
useEffect(() => {
|
|
530
|
+
if (!IS_WEB4) return void 0;
|
|
531
|
+
const onMouseDown = (event) => {
|
|
532
|
+
const anchorNode = anchorRef.current;
|
|
533
|
+
const menuNode = menuRef.current;
|
|
534
|
+
const target = event.target instanceof Node ? event.target : null;
|
|
535
|
+
const insideAnchor = anchorNode !== null && target !== null && anchorNode.contains(target);
|
|
536
|
+
const insideMenu = menuNode !== null && target !== null && menuNode.contains(target);
|
|
537
|
+
if (!insideAnchor && !insideMenu) onDismiss();
|
|
538
|
+
};
|
|
539
|
+
const onKeyDown = (event) => {
|
|
540
|
+
if (event.key === ESCAPE_KEY) onDismiss();
|
|
541
|
+
};
|
|
542
|
+
document.addEventListener("mousedown", onMouseDown);
|
|
543
|
+
document.addEventListener("keydown", onKeyDown);
|
|
544
|
+
return () => {
|
|
545
|
+
document.removeEventListener("mousedown", onMouseDown);
|
|
546
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
547
|
+
};
|
|
548
|
+
}, [anchorRef, menuRef, onDismiss]);
|
|
549
|
+
}
|
|
550
|
+
function AnchoredMenu({
|
|
551
|
+
options,
|
|
552
|
+
selectedValue,
|
|
553
|
+
onSelect,
|
|
554
|
+
onDismiss,
|
|
555
|
+
colors,
|
|
556
|
+
optionHint,
|
|
557
|
+
accessibilityLabel,
|
|
558
|
+
testID,
|
|
559
|
+
anchorRef,
|
|
560
|
+
usePortal = true
|
|
561
|
+
}) {
|
|
562
|
+
const menuRef = React2.useRef(null);
|
|
563
|
+
useDismiss(anchorRef, menuRef, onDismiss);
|
|
564
|
+
const rect = useAnchorRect(anchorRef, usePortal);
|
|
565
|
+
const portalStyle = rect !== null ? buildPortalPopoverStyle(rect) : null;
|
|
566
|
+
const menu = /* @__PURE__ */ jsx(
|
|
567
|
+
View,
|
|
568
|
+
{
|
|
569
|
+
ref: menuRef,
|
|
570
|
+
accessibilityLabel,
|
|
571
|
+
accessibilityRole: "menu",
|
|
572
|
+
style: [controlStyles.menu, { borderColor: colors.border, backgroundColor: colors.surface }, portalStyle],
|
|
573
|
+
testID: `${testID}-menu`,
|
|
574
|
+
children: /* @__PURE__ */ jsx(ScrollView, { keyboardShouldPersistTaps: "handled", nestedScrollEnabled: true, children: options.map((opt) => {
|
|
575
|
+
const active = opt.value === selectedValue;
|
|
576
|
+
return /* @__PURE__ */ jsx(
|
|
577
|
+
Pressable,
|
|
578
|
+
{
|
|
579
|
+
accessibilityRole: "menuitem",
|
|
580
|
+
accessibilityLabel: opt.label,
|
|
581
|
+
accessibilityHint: optionHint,
|
|
582
|
+
accessibilityState: { selected: active },
|
|
583
|
+
"aria-selected": active,
|
|
584
|
+
onPress: () => onSelect(opt.value),
|
|
585
|
+
style: controlStyles.option,
|
|
586
|
+
testID: `${testID}-option-${opt.value}`,
|
|
587
|
+
children: /* @__PURE__ */ jsx(Text, { style: [controlStyles.optionText, { color: active ? colors.brand : colors.text, fontWeight: active ? ACTIVE_WEIGHT : IDLE_WEIGHT }], children: opt.label })
|
|
588
|
+
},
|
|
589
|
+
opt.value
|
|
590
|
+
);
|
|
591
|
+
}) })
|
|
592
|
+
}
|
|
593
|
+
);
|
|
594
|
+
if (usePortal && IS_WEB4) return createPortal(menu, document.body);
|
|
595
|
+
return menu;
|
|
596
|
+
}
|
|
597
|
+
var IS_WEB5 = Platform.OS === "web";
|
|
462
598
|
var TRANSITION_PROPERTY = "border-color, background-color, box-shadow";
|
|
463
599
|
var TRANSITION_DURATION = "0.14s";
|
|
464
600
|
var TRANSITION_TIMING = "ease";
|
|
@@ -474,7 +610,7 @@ var GREEN_START = 3;
|
|
|
474
610
|
var BLUE_START = 5;
|
|
475
611
|
var CHANNEL_END_OFFSET = 2;
|
|
476
612
|
function prefersReducedMotion() {
|
|
477
|
-
if (!
|
|
613
|
+
if (!IS_WEB5 || typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
478
614
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
479
615
|
}
|
|
480
616
|
function withAlpha(color, alpha) {
|
|
@@ -517,7 +653,7 @@ function useThemedInput(options = {}) {
|
|
|
517
653
|
const borderColor = isFocused ? focusBorder : isHovered ? withAlpha(colors.text, HOVER_BORDER_ALPHA) : restBorder;
|
|
518
654
|
const backgroundColor = isFocused ? colors.surface : colors.surfaceElevated;
|
|
519
655
|
const base = { color: colors.text, borderColor, backgroundColor };
|
|
520
|
-
if (!
|
|
656
|
+
if (!IS_WEB5) return base;
|
|
521
657
|
const ringColor = hasError ? errorColor : primary;
|
|
522
658
|
const ring = isFocused ? `0 0 0 ${FOCUS_RING_WIDTH}px ${withAlpha(ringColor, FOCUS_RING_ALPHA)}` : "none";
|
|
523
659
|
const webStyle = {
|
|
@@ -542,7 +678,7 @@ function useThemedInput(options = {}) {
|
|
|
542
678
|
]);
|
|
543
679
|
const focusBind = useMemo(() => ({ onFocus: handleFocus, onBlur: handleBlur }), [handleFocus, handleBlur]);
|
|
544
680
|
const hoverBind = useMemo(
|
|
545
|
-
() =>
|
|
681
|
+
() => IS_WEB5 ? { onMouseEnter: handleHoverIn, onMouseLeave: handleHoverOut } : {},
|
|
546
682
|
[handleHoverIn, handleHoverOut]
|
|
547
683
|
);
|
|
548
684
|
return {
|
|
@@ -554,9 +690,9 @@ function useThemedInput(options = {}) {
|
|
|
554
690
|
hoverBind
|
|
555
691
|
};
|
|
556
692
|
}
|
|
557
|
-
var
|
|
693
|
+
var IS_WEB6 = Platform.OS === "web";
|
|
558
694
|
function webFieldA11y({ describedById, hasError = false, required = false }) {
|
|
559
|
-
if (!
|
|
695
|
+
if (!IS_WEB6) {
|
|
560
696
|
return {};
|
|
561
697
|
}
|
|
562
698
|
return {
|
|
@@ -606,12 +742,187 @@ var ThemedTextInput = React2.forwardRef(function ThemedTextInput2({
|
|
|
606
742
|
}
|
|
607
743
|
);
|
|
608
744
|
});
|
|
745
|
+
|
|
746
|
+
// src/useRecentValues/recentValuesCore.ts
|
|
747
|
+
var STORAGE_PREFIX = "@dloizides/ui-forms:recent:";
|
|
748
|
+
var DEFAULT_MAX_RECENTS = 10;
|
|
749
|
+
function recentStorageKey(key) {
|
|
750
|
+
return `${STORAGE_PREFIX}${key}`;
|
|
751
|
+
}
|
|
752
|
+
function getStorage() {
|
|
753
|
+
try {
|
|
754
|
+
if (typeof window === "undefined") return null;
|
|
755
|
+
const storage = window.localStorage;
|
|
756
|
+
return storage !== null && storage !== void 0 ? storage : null;
|
|
757
|
+
} catch {
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function readRecents(key) {
|
|
762
|
+
const storage = getStorage();
|
|
763
|
+
if (storage === null) return [];
|
|
764
|
+
try {
|
|
765
|
+
const raw = storage.getItem(recentStorageKey(key));
|
|
766
|
+
if (raw === null) return [];
|
|
767
|
+
const parsed = JSON.parse(raw);
|
|
768
|
+
if (!Array.isArray(parsed)) return [];
|
|
769
|
+
return parsed.filter((entry) => typeof entry === "string");
|
|
770
|
+
} catch {
|
|
771
|
+
return [];
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function writeRecents(key, values) {
|
|
775
|
+
const storage = getStorage();
|
|
776
|
+
if (storage === null) return;
|
|
777
|
+
try {
|
|
778
|
+
storage.setItem(recentStorageKey(key), JSON.stringify(values));
|
|
779
|
+
} catch {
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function clearRecents(key) {
|
|
783
|
+
const storage = getStorage();
|
|
784
|
+
if (storage === null) return;
|
|
785
|
+
try {
|
|
786
|
+
storage.removeItem(recentStorageKey(key));
|
|
787
|
+
} catch {
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function computeNextRecents(current, value, max) {
|
|
791
|
+
const trimmed = value.trim();
|
|
792
|
+
if (trimmed === "") return [...current];
|
|
793
|
+
const lower = trimmed.toLowerCase();
|
|
794
|
+
const withoutMatch = current.filter((entry) => entry.toLowerCase() !== lower);
|
|
795
|
+
const promoted = [trimmed, ...withoutMatch];
|
|
796
|
+
const cap = max > 0 ? max : 0;
|
|
797
|
+
return promoted.slice(0, cap);
|
|
798
|
+
}
|
|
799
|
+
function recentsEqual(a, b) {
|
|
800
|
+
if (a.length !== b.length) return false;
|
|
801
|
+
return a.every((entry, index) => entry === b[index]);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/useRecentValues/useRecentValues.ts
|
|
805
|
+
function useRecentValues(key, max = DEFAULT_MAX_RECENTS) {
|
|
806
|
+
const [recents, setRecents] = useState(() => readRecents(key).slice(0, max));
|
|
807
|
+
useEffect(() => {
|
|
808
|
+
setRecents(readRecents(key).slice(0, max));
|
|
809
|
+
}, [key, max]);
|
|
810
|
+
const remember = useCallback(
|
|
811
|
+
(value) => {
|
|
812
|
+
setRecents((current) => {
|
|
813
|
+
const next = computeNextRecents(current, value, max);
|
|
814
|
+
if (recentsEqual(next, current)) return current;
|
|
815
|
+
writeRecents(key, next);
|
|
816
|
+
return next;
|
|
817
|
+
});
|
|
818
|
+
},
|
|
819
|
+
[key, max]
|
|
820
|
+
);
|
|
821
|
+
const clear = useCallback(() => {
|
|
822
|
+
clearRecents(key);
|
|
823
|
+
setRecents([]);
|
|
824
|
+
}, [key]);
|
|
825
|
+
return { recents, remember, clear };
|
|
826
|
+
}
|
|
827
|
+
var CLOSE_DELAY_MS = 140;
|
|
828
|
+
function RecentInputField({
|
|
829
|
+
recentKey,
|
|
830
|
+
recentMax,
|
|
831
|
+
label,
|
|
832
|
+
value,
|
|
833
|
+
testID,
|
|
834
|
+
onChangeText,
|
|
835
|
+
onSubmitEditing,
|
|
836
|
+
onFocus,
|
|
837
|
+
onBlur,
|
|
838
|
+
...rest
|
|
839
|
+
}) {
|
|
840
|
+
const { theme } = useUi();
|
|
841
|
+
const { colors, palette } = theme;
|
|
842
|
+
const { recents, remember } = useRecentValues(recentKey, recentMax);
|
|
843
|
+
const anchorRef = useRef(null);
|
|
844
|
+
const latestText = useRef(typeof value === "string" ? value : "");
|
|
845
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
846
|
+
const handleChange = useCallback(
|
|
847
|
+
(text) => {
|
|
848
|
+
latestText.current = text;
|
|
849
|
+
onChangeText?.(text);
|
|
850
|
+
},
|
|
851
|
+
[onChangeText]
|
|
852
|
+
);
|
|
853
|
+
const handleFocus = useCallback(
|
|
854
|
+
(event) => {
|
|
855
|
+
setIsOpen(true);
|
|
856
|
+
onFocus?.(event);
|
|
857
|
+
},
|
|
858
|
+
[onFocus]
|
|
859
|
+
);
|
|
860
|
+
const handleBlur = useCallback(
|
|
861
|
+
(event) => {
|
|
862
|
+
if (latestText.current.trim() !== "") remember(latestText.current);
|
|
863
|
+
setTimeout(() => setIsOpen(false), CLOSE_DELAY_MS);
|
|
864
|
+
onBlur?.(event);
|
|
865
|
+
},
|
|
866
|
+
[remember, onBlur]
|
|
867
|
+
);
|
|
868
|
+
const handleSubmit = useCallback(
|
|
869
|
+
(event) => {
|
|
870
|
+
remember(latestText.current);
|
|
871
|
+
setIsOpen(false);
|
|
872
|
+
onSubmitEditing?.(event);
|
|
873
|
+
},
|
|
874
|
+
[remember, onSubmitEditing]
|
|
875
|
+
);
|
|
876
|
+
const pick = useCallback(
|
|
877
|
+
(picked) => {
|
|
878
|
+
latestText.current = picked;
|
|
879
|
+
onChangeText?.(picked);
|
|
880
|
+
remember(picked);
|
|
881
|
+
setIsOpen(false);
|
|
882
|
+
},
|
|
883
|
+
[onChangeText, remember]
|
|
884
|
+
);
|
|
885
|
+
const options = recents.map((entry) => ({ label: entry, value: entry }));
|
|
886
|
+
const showMenu = isOpen && options.length > 0;
|
|
887
|
+
const recentTestID = testID !== void 0 ? `${testID}-recent` : "form-field-recent";
|
|
888
|
+
return /* @__PURE__ */ jsxs(View, { ref: anchorRef, style: controlStyles.anchor, children: [
|
|
889
|
+
/* @__PURE__ */ jsx(
|
|
890
|
+
ThemedTextInput,
|
|
891
|
+
{
|
|
892
|
+
...rest,
|
|
893
|
+
testID,
|
|
894
|
+
value,
|
|
895
|
+
onChangeText: handleChange,
|
|
896
|
+
onFocus: handleFocus,
|
|
897
|
+
onBlur: handleBlur,
|
|
898
|
+
onSubmitEditing: handleSubmit
|
|
899
|
+
}
|
|
900
|
+
),
|
|
901
|
+
showMenu ? /* @__PURE__ */ jsx(
|
|
902
|
+
AnchoredMenu,
|
|
903
|
+
{
|
|
904
|
+
accessibilityLabel: `Recent ${label} values`,
|
|
905
|
+
anchorRef,
|
|
906
|
+
colors: { text: colors.text, border: colors.border, surface: colors.surface, brand: palette.primary["500"] },
|
|
907
|
+
onDismiss: () => setIsOpen(false),
|
|
908
|
+
onSelect: pick,
|
|
909
|
+
optionHint: `Fill ${label} with this recent value`,
|
|
910
|
+
options,
|
|
911
|
+
selectedValue: typeof value === "string" ? value : "",
|
|
912
|
+
testID: recentTestID,
|
|
913
|
+
usePortal: true
|
|
914
|
+
}
|
|
915
|
+
) : null
|
|
916
|
+
] });
|
|
917
|
+
}
|
|
609
918
|
var FormField = ({
|
|
610
919
|
label,
|
|
611
920
|
labelHidden = false,
|
|
612
921
|
required = false,
|
|
613
922
|
error,
|
|
614
923
|
containerStyle,
|
|
924
|
+
recentKey,
|
|
925
|
+
recentMax,
|
|
615
926
|
...textInputProps
|
|
616
927
|
}) => /* @__PURE__ */ jsx(
|
|
617
928
|
Field,
|
|
@@ -620,9 +931,8 @@ var FormField = ({
|
|
|
620
931
|
error,
|
|
621
932
|
label: labelHidden ? void 0 : label,
|
|
622
933
|
required,
|
|
623
|
-
children: ({ describedById, hasError, controlId }) =>
|
|
624
|
-
|
|
625
|
-
{
|
|
934
|
+
children: ({ describedById, hasError, controlId }) => {
|
|
935
|
+
const inputProps = {
|
|
626
936
|
accessibilityHint: `Enter ${label}`,
|
|
627
937
|
accessibilityLabel: label,
|
|
628
938
|
describedById,
|
|
@@ -631,8 +941,9 @@ var FormField = ({
|
|
|
631
941
|
requiredField: required,
|
|
632
942
|
testID: "form-field-input",
|
|
633
943
|
...textInputProps
|
|
634
|
-
}
|
|
635
|
-
|
|
944
|
+
};
|
|
945
|
+
return recentKey !== void 0 && recentKey !== "" ? /* @__PURE__ */ jsx(RecentInputField, { label, recentKey, recentMax, ...inputProps }) : /* @__PURE__ */ jsx(ThemedTextInput, { ...inputProps });
|
|
946
|
+
}
|
|
636
947
|
}
|
|
637
948
|
);
|
|
638
949
|
var TRANSPARENT_COLOR = "transparent";
|
|
@@ -1003,140 +1314,6 @@ var FormCheckbox = ({
|
|
|
1003
1314
|
}
|
|
1004
1315
|
);
|
|
1005
1316
|
};
|
|
1006
|
-
var IS_WEB5 = Platform.OS === "web";
|
|
1007
|
-
var MENU_PORTAL_Z_INDEX = 1e3;
|
|
1008
|
-
var MENU_TOP_GAP2 = 4;
|
|
1009
|
-
function toDomNode(ref) {
|
|
1010
|
-
const node = ref.current;
|
|
1011
|
-
if (node === null || typeof node.getBoundingClientRect !== "function") return null;
|
|
1012
|
-
return node;
|
|
1013
|
-
}
|
|
1014
|
-
function readAnchorRect(ref) {
|
|
1015
|
-
if (!IS_WEB5) return null;
|
|
1016
|
-
const node = toDomNode(ref);
|
|
1017
|
-
if (node === null) return null;
|
|
1018
|
-
const r = node.getBoundingClientRect();
|
|
1019
|
-
return { top: r.top, left: r.left, width: r.width, bottom: r.bottom };
|
|
1020
|
-
}
|
|
1021
|
-
function buildPortalPopoverStyle(rect) {
|
|
1022
|
-
return {
|
|
1023
|
-
// RN's ViewStyle union omits 'fixed', but react-native-web honours it at runtime.
|
|
1024
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- web-only position value
|
|
1025
|
-
position: "fixed",
|
|
1026
|
-
top: rect.bottom + MENU_TOP_GAP2,
|
|
1027
|
-
left: rect.left,
|
|
1028
|
-
width: rect.width,
|
|
1029
|
-
zIndex: MENU_PORTAL_Z_INDEX
|
|
1030
|
-
};
|
|
1031
|
-
}
|
|
1032
|
-
function useAnchorRect(ref, enabled) {
|
|
1033
|
-
const [rect, setRect] = useState(null);
|
|
1034
|
-
useLayoutEffect(() => {
|
|
1035
|
-
if (!IS_WEB5 || !enabled) {
|
|
1036
|
-
setRect(null);
|
|
1037
|
-
return void 0;
|
|
1038
|
-
}
|
|
1039
|
-
let frame = null;
|
|
1040
|
-
let disposed = false;
|
|
1041
|
-
const apply = () => {
|
|
1042
|
-
frame = null;
|
|
1043
|
-
if (disposed) return;
|
|
1044
|
-
setRect(readAnchorRect(ref));
|
|
1045
|
-
};
|
|
1046
|
-
const schedule = () => {
|
|
1047
|
-
if (frame !== null) return;
|
|
1048
|
-
frame = window.requestAnimationFrame(apply);
|
|
1049
|
-
};
|
|
1050
|
-
apply();
|
|
1051
|
-
document.addEventListener("scroll", schedule, true);
|
|
1052
|
-
window.addEventListener("resize", schedule);
|
|
1053
|
-
const node = toDomNode(ref);
|
|
1054
|
-
const observer = typeof ResizeObserver === "function" ? new ResizeObserver(schedule) : null;
|
|
1055
|
-
if (observer !== null) {
|
|
1056
|
-
if (node !== null) observer.observe(node);
|
|
1057
|
-
observer.observe(document.body);
|
|
1058
|
-
}
|
|
1059
|
-
return () => {
|
|
1060
|
-
disposed = true;
|
|
1061
|
-
if (frame !== null) window.cancelAnimationFrame(frame);
|
|
1062
|
-
document.removeEventListener("scroll", schedule, true);
|
|
1063
|
-
window.removeEventListener("resize", schedule);
|
|
1064
|
-
if (observer !== null) observer.disconnect();
|
|
1065
|
-
};
|
|
1066
|
-
}, [ref, enabled]);
|
|
1067
|
-
return IS_WEB5 && enabled ? rect : null;
|
|
1068
|
-
}
|
|
1069
|
-
var IS_WEB6 = typeof document !== "undefined";
|
|
1070
|
-
var ESCAPE_KEY = "Escape";
|
|
1071
|
-
var ACTIVE_WEIGHT = "700";
|
|
1072
|
-
var IDLE_WEIGHT = "400";
|
|
1073
|
-
function useDismiss(anchorRef, menuRef, onDismiss) {
|
|
1074
|
-
useEffect(() => {
|
|
1075
|
-
if (!IS_WEB6) return void 0;
|
|
1076
|
-
const onMouseDown = (event) => {
|
|
1077
|
-
const anchorNode = anchorRef.current;
|
|
1078
|
-
const menuNode = menuRef.current;
|
|
1079
|
-
const target = event.target instanceof Node ? event.target : null;
|
|
1080
|
-
const insideAnchor = anchorNode !== null && target !== null && anchorNode.contains(target);
|
|
1081
|
-
const insideMenu = menuNode !== null && target !== null && menuNode.contains(target);
|
|
1082
|
-
if (!insideAnchor && !insideMenu) onDismiss();
|
|
1083
|
-
};
|
|
1084
|
-
const onKeyDown = (event) => {
|
|
1085
|
-
if (event.key === ESCAPE_KEY) onDismiss();
|
|
1086
|
-
};
|
|
1087
|
-
document.addEventListener("mousedown", onMouseDown);
|
|
1088
|
-
document.addEventListener("keydown", onKeyDown);
|
|
1089
|
-
return () => {
|
|
1090
|
-
document.removeEventListener("mousedown", onMouseDown);
|
|
1091
|
-
document.removeEventListener("keydown", onKeyDown);
|
|
1092
|
-
};
|
|
1093
|
-
}, [anchorRef, menuRef, onDismiss]);
|
|
1094
|
-
}
|
|
1095
|
-
function AnchoredMenu({
|
|
1096
|
-
options,
|
|
1097
|
-
selectedValue,
|
|
1098
|
-
onSelect,
|
|
1099
|
-
onDismiss,
|
|
1100
|
-
colors,
|
|
1101
|
-
optionHint,
|
|
1102
|
-
testID,
|
|
1103
|
-
anchorRef,
|
|
1104
|
-
usePortal = true
|
|
1105
|
-
}) {
|
|
1106
|
-
const menuRef = React2.useRef(null);
|
|
1107
|
-
useDismiss(anchorRef, menuRef, onDismiss);
|
|
1108
|
-
const rect = useAnchorRect(anchorRef, usePortal);
|
|
1109
|
-
const portalStyle = rect !== null ? buildPortalPopoverStyle(rect) : null;
|
|
1110
|
-
const menu = /* @__PURE__ */ jsx(
|
|
1111
|
-
View,
|
|
1112
|
-
{
|
|
1113
|
-
ref: menuRef,
|
|
1114
|
-
accessibilityRole: "menu",
|
|
1115
|
-
style: [controlStyles.menu, { borderColor: colors.border, backgroundColor: colors.surface }, portalStyle],
|
|
1116
|
-
testID: `${testID}-menu`,
|
|
1117
|
-
children: /* @__PURE__ */ jsx(ScrollView, { keyboardShouldPersistTaps: "handled", nestedScrollEnabled: true, children: options.map((opt) => {
|
|
1118
|
-
const active = opt.value === selectedValue;
|
|
1119
|
-
return /* @__PURE__ */ jsx(
|
|
1120
|
-
Pressable,
|
|
1121
|
-
{
|
|
1122
|
-
accessibilityRole: "menuitem",
|
|
1123
|
-
accessibilityLabel: opt.label,
|
|
1124
|
-
accessibilityHint: optionHint,
|
|
1125
|
-
accessibilityState: { selected: active },
|
|
1126
|
-
"aria-selected": active,
|
|
1127
|
-
onPress: () => onSelect(opt.value),
|
|
1128
|
-
style: controlStyles.option,
|
|
1129
|
-
testID: `${testID}-option-${opt.value}`,
|
|
1130
|
-
children: /* @__PURE__ */ jsx(Text, { style: [controlStyles.optionText, { color: active ? colors.brand : colors.text, fontWeight: active ? ACTIVE_WEIGHT : IDLE_WEIGHT }], children: opt.label })
|
|
1131
|
-
},
|
|
1132
|
-
opt.value
|
|
1133
|
-
);
|
|
1134
|
-
}) })
|
|
1135
|
-
}
|
|
1136
|
-
);
|
|
1137
|
-
if (usePortal && IS_WEB6) return createPortal(menu, document.body);
|
|
1138
|
-
return menu;
|
|
1139
|
-
}
|
|
1140
1317
|
var CHEVRON_DOWN = "\u25BE";
|
|
1141
1318
|
var CHEVRON_UP = "\u25B4";
|
|
1142
1319
|
function SelectControl({
|
|
@@ -1238,7 +1415,7 @@ function isUnmatched(options, text) {
|
|
|
1238
1415
|
if (needle === "") return false;
|
|
1239
1416
|
return !options.some((o) => o.value.toLowerCase() === needle || o.label.toLowerCase() === needle);
|
|
1240
1417
|
}
|
|
1241
|
-
var
|
|
1418
|
+
var CLOSE_DELAY_MS2 = 140;
|
|
1242
1419
|
var DEFAULT_TYPEAHEAD_MIN_CHARS = 1;
|
|
1243
1420
|
var DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS = 8;
|
|
1244
1421
|
function TypeaheadControl({
|
|
@@ -1282,7 +1459,7 @@ function TypeaheadControl({
|
|
|
1282
1459
|
[options, onChange]
|
|
1283
1460
|
);
|
|
1284
1461
|
const handleBlur = useCallback(() => {
|
|
1285
|
-
setTimeout(() => setIsOpen(false),
|
|
1462
|
+
setTimeout(() => setIsOpen(false), CLOSE_DELAY_MS2);
|
|
1286
1463
|
}, []);
|
|
1287
1464
|
const handleSubmit = useCallback(() => {
|
|
1288
1465
|
setIsOpen(false);
|
|
@@ -1399,6 +1576,6 @@ function DateRangeControl({
|
|
|
1399
1576
|
] });
|
|
1400
1577
|
}
|
|
1401
1578
|
|
|
1402
|
-
export { AnchoredMenu, ChipSelector, DEFAULT_OPTIONAL_LABEL, DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS, DEFAULT_TYPEAHEAD_MIN_CHARS, DateRangeControl, Field, FormActions, FormCell, FormCheckbox, FormField, FormGrid, FormSection, FormSwitch, SelectControl, ThemedTextInput, TypeaheadControl, controlStyles, isUnmatched, suggestOptions, useThemedInput };
|
|
1579
|
+
export { AnchoredMenu, ChipSelector, DEFAULT_MAX_RECENTS, DEFAULT_OPTIONAL_LABEL, DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS, DEFAULT_TYPEAHEAD_MIN_CHARS, DateRangeControl, Field, FormActions, FormCell, FormCheckbox, FormField, FormGrid, FormSection, FormSwitch, SelectControl, ThemedTextInput, TypeaheadControl, controlStyles, isUnmatched, suggestOptions, useRecentValues, useThemedInput };
|
|
1403
1580
|
//# sourceMappingURL=index.mjs.map
|
|
1404
1581
|
//# sourceMappingURL=index.mjs.map
|