@dloizides/ui-forms 1.9.1 → 1.11.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.js CHANGED
@@ -1,25 +1,63 @@
1
1
  'use strict';
2
2
 
3
- var React = require('react');
3
+ var React2 = require('react');
4
4
  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');
8
9
 
9
10
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
11
 
11
- var React__default = /*#__PURE__*/_interopDefault(React);
12
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
12
13
 
13
14
  // src/Field/Field.tsx
15
+ function attachControlId(child, candidateId, describedById) {
16
+ if (!React2__default.default.isValidElement(child) || child.type === React2__default.default.Fragment) {
17
+ return { node: child };
18
+ }
19
+ const props = child.props;
20
+ const existing = props.id ?? props.nativeID;
21
+ const element = child;
22
+ const controlId = existing !== void 0 && existing !== "" ? existing : candidateId;
23
+ const injected = { id: controlId };
24
+ if (describedById !== void 0 && props.describedById === void 0) {
25
+ injected.describedById = describedById;
26
+ }
27
+ return { node: React2__default.default.cloneElement(element, injected), controlId };
28
+ }
29
+ var IS_WEB = reactNative.Platform.OS === "web";
30
+ function isAttributeHost(node) {
31
+ return typeof node === "object" && node !== null && typeof node.setAttribute === "function";
32
+ }
33
+ function useHtmlFor(controlId) {
34
+ const ref = React2.useRef(null);
35
+ React2.useEffect(() => {
36
+ const node = ref.current;
37
+ if (!IS_WEB || !isAttributeHost(node)) return;
38
+ const target = controlId !== void 0 && controlId !== "" ? node.ownerDocument?.getElementById(controlId) : null;
39
+ if (target === null || target === void 0) {
40
+ node.removeAttribute("for");
41
+ return;
42
+ }
43
+ node.setAttribute("for", controlId);
44
+ }, [controlId]);
45
+ return ref;
46
+ }
47
+ var IS_WEB2 = reactNative.Platform.OS === "web";
14
48
  function FieldLabel({
15
49
  label,
16
50
  marker,
17
51
  optionalLabel,
52
+ controlId,
53
+ labelId,
18
54
  style,
19
55
  requiredMarkStyle,
20
56
  optionalMarkStyle
21
57
  }) {
22
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style, children: [
58
+ const webProps = IS_WEB2 ? { role: "label" } : {};
59
+ const labelRef = useHtmlFor(controlId);
60
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { id: labelId, ref: labelRef, style, ...webProps, children: [
23
61
  label,
24
62
  " ",
25
63
  marker === "required" ? (
@@ -96,15 +134,15 @@ var labelVariantStyles = reactNative.StyleSheet.create({
96
134
  function resolveLabelVariantStyle(variant) {
97
135
  return variant === "control" ? labelVariantStyles.control : labelVariantStyles.field;
98
136
  }
99
- var FormGridContext = React__default.default.createContext(null);
100
- var GapOwnedContext = React__default.default.createContext(false);
137
+ var FormGridContext = React2__default.default.createContext(null);
138
+ var GapOwnedContext = React2__default.default.createContext(false);
101
139
  var FormGridProvider = FormGridContext.Provider;
102
140
  var GapOwnedProvider = GapOwnedContext.Provider;
103
141
  function useFormGridMetrics() {
104
- return React__default.default.useContext(FormGridContext);
142
+ return React2__default.default.useContext(FormGridContext);
105
143
  }
106
144
  function useIsGapOwned() {
107
- return React__default.default.useContext(GapOwnedContext);
145
+ return React2__default.default.useContext(GapOwnedContext);
108
146
  }
109
147
  var ERROR_FONT_SIZE = 12;
110
148
  var HINT_FONT_SIZE = 12;
@@ -122,10 +160,15 @@ var styles = reactNative.StyleSheet.create({
122
160
  });
123
161
  var fieldSeq = 0;
124
162
  function useFieldIds() {
125
- const [ids] = React__default.default.useState(() => {
163
+ const [ids] = React2__default.default.useState(() => {
126
164
  fieldSeq += 1;
127
165
  const seq = String(fieldSeq);
128
- return { hintId: `field-hint-${seq}`, errorId: `field-error-${seq}` };
166
+ return {
167
+ hintId: `field-hint-${seq}`,
168
+ errorId: `field-error-${seq}`,
169
+ controlId: `field-control-${seq}`,
170
+ labelId: `field-label-${seq}`
171
+ };
129
172
  });
130
173
  return ids;
131
174
  }
@@ -163,8 +206,8 @@ var Field = ({
163
206
  const labelText = label ?? "";
164
207
  const hasLabel = labelText !== "";
165
208
  const marker = resolveFieldMarker(required, optional);
166
- const { hintId, errorId } = useFieldIds();
167
- const themeStyles = React__default.default.useMemo(
209
+ const { hintId, errorId, controlId, labelId } = useFieldIds();
210
+ const themeStyles = React2__default.default.useMemo(
168
211
  () => ({
169
212
  label: { color: colors.textSecondary },
170
213
  requiredMark: { color: errorColor },
@@ -176,12 +219,16 @@ var Field = ({
176
219
  [colors.textSecondary, errorColor]
177
220
  );
178
221
  const describedById = joinDescribedBy(hasHint ? hintId : void 0, hasError ? errorId : void 0);
179
- const control = typeof children === "function" ? children({ describedById, hasError }) : children;
222
+ const isRenderChild = typeof children === "function";
223
+ const attached = isRenderChild ? { node: children({ describedById, hasError, controlId, labelId }), controlId } : attachControlId(children, controlId, describedById);
224
+ const control = attached.node;
180
225
  return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [spacingStyles.container, containerStyle], testID, children: [
181
226
  hasLabel ? /* @__PURE__ */ jsxRuntime.jsx(
182
227
  FieldLabel,
183
228
  {
229
+ controlId: attached.controlId,
184
230
  label: labelText,
231
+ labelId,
185
232
  marker,
186
233
  optionalLabel,
187
234
  optionalMarkStyle: [styles.optionalMark, themeStyles.optionalMark],
@@ -211,7 +258,7 @@ var FormGrid = ({
211
258
  style,
212
259
  testID
213
260
  }) => {
214
- const metrics = React__default.default.useMemo(() => ({ minWidth, basis }), [minWidth, basis]);
261
+ const metrics = React2__default.default.useMemo(() => ({ minWidth, basis }), [minWidth, basis]);
215
262
  return /* @__PURE__ */ jsxRuntime.jsx(FormGridProvider, { value: metrics, children: /* @__PURE__ */ jsxRuntime.jsx(GapOwnedProvider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles2.grid, { gap }, style], testID, children }) }) });
216
263
  };
217
264
  var GROW = 1;
@@ -246,7 +293,7 @@ var styles3 = reactNative.StyleSheet.create({
246
293
  });
247
294
  var sectionSeq = 0;
248
295
  function useSectionLegendId() {
249
- const [id] = React__default.default.useState(() => {
296
+ const [id] = React2__default.default.useState(() => {
250
297
  sectionSeq += 1;
251
298
  return `form-section-legend-${String(sectionSeq)}`;
252
299
  });
@@ -333,7 +380,90 @@ var FormActions = ({
333
380
  }
334
381
  )
335
382
  ] });
336
- var IS_WEB = reactNative.Platform.OS === "web";
383
+ var FIELD_GAP = 4;
384
+ var LABEL_FONT = 11;
385
+ var LABEL_LETTER_SPACING = 0.4;
386
+ var INPUT_RADIUS = 8;
387
+ var INPUT_PAD_H = 12;
388
+ var INPUT_PAD_V = 10;
389
+ var INPUT_FONT = 14;
390
+ var INPUT_BORDER = 1;
391
+ var DATE_ROW_GAP = 8;
392
+ var CHEVRON_FONT = 10;
393
+ var MENU_TOP_GAP = 4;
394
+ var MENU_RADIUS = 8;
395
+ var MENU_PAD_V = 4;
396
+ var MENU_OPT_PAD_H = 12;
397
+ var MENU_OPT_PAD_V = 8;
398
+ var MENU_MIN_WIDTH = 160;
399
+ var MENU_MAX_HEIGHT = 260;
400
+ var MENU_Z = 1e3;
401
+ var MENU_ELEVATION = 8;
402
+ var MENU_BOX_SHADOW = "0px 2px 8px rgba(0, 0, 0, 0.15)";
403
+ var OPTION_FONT = 14;
404
+ var ERROR_FONT = 12;
405
+ var ERROR_GAP = 3;
406
+ var controlStyles = reactNative.StyleSheet.create({
407
+ /**
408
+ * The shared bordered box every dense text control renders in.
409
+ *
410
+ * `minHeight` is the kit floor: padding alone produced a ~38px box, and a bare input with no
411
+ * box style at all measured **19px** in production. Height is a hit-target guarantee, so it
412
+ * is stated rather than left to emerge from font size + padding.
413
+ */
414
+ input: {
415
+ borderWidth: INPUT_BORDER,
416
+ borderRadius: INPUT_RADIUS,
417
+ paddingHorizontal: INPUT_PAD_H,
418
+ paddingVertical: INPUT_PAD_V,
419
+ fontSize: INPUT_FONT,
420
+ minHeight: a11y.MIN_TOUCH_TARGET_PX
421
+ },
422
+ /** The select trigger: a bordered field box matching the text inputs + a chevron. */
423
+ selectTrigger: {
424
+ minHeight: a11y.MIN_TOUCH_TARGET_PX,
425
+ flexDirection: "row",
426
+ alignItems: "center",
427
+ justifyContent: "space-between",
428
+ gap: DATE_ROW_GAP,
429
+ borderWidth: INPUT_BORDER,
430
+ borderRadius: INPUT_RADIUS,
431
+ paddingHorizontal: INPUT_PAD_H,
432
+ paddingVertical: INPUT_PAD_V
433
+ },
434
+ selectTriggerText: { fontSize: INPUT_FONT, flexShrink: 1 },
435
+ chevron: { fontSize: CHEVRON_FONT },
436
+ dateRow: { flexDirection: "row", gap: DATE_ROW_GAP },
437
+ dateCol: { gap: FIELD_GAP, flex: 1 },
438
+ /**
439
+ * The micro-caption above a sub-control (a date range's From/To) and beside a switch. Note it
440
+ * is deliberately NOT the `control` label voice: it carries no `700`/uppercase, because it
441
+ * names a HALF of a control that already has a `control`-voice label above it. Uppercasing it
442
+ * too would make one field shout twice.
443
+ */
444
+ subLabel: { fontSize: LABEL_FONT, letterSpacing: LABEL_LETTER_SPACING },
445
+ /** Positioning context for a floating `AnchoredMenu` (`position:absolute; top:100%`). */
446
+ anchor: { position: "relative" },
447
+ menu: {
448
+ position: "absolute",
449
+ top: "100%",
450
+ left: 0,
451
+ marginTop: MENU_TOP_GAP,
452
+ minWidth: MENU_MIN_WIDTH,
453
+ maxHeight: MENU_MAX_HEIGHT,
454
+ borderWidth: INPUT_BORDER,
455
+ borderRadius: MENU_RADIUS,
456
+ paddingVertical: MENU_PAD_V,
457
+ zIndex: MENU_Z,
458
+ boxShadow: MENU_BOX_SHADOW,
459
+ elevation: MENU_ELEVATION,
460
+ overflow: "hidden"
461
+ },
462
+ option: { paddingHorizontal: MENU_OPT_PAD_H, paddingVertical: MENU_OPT_PAD_V },
463
+ optionText: { fontSize: OPTION_FONT },
464
+ error: { fontSize: ERROR_FONT, marginTop: ERROR_GAP }
465
+ });
466
+ var IS_WEB3 = reactNative.Platform.OS === "web";
337
467
  var TRANSITION_PROPERTY = "border-color, background-color, box-shadow";
338
468
  var TRANSITION_DURATION = "0.14s";
339
469
  var TRANSITION_TIMING = "ease";
@@ -348,7 +478,7 @@ var GREEN_START = 3;
348
478
  var BLUE_START = 5;
349
479
  var CHANNEL_END_OFFSET = 2;
350
480
  function prefersReducedMotion() {
351
- if (!IS_WEB || typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
481
+ if (!IS_WEB3 || typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
352
482
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
353
483
  }
354
484
  function withAlpha(color, alpha) {
@@ -365,33 +495,33 @@ function useThemedInput(options = {}) {
365
495
  const { hasError = false, onFocus, onBlur } = options;
366
496
  const { theme } = uiFeedback.useUi();
367
497
  const { colors, palette, semantic } = theme;
368
- const [isFocused, setIsFocused] = React.useState(false);
369
- const [isHovered, setIsHovered] = React.useState(false);
370
- const handleFocus = React.useCallback(
498
+ const [isFocused, setIsFocused] = React2.useState(false);
499
+ const [isHovered, setIsHovered] = React2.useState(false);
500
+ const handleFocus = React2.useCallback(
371
501
  (e) => {
372
502
  setIsFocused(true);
373
503
  onFocus?.(e);
374
504
  },
375
505
  [onFocus]
376
506
  );
377
- const handleBlur = React.useCallback(
507
+ const handleBlur = React2.useCallback(
378
508
  (e) => {
379
509
  setIsFocused(false);
380
510
  onBlur?.(e);
381
511
  },
382
512
  [onBlur]
383
513
  );
384
- const handleHoverIn = React.useCallback(() => setIsHovered(true), []);
385
- const handleHoverOut = React.useCallback(() => setIsHovered(false), []);
514
+ const handleHoverIn = React2.useCallback(() => setIsHovered(true), []);
515
+ const handleHoverOut = React2.useCallback(() => setIsHovered(false), []);
386
516
  const primary = palette.primary["500"];
387
517
  const errorColor = semantic.error["500"];
388
- const style = React.useMemo(() => {
518
+ const style = React2.useMemo(() => {
389
519
  const restBorder = hasError ? errorColor : colors.border;
390
520
  const focusBorder = hasError ? errorColor : primary;
391
521
  const borderColor = isFocused ? focusBorder : isHovered ? withAlpha(colors.text, HOVER_BORDER_ALPHA) : restBorder;
392
522
  const backgroundColor = isFocused ? colors.surface : colors.surfaceElevated;
393
523
  const base = { color: colors.text, borderColor, backgroundColor };
394
- if (!IS_WEB) return base;
524
+ if (!IS_WEB3) return base;
395
525
  const ringColor = hasError ? errorColor : primary;
396
526
  const ring = isFocused ? `0 0 0 ${FOCUS_RING_WIDTH}px ${withAlpha(ringColor, FOCUS_RING_ALPHA)}` : "none";
397
527
  const webStyle = {
@@ -414,9 +544,9 @@ function useThemedInput(options = {}) {
414
544
  primary,
415
545
  errorColor
416
546
  ]);
417
- const focusBind = React.useMemo(() => ({ onFocus: handleFocus, onBlur: handleBlur }), [handleFocus, handleBlur]);
418
- const hoverBind = React.useMemo(
419
- () => IS_WEB ? { onMouseEnter: handleHoverIn, onMouseLeave: handleHoverOut } : {},
547
+ const focusBind = React2.useMemo(() => ({ onFocus: handleFocus, onBlur: handleBlur }), [handleFocus, handleBlur]);
548
+ const hoverBind = React2.useMemo(
549
+ () => IS_WEB3 ? { onMouseEnter: handleHoverIn, onMouseLeave: handleHoverOut } : {},
420
550
  [handleHoverIn, handleHoverOut]
421
551
  );
422
552
  return {
@@ -428,9 +558,9 @@ function useThemedInput(options = {}) {
428
558
  hoverBind
429
559
  };
430
560
  }
431
- var IS_WEB2 = reactNative.Platform.OS === "web";
561
+ var IS_WEB4 = reactNative.Platform.OS === "web";
432
562
  function webFieldA11y({ describedById, hasError = false, required = false }) {
433
- if (!IS_WEB2) {
563
+ if (!IS_WEB4) {
434
564
  return {};
435
565
  }
436
566
  return {
@@ -439,7 +569,24 @@ function webFieldA11y({ describedById, hasError = false, required = false }) {
439
569
  "aria-required": required ? true : void 0
440
570
  };
441
571
  }
442
- var ThemedTextInput = React__default.default.forwardRef(function ThemedTextInput2({ hasError = false, describedById, requiredField = false, onFocus, onBlur, style, placeholderTextColor, ...rest }, ref) {
572
+ var MULTILINE_MIN_HEIGHT = 96;
573
+ var boxStyles = reactNative.StyleSheet.create({
574
+ multiline: {
575
+ minHeight: MULTILINE_MIN_HEIGHT,
576
+ textAlignVertical: "top"
577
+ }
578
+ });
579
+ var ThemedTextInput = React2__default.default.forwardRef(function ThemedTextInput2({
580
+ hasError = false,
581
+ describedById,
582
+ requiredField = false,
583
+ unstyledBox = false,
584
+ onFocus,
585
+ onBlur,
586
+ style,
587
+ placeholderTextColor,
588
+ ...rest
589
+ }, ref) {
443
590
  const {
444
591
  style: themedStyle,
445
592
  placeholderTextColor: themedPlaceholder,
@@ -447,6 +594,8 @@ var ThemedTextInput = React__default.default.forwardRef(function ThemedTextInput
447
594
  hoverBind
448
595
  } = useThemedInput({ hasError, onFocus, onBlur });
449
596
  const webA11y = webFieldA11y({ describedById, hasError, required: requiredField });
597
+ const boxStyle = unstyledBox ? null : controlStyles.input;
598
+ const multilineStyle = rest.multiline === true && !unstyledBox ? boxStyles.multiline : null;
450
599
  return /* @__PURE__ */ jsxRuntime.jsx(
451
600
  reactNative.TextInput,
452
601
  {
@@ -457,37 +606,25 @@ var ThemedTextInput = React__default.default.forwardRef(function ThemedTextInput
457
606
  ...hoverBind,
458
607
  onFocus: focusBind.onFocus,
459
608
  onBlur: focusBind.onBlur,
460
- style: [style, themedStyle]
609
+ style: [boxStyle, multilineStyle, style, themedStyle]
461
610
  }
462
611
  );
463
612
  });
464
- var INPUT_PADDING_V = 10;
465
- var INPUT_PADDING_H = 12;
466
- var INPUT_BORDER_RADIUS = 8;
467
- var INPUT_BORDER_WIDTH = 1;
468
- var styles5 = reactNative.StyleSheet.create({
469
- input: {
470
- paddingVertical: INPUT_PADDING_V,
471
- paddingHorizontal: INPUT_PADDING_H,
472
- borderRadius: INPUT_BORDER_RADIUS,
473
- borderWidth: INPUT_BORDER_WIDTH
474
- }
475
- });
476
613
  var FormField = ({
477
614
  label,
478
615
  required = false,
479
616
  error,
480
617
  containerStyle,
481
618
  ...textInputProps
482
- }) => /* @__PURE__ */ jsxRuntime.jsx(Field, { containerStyle, error, label, required, children: ({ describedById, hasError }) => /* @__PURE__ */ jsxRuntime.jsx(
619
+ }) => /* @__PURE__ */ jsxRuntime.jsx(Field, { containerStyle, error, label, required, children: ({ describedById, hasError, controlId }) => /* @__PURE__ */ jsxRuntime.jsx(
483
620
  ThemedTextInput,
484
621
  {
485
622
  accessibilityHint: `Enter ${label}`,
486
623
  accessibilityLabel: label,
487
624
  describedById,
488
625
  hasError,
626
+ id: controlId,
489
627
  requiredField: required,
490
- style: styles5.input,
491
628
  testID: "form-field-input",
492
629
  ...textInputProps
493
630
  }
@@ -525,7 +662,7 @@ function withAlpha2(hex, alpha) {
525
662
  const isValid = !Number.isNaN(r) && !Number.isNaN(g) && !Number.isNaN(b);
526
663
  return isValid ? `rgba(${r}, ${g}, ${b}, ${alpha})` : hex;
527
664
  }
528
- var styles6 = reactNative.StyleSheet.create({
665
+ var styles5 = reactNative.StyleSheet.create({
529
666
  chipContainer: {
530
667
  flexDirection: "row",
531
668
  flexWrap: "wrap",
@@ -577,7 +714,7 @@ function OutlineChip({
577
714
  ariaPressed,
578
715
  onPress
579
716
  }) {
580
- const [hovered, setHovered] = React__default.default.useState(false);
717
+ const [hovered, setHovered] = React2__default.default.useState(false);
581
718
  const active = selected || hovered;
582
719
  const borderColor = active ? colors.primary : colors.border;
583
720
  const backgroundColor = selected ? colors.tint : colors.muted;
@@ -596,7 +733,7 @@ function OutlineChip({
596
733
  onHoverIn: () => setHovered(true),
597
734
  onHoverOut: () => setHovered(false),
598
735
  onPress,
599
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles6.chipOutline, { borderColor, backgroundColor }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.chipText, styles6.chipTextOutline, { color: textColor }], children: option.label }) })
736
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles5.chipOutline, { borderColor, backgroundColor }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.chipText, styles5.chipTextOutline, { color: textColor }], children: option.label }) })
600
737
  }
601
738
  );
602
739
  }
@@ -624,7 +761,7 @@ function SolidChip({
624
761
  hitSlop: CHIP_HIT_SLOP,
625
762
  testID,
626
763
  onPress,
627
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles6.chip, { borderColor: colors.border, backgroundColor }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.chipText, { color: textColor }], children: option.label }) })
764
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles5.chip, { borderColor: colors.border, backgroundColor }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.chipText, { color: textColor }], children: option.label }) })
628
765
  }
629
766
  );
630
767
  }
@@ -646,7 +783,7 @@ var ChipSelector = ({
646
783
  }) => {
647
784
  const { theme } = uiFeedback.useUi();
648
785
  const { colors, palette } = theme;
649
- const chipColors = React__default.default.useMemo(() => {
786
+ const chipColors = React2__default.default.useMemo(() => {
650
787
  const primary = palette.primary["500"];
651
788
  return {
652
789
  border: colors.border,
@@ -689,12 +826,12 @@ var ChipSelector = ({
689
826
  required,
690
827
  spacing,
691
828
  testID,
692
- children: ({ describedById, hasError }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles6.chipContainer, ...webFieldA11y({ describedById, hasError }), children: options.map(renderChip) })
829
+ children: ({ describedById, hasError }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles5.chipContainer, ...webFieldA11y({ describedById, hasError }), children: options.map(renderChip) })
693
830
  }
694
831
  );
695
832
  };
696
833
  var DESCRIPTION_FONT_SIZE2 = 12;
697
- var styles7 = reactNative.StyleSheet.create({
834
+ var styles6 = reactNative.StyleSheet.create({
698
835
  container: {
699
836
  flexDirection: "row",
700
837
  justifyContent: "space-between",
@@ -725,7 +862,7 @@ var FormSwitch = ({
725
862
  const { theme } = uiFeedback.useUi();
726
863
  const { colors } = theme;
727
864
  const hasDescription = typeof description === "string" && description !== "";
728
- const themeStyles = React__default.default.useMemo(() => {
865
+ const themeStyles = React2__default.default.useMemo(() => {
729
866
  const labelStyle = { color: colors.text };
730
867
  const descriptionStyle = { color: colors.textSecondary };
731
868
  return { label: labelStyle, description: descriptionStyle };
@@ -734,13 +871,13 @@ var FormSwitch = ({
734
871
  reactNative.View,
735
872
  {
736
873
  style: [
737
- styles7.container,
874
+ styles6.container,
738
875
  containerStyle
739
876
  ],
740
877
  children: [
741
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles7.textColumn, children: [
742
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.label, themeStyles.label], children: label }),
743
- hasDescription ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.description, themeStyles.description], children: description }) : null
878
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles6.textColumn, children: [
879
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.label, themeStyles.label], children: label }),
880
+ hasDescription ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.description, themeStyles.description], children: description }) : null
744
881
  ] }),
745
882
  /* @__PURE__ */ jsxRuntime.jsx(
746
883
  reactNative.Switch,
@@ -765,7 +902,7 @@ var BOX_SIZE = 20;
765
902
  var BOX_RADIUS = 5;
766
903
  var CHECK_FONT_SIZE = 14;
767
904
  var DISABLED_OPACITY = 0.5;
768
- var styles8 = reactNative.StyleSheet.create({
905
+ var styles7 = reactNative.StyleSheet.create({
769
906
  container: {
770
907
  flexDirection: "row",
771
908
  alignItems: "flex-start",
@@ -816,7 +953,7 @@ var FormCheckbox = ({
816
953
  const brand = palette.primary["500"];
817
954
  const hintText = typeof hint === "string" && hint !== "" ? hint : description;
818
955
  const hasHint = typeof hintText === "string" && hintText !== "";
819
- const themeStyles = React__default.default.useMemo(() => {
956
+ const themeStyles = React2__default.default.useMemo(() => {
820
957
  const boxOn = { backgroundColor: brand, borderColor: brand };
821
958
  const boxOff = { backgroundColor: "transparent", borderColor: colors.border };
822
959
  const labelStyle = { color: colors.text };
@@ -847,101 +984,26 @@ var FormCheckbox = ({
847
984
  disabled,
848
985
  onPress: toggle,
849
986
  role: "checkbox",
850
- style: [styles8.container, containerStyle, disabled ? { opacity: DISABLED_OPACITY } : null],
987
+ style: [styles7.container, containerStyle, disabled ? { opacity: DISABLED_OPACITY } : null],
851
988
  testID,
852
989
  ...keyboardProps,
853
990
  children: [
854
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles8.box, value ? themeStyles.boxOn : themeStyles.boxOff], children: value ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.check, { color: colors.surface }], accessibilityElementsHidden: true, children: "\u2713" }) : null }),
855
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles8.textColumn, children: [
856
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.label, themeStyles.label], children: label }),
857
- hasHint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.hint, themeStyles.hint], children: hintText }) : null
991
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles7.box, value ? themeStyles.boxOn : themeStyles.boxOff], children: value ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.check, { color: colors.surface }], accessibilityElementsHidden: true, children: "\u2713" }) : null }),
992
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles7.textColumn, children: [
993
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.label, themeStyles.label], children: label }),
994
+ hasHint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.hint, themeStyles.hint], children: hintText }) : null
858
995
  ] })
859
996
  ]
860
997
  }
861
998
  );
862
999
  };
863
- var FIELD_GAP = 4;
864
- var LABEL_FONT = 11;
865
- var LABEL_LETTER_SPACING = 0.4;
866
- var INPUT_RADIUS = 8;
867
- var INPUT_PAD_H = 12;
868
- var INPUT_PAD_V = 10;
869
- var INPUT_FONT = 14;
870
- var INPUT_BORDER = 1;
871
- var DATE_ROW_GAP = 8;
872
- var CHEVRON_FONT = 10;
873
- var MENU_TOP_GAP = 4;
874
- var MENU_RADIUS = 8;
875
- var MENU_PAD_V = 4;
876
- var MENU_OPT_PAD_H = 12;
877
- var MENU_OPT_PAD_V = 8;
878
- var MENU_MIN_WIDTH = 160;
879
- var MENU_MAX_HEIGHT = 260;
880
- var MENU_Z = 1e3;
881
- var MENU_ELEVATION = 8;
882
- var MENU_BOX_SHADOW = "0px 2px 8px rgba(0, 0, 0, 0.15)";
883
- var OPTION_FONT = 14;
884
- var ERROR_FONT = 12;
885
- var ERROR_GAP = 3;
886
- var controlStyles = reactNative.StyleSheet.create({
887
- /** The shared bordered box every dense text control renders in. */
888
- input: {
889
- borderWidth: INPUT_BORDER,
890
- borderRadius: INPUT_RADIUS,
891
- paddingHorizontal: INPUT_PAD_H,
892
- paddingVertical: INPUT_PAD_V,
893
- fontSize: INPUT_FONT
894
- },
895
- /** The select trigger: a bordered field box matching the text inputs + a chevron. */
896
- selectTrigger: {
897
- flexDirection: "row",
898
- alignItems: "center",
899
- justifyContent: "space-between",
900
- gap: DATE_ROW_GAP,
901
- borderWidth: INPUT_BORDER,
902
- borderRadius: INPUT_RADIUS,
903
- paddingHorizontal: INPUT_PAD_H,
904
- paddingVertical: INPUT_PAD_V
905
- },
906
- selectTriggerText: { fontSize: INPUT_FONT, flexShrink: 1 },
907
- chevron: { fontSize: CHEVRON_FONT },
908
- dateRow: { flexDirection: "row", gap: DATE_ROW_GAP },
909
- dateCol: { gap: FIELD_GAP, flex: 1 },
910
- /**
911
- * The micro-caption above a sub-control (a date range's From/To) and beside a switch. Note it
912
- * is deliberately NOT the `control` label voice: it carries no `700`/uppercase, because it
913
- * names a HALF of a control that already has a `control`-voice label above it. Uppercasing it
914
- * too would make one field shout twice.
915
- */
916
- subLabel: { fontSize: LABEL_FONT, letterSpacing: LABEL_LETTER_SPACING },
917
- /** Positioning context for a floating `AnchoredMenu` (`position:absolute; top:100%`). */
918
- anchor: { position: "relative" },
919
- menu: {
920
- position: "absolute",
921
- top: "100%",
922
- left: 0,
923
- marginTop: MENU_TOP_GAP,
924
- minWidth: MENU_MIN_WIDTH,
925
- maxHeight: MENU_MAX_HEIGHT,
926
- borderWidth: INPUT_BORDER,
927
- borderRadius: MENU_RADIUS,
928
- paddingVertical: MENU_PAD_V,
929
- zIndex: MENU_Z,
930
- boxShadow: MENU_BOX_SHADOW,
931
- elevation: MENU_ELEVATION,
932
- overflow: "hidden"
933
- },
934
- option: { paddingHorizontal: MENU_OPT_PAD_H, paddingVertical: MENU_OPT_PAD_V },
935
- optionText: { fontSize: OPTION_FONT },
936
- error: { fontSize: ERROR_FONT, marginTop: ERROR_GAP }
937
- });
938
- var IS_WEB3 = typeof document !== "undefined";
1000
+ var IS_WEB5 = typeof document !== "undefined";
939
1001
  var ESCAPE_KEY = "Escape";
940
1002
  var ACTIVE_WEIGHT = "700";
941
1003
  var IDLE_WEIGHT = "400";
942
1004
  function useDismiss(anchorRef, menuRef, onDismiss) {
943
- React.useEffect(() => {
944
- if (!IS_WEB3) return void 0;
1005
+ React2.useEffect(() => {
1006
+ if (!IS_WEB5) return void 0;
945
1007
  const onMouseDown = (event) => {
946
1008
  const anchorNode = anchorRef.current;
947
1009
  const menuNode = menuRef.current;
@@ -971,7 +1033,7 @@ function AnchoredMenu({
971
1033
  testID,
972
1034
  anchorRef
973
1035
  }) {
974
- const menuRef = React__default.default.useRef(null);
1036
+ const menuRef = React2__default.default.useRef(null);
975
1037
  useDismiss(anchorRef, menuRef, onDismiss);
976
1038
  return /* @__PURE__ */ jsxRuntime.jsx(
977
1039
  reactNative.View,
@@ -1010,25 +1072,26 @@ function SelectControl({
1010
1072
  placeholder = "",
1011
1073
  accessibilityLabel,
1012
1074
  accessibilityHint,
1075
+ labelledById,
1013
1076
  optionHint = "",
1014
1077
  style,
1015
1078
  testID
1016
1079
  }) {
1017
1080
  const { theme } = uiFeedback.useUi();
1018
1081
  const { colors, palette } = theme;
1019
- const anchorRef = React.useRef(null);
1020
- const [isOpen, setIsOpen] = React.useState(false);
1021
- const selectedLabel = React.useMemo(() => {
1082
+ const anchorRef = React2.useRef(null);
1083
+ const [isOpen, setIsOpen] = React2.useState(false);
1084
+ const selectedLabel = React2.useMemo(() => {
1022
1085
  const found = options.find((o) => o.value === value);
1023
1086
  return found?.label ?? placeholder;
1024
1087
  }, [options, placeholder, value]);
1025
- const close = React.useCallback(() => {
1088
+ const close = React2.useCallback(() => {
1026
1089
  setIsOpen(false);
1027
1090
  }, []);
1028
- const toggle = React.useCallback(() => {
1091
+ const toggle = React2.useCallback(() => {
1029
1092
  setIsOpen((prev) => !prev);
1030
1093
  }, []);
1031
- const select = React.useCallback(
1094
+ const select = React2.useCallback(
1032
1095
  (next) => {
1033
1096
  onChange(next);
1034
1097
  setIsOpen(false);
@@ -1044,6 +1107,7 @@ function SelectControl({
1044
1107
  accessibilityHint,
1045
1108
  accessibilityState: { expanded: isOpen },
1046
1109
  "aria-expanded": isOpen,
1110
+ "aria-labelledby": labelledById,
1047
1111
  onPress: toggle,
1048
1112
  style: [controlStyles.selectTrigger, { borderColor: colors.border, backgroundColor: colors.surface }],
1049
1113
  testID: `${testID}-trigger`,
@@ -1120,22 +1184,22 @@ function TypeaheadControl({
1120
1184
  }) {
1121
1185
  const { theme } = uiFeedback.useUi();
1122
1186
  const { colors, palette, semantic } = theme;
1123
- const anchorRef = React.useRef(null);
1124
- const [isOpen, setIsOpen] = React.useState(false);
1125
- const exactPick = React.useMemo(() => options.some((o) => o.label === value), [options, value]);
1126
- const suggestions = React.useMemo(
1187
+ const anchorRef = React2.useRef(null);
1188
+ const [isOpen, setIsOpen] = React2.useState(false);
1189
+ const exactPick = React2.useMemo(() => options.some((o) => o.label === value), [options, value]);
1190
+ const suggestions = React2.useMemo(
1127
1191
  () => exactPick ? [] : suggestOptions(options, value, minChars, maxSuggestions),
1128
1192
  [exactPick, options, value, minChars, maxSuggestions]
1129
1193
  );
1130
1194
  const showMenu = isOpen && suggestions.length > 0;
1131
- const handleChange = React.useCallback(
1195
+ const handleChange = React2.useCallback(
1132
1196
  (next) => {
1133
1197
  onChange(next);
1134
1198
  setIsOpen(true);
1135
1199
  },
1136
1200
  [onChange]
1137
1201
  );
1138
- const pick = React.useCallback(
1202
+ const pick = React2.useCallback(
1139
1203
  (optionValue) => {
1140
1204
  const opt = options.find((o) => o.value === optionValue);
1141
1205
  onChange(opt?.label ?? optionValue);
@@ -1143,10 +1207,10 @@ function TypeaheadControl({
1143
1207
  },
1144
1208
  [options, onChange]
1145
1209
  );
1146
- const handleBlur = React.useCallback(() => {
1210
+ const handleBlur = React2.useCallback(() => {
1147
1211
  setTimeout(() => setIsOpen(false), CLOSE_DELAY_MS);
1148
1212
  }, []);
1149
- const handleSubmit = React.useCallback(() => {
1213
+ const handleSubmit = React2.useCallback(() => {
1150
1214
  setIsOpen(false);
1151
1215
  onSubmit?.();
1152
1216
  }, [onSubmit]);