@dloizides/ui-forms 1.11.1 → 1.13.1

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/dist/index.mjs CHANGED
@@ -1,9 +1,10 @@
1
- import React2, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
1
+ import React2, { useState, useCallback, useMemo, useRef, useEffect, useLayoutEffect } from 'react';
2
2
  import { Platform, StyleSheet, TextInput, View, Text, Switch, Pressable, 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
6
  import { MIN_TOUCH_TARGET_PX } from '@dloizides/a11y';
7
+ import { createPortal } from 'react-dom';
7
8
 
8
9
  // src/Field/Field.tsx
9
10
  function attachControlId(child, candidateId, describedById) {
@@ -606,23 +607,33 @@ var ThemedTextInput = React2.forwardRef(function ThemedTextInput2({
606
607
  });
607
608
  var FormField = ({
608
609
  label,
610
+ labelHidden = false,
609
611
  required = false,
610
612
  error,
611
613
  containerStyle,
612
614
  ...textInputProps
613
- }) => /* @__PURE__ */ jsx(Field, { containerStyle, error, label, required, children: ({ describedById, hasError, controlId }) => /* @__PURE__ */ jsx(
614
- ThemedTextInput,
615
+ }) => /* @__PURE__ */ jsx(
616
+ Field,
615
617
  {
616
- accessibilityHint: `Enter ${label}`,
617
- accessibilityLabel: label,
618
- describedById,
619
- hasError,
620
- id: controlId,
621
- requiredField: required,
622
- testID: "form-field-input",
623
- ...textInputProps
618
+ containerStyle,
619
+ error,
620
+ label: labelHidden ? void 0 : label,
621
+ required,
622
+ children: ({ describedById, hasError, controlId }) => /* @__PURE__ */ jsx(
623
+ ThemedTextInput,
624
+ {
625
+ accessibilityHint: `Enter ${label}`,
626
+ accessibilityLabel: label,
627
+ describedById,
628
+ hasError,
629
+ id: controlId,
630
+ requiredField: required,
631
+ testID: "form-field-input",
632
+ ...textInputProps
633
+ }
634
+ )
624
635
  }
625
- ) });
636
+ );
626
637
  var TRANSPARENT_COLOR = "transparent";
627
638
  var WHITE_COLOR = "#fff";
628
639
  var SOLID_RADIUS = 20;
@@ -991,13 +1002,76 @@ var FormCheckbox = ({
991
1002
  }
992
1003
  );
993
1004
  };
994
- var IS_WEB5 = typeof document !== "undefined";
1005
+ var IS_WEB5 = Platform.OS === "web";
1006
+ var MENU_PORTAL_Z_INDEX = 1e3;
1007
+ var MENU_TOP_GAP2 = 4;
1008
+ function toDomNode(ref) {
1009
+ const node = ref.current;
1010
+ if (node === null || typeof node.getBoundingClientRect !== "function") return null;
1011
+ return node;
1012
+ }
1013
+ function readAnchorRect(ref) {
1014
+ if (!IS_WEB5) return null;
1015
+ const node = toDomNode(ref);
1016
+ if (node === null) return null;
1017
+ const r = node.getBoundingClientRect();
1018
+ return { top: r.top, left: r.left, width: r.width, bottom: r.bottom };
1019
+ }
1020
+ function buildPortalPopoverStyle(rect) {
1021
+ return {
1022
+ // RN's ViewStyle union omits 'fixed', but react-native-web honours it at runtime.
1023
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- web-only position value
1024
+ position: "fixed",
1025
+ top: rect.bottom + MENU_TOP_GAP2,
1026
+ left: rect.left,
1027
+ width: rect.width,
1028
+ zIndex: MENU_PORTAL_Z_INDEX
1029
+ };
1030
+ }
1031
+ function useAnchorRect(ref, enabled) {
1032
+ const [rect, setRect] = useState(null);
1033
+ useLayoutEffect(() => {
1034
+ if (!IS_WEB5 || !enabled) {
1035
+ setRect(null);
1036
+ return void 0;
1037
+ }
1038
+ let frame = null;
1039
+ let disposed = false;
1040
+ const apply = () => {
1041
+ frame = null;
1042
+ if (disposed) return;
1043
+ setRect(readAnchorRect(ref));
1044
+ };
1045
+ const schedule = () => {
1046
+ if (frame !== null) return;
1047
+ frame = window.requestAnimationFrame(apply);
1048
+ };
1049
+ apply();
1050
+ document.addEventListener("scroll", schedule, true);
1051
+ window.addEventListener("resize", schedule);
1052
+ const node = toDomNode(ref);
1053
+ const observer = typeof ResizeObserver === "function" ? new ResizeObserver(schedule) : null;
1054
+ if (observer !== null) {
1055
+ if (node !== null) observer.observe(node);
1056
+ observer.observe(document.body);
1057
+ }
1058
+ return () => {
1059
+ disposed = true;
1060
+ if (frame !== null) window.cancelAnimationFrame(frame);
1061
+ document.removeEventListener("scroll", schedule, true);
1062
+ window.removeEventListener("resize", schedule);
1063
+ if (observer !== null) observer.disconnect();
1064
+ };
1065
+ }, [ref, enabled]);
1066
+ return IS_WEB5 && enabled ? rect : null;
1067
+ }
1068
+ var IS_WEB6 = typeof document !== "undefined";
995
1069
  var ESCAPE_KEY = "Escape";
996
1070
  var ACTIVE_WEIGHT = "700";
997
1071
  var IDLE_WEIGHT = "400";
998
1072
  function useDismiss(anchorRef, menuRef, onDismiss) {
999
1073
  useEffect(() => {
1000
- if (!IS_WEB5) return void 0;
1074
+ if (!IS_WEB6) return void 0;
1001
1075
  const onMouseDown = (event) => {
1002
1076
  const anchorNode = anchorRef.current;
1003
1077
  const menuNode = menuRef.current;
@@ -1025,16 +1099,19 @@ function AnchoredMenu({
1025
1099
  colors,
1026
1100
  optionHint,
1027
1101
  testID,
1028
- anchorRef
1102
+ anchorRef,
1103
+ usePortal = false
1029
1104
  }) {
1030
1105
  const menuRef = React2.useRef(null);
1031
1106
  useDismiss(anchorRef, menuRef, onDismiss);
1032
- return /* @__PURE__ */ jsx(
1107
+ const rect = useAnchorRect(anchorRef, usePortal);
1108
+ const portalStyle = rect !== null ? buildPortalPopoverStyle(rect) : null;
1109
+ const menu = /* @__PURE__ */ jsx(
1033
1110
  View,
1034
1111
  {
1035
1112
  ref: menuRef,
1036
1113
  accessibilityRole: "menu",
1037
- style: [controlStyles.menu, { borderColor: colors.border, backgroundColor: colors.surface }],
1114
+ style: [controlStyles.menu, { borderColor: colors.border, backgroundColor: colors.surface }, portalStyle],
1038
1115
  testID: `${testID}-menu`,
1039
1116
  children: options.map((opt) => {
1040
1117
  const active = opt.value === selectedValue;
@@ -1056,6 +1133,8 @@ function AnchoredMenu({
1056
1133
  })
1057
1134
  }
1058
1135
  );
1136
+ if (usePortal && IS_WEB6) return createPortal(menu, document.body);
1137
+ return menu;
1059
1138
  }
1060
1139
  var CHEVRON_DOWN = "\u25BE";
1061
1140
  var CHEVRON_UP = "\u25B4";
@@ -1239,7 +1318,8 @@ function TypeaheadControl({
1239
1318
  colors: { text: colors.text, border: colors.border, surface: colors.surface, brand: palette.primary["500"] },
1240
1319
  optionHint,
1241
1320
  testID,
1242
- anchorRef
1321
+ anchorRef,
1322
+ usePortal: true
1243
1323
  }
1244
1324
  ) : null,
1245
1325
  hasError ? /* @__PURE__ */ jsx(Text, { role: "alert", style: [controlStyles.error, { color: semantic.error["500"] }], testID: `${testID}-error`, children: error }) : null