@dloizides/ui-forms 1.9.1 → 1.12.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/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,41 +606,39 @@ 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,
615
+ labelHidden = false,
478
616
  required = false,
479
617
  error,
480
618
  containerStyle,
481
619
  ...textInputProps
482
- }) => /* @__PURE__ */ jsxRuntime.jsx(Field, { containerStyle, error, label, required, children: ({ describedById, hasError }) => /* @__PURE__ */ jsxRuntime.jsx(
483
- ThemedTextInput,
620
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
621
+ Field,
484
622
  {
485
- accessibilityHint: `Enter ${label}`,
486
- accessibilityLabel: label,
487
- describedById,
488
- hasError,
489
- requiredField: required,
490
- style: styles5.input,
491
- testID: "form-field-input",
492
- ...textInputProps
623
+ containerStyle,
624
+ error,
625
+ label: labelHidden ? void 0 : label,
626
+ required,
627
+ children: ({ describedById, hasError, controlId }) => /* @__PURE__ */ jsxRuntime.jsx(
628
+ ThemedTextInput,
629
+ {
630
+ accessibilityHint: `Enter ${label}`,
631
+ accessibilityLabel: label,
632
+ describedById,
633
+ hasError,
634
+ id: controlId,
635
+ requiredField: required,
636
+ testID: "form-field-input",
637
+ ...textInputProps
638
+ }
639
+ )
493
640
  }
494
- ) });
641
+ );
495
642
  var TRANSPARENT_COLOR = "transparent";
496
643
  var WHITE_COLOR = "#fff";
497
644
  var SOLID_RADIUS = 20;
@@ -525,7 +672,7 @@ function withAlpha2(hex, alpha) {
525
672
  const isValid = !Number.isNaN(r) && !Number.isNaN(g) && !Number.isNaN(b);
526
673
  return isValid ? `rgba(${r}, ${g}, ${b}, ${alpha})` : hex;
527
674
  }
528
- var styles6 = reactNative.StyleSheet.create({
675
+ var styles5 = reactNative.StyleSheet.create({
529
676
  chipContainer: {
530
677
  flexDirection: "row",
531
678
  flexWrap: "wrap",
@@ -577,7 +724,7 @@ function OutlineChip({
577
724
  ariaPressed,
578
725
  onPress
579
726
  }) {
580
- const [hovered, setHovered] = React__default.default.useState(false);
727
+ const [hovered, setHovered] = React2__default.default.useState(false);
581
728
  const active = selected || hovered;
582
729
  const borderColor = active ? colors.primary : colors.border;
583
730
  const backgroundColor = selected ? colors.tint : colors.muted;
@@ -596,7 +743,7 @@ function OutlineChip({
596
743
  onHoverIn: () => setHovered(true),
597
744
  onHoverOut: () => setHovered(false),
598
745
  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 }) })
746
+ 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
747
  }
601
748
  );
602
749
  }
@@ -624,7 +771,7 @@ function SolidChip({
624
771
  hitSlop: CHIP_HIT_SLOP,
625
772
  testID,
626
773
  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 }) })
774
+ 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
775
  }
629
776
  );
630
777
  }
@@ -646,7 +793,7 @@ var ChipSelector = ({
646
793
  }) => {
647
794
  const { theme } = uiFeedback.useUi();
648
795
  const { colors, palette } = theme;
649
- const chipColors = React__default.default.useMemo(() => {
796
+ const chipColors = React2__default.default.useMemo(() => {
650
797
  const primary = palette.primary["500"];
651
798
  return {
652
799
  border: colors.border,
@@ -689,12 +836,12 @@ var ChipSelector = ({
689
836
  required,
690
837
  spacing,
691
838
  testID,
692
- children: ({ describedById, hasError }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles6.chipContainer, ...webFieldA11y({ describedById, hasError }), children: options.map(renderChip) })
839
+ children: ({ describedById, hasError }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles5.chipContainer, ...webFieldA11y({ describedById, hasError }), children: options.map(renderChip) })
693
840
  }
694
841
  );
695
842
  };
696
843
  var DESCRIPTION_FONT_SIZE2 = 12;
697
- var styles7 = reactNative.StyleSheet.create({
844
+ var styles6 = reactNative.StyleSheet.create({
698
845
  container: {
699
846
  flexDirection: "row",
700
847
  justifyContent: "space-between",
@@ -725,7 +872,7 @@ var FormSwitch = ({
725
872
  const { theme } = uiFeedback.useUi();
726
873
  const { colors } = theme;
727
874
  const hasDescription = typeof description === "string" && description !== "";
728
- const themeStyles = React__default.default.useMemo(() => {
875
+ const themeStyles = React2__default.default.useMemo(() => {
729
876
  const labelStyle = { color: colors.text };
730
877
  const descriptionStyle = { color: colors.textSecondary };
731
878
  return { label: labelStyle, description: descriptionStyle };
@@ -734,13 +881,13 @@ var FormSwitch = ({
734
881
  reactNative.View,
735
882
  {
736
883
  style: [
737
- styles7.container,
884
+ styles6.container,
738
885
  containerStyle
739
886
  ],
740
887
  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
888
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles6.textColumn, children: [
889
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.label, themeStyles.label], children: label }),
890
+ hasDescription ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.description, themeStyles.description], children: description }) : null
744
891
  ] }),
745
892
  /* @__PURE__ */ jsxRuntime.jsx(
746
893
  reactNative.Switch,
@@ -765,7 +912,7 @@ var BOX_SIZE = 20;
765
912
  var BOX_RADIUS = 5;
766
913
  var CHECK_FONT_SIZE = 14;
767
914
  var DISABLED_OPACITY = 0.5;
768
- var styles8 = reactNative.StyleSheet.create({
915
+ var styles7 = reactNative.StyleSheet.create({
769
916
  container: {
770
917
  flexDirection: "row",
771
918
  alignItems: "flex-start",
@@ -816,7 +963,7 @@ var FormCheckbox = ({
816
963
  const brand = palette.primary["500"];
817
964
  const hintText = typeof hint === "string" && hint !== "" ? hint : description;
818
965
  const hasHint = typeof hintText === "string" && hintText !== "";
819
- const themeStyles = React__default.default.useMemo(() => {
966
+ const themeStyles = React2__default.default.useMemo(() => {
820
967
  const boxOn = { backgroundColor: brand, borderColor: brand };
821
968
  const boxOff = { backgroundColor: "transparent", borderColor: colors.border };
822
969
  const labelStyle = { color: colors.text };
@@ -847,101 +994,26 @@ var FormCheckbox = ({
847
994
  disabled,
848
995
  onPress: toggle,
849
996
  role: "checkbox",
850
- style: [styles8.container, containerStyle, disabled ? { opacity: DISABLED_OPACITY } : null],
997
+ style: [styles7.container, containerStyle, disabled ? { opacity: DISABLED_OPACITY } : null],
851
998
  testID,
852
999
  ...keyboardProps,
853
1000
  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
1001
+ /* @__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 }),
1002
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles7.textColumn, children: [
1003
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.label, themeStyles.label], children: label }),
1004
+ hasHint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.hint, themeStyles.hint], children: hintText }) : null
858
1005
  ] })
859
1006
  ]
860
1007
  }
861
1008
  );
862
1009
  };
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";
1010
+ var IS_WEB5 = typeof document !== "undefined";
939
1011
  var ESCAPE_KEY = "Escape";
940
1012
  var ACTIVE_WEIGHT = "700";
941
1013
  var IDLE_WEIGHT = "400";
942
1014
  function useDismiss(anchorRef, menuRef, onDismiss) {
943
- React.useEffect(() => {
944
- if (!IS_WEB3) return void 0;
1015
+ React2.useEffect(() => {
1016
+ if (!IS_WEB5) return void 0;
945
1017
  const onMouseDown = (event) => {
946
1018
  const anchorNode = anchorRef.current;
947
1019
  const menuNode = menuRef.current;
@@ -971,7 +1043,7 @@ function AnchoredMenu({
971
1043
  testID,
972
1044
  anchorRef
973
1045
  }) {
974
- const menuRef = React__default.default.useRef(null);
1046
+ const menuRef = React2__default.default.useRef(null);
975
1047
  useDismiss(anchorRef, menuRef, onDismiss);
976
1048
  return /* @__PURE__ */ jsxRuntime.jsx(
977
1049
  reactNative.View,
@@ -1010,25 +1082,26 @@ function SelectControl({
1010
1082
  placeholder = "",
1011
1083
  accessibilityLabel,
1012
1084
  accessibilityHint,
1085
+ labelledById,
1013
1086
  optionHint = "",
1014
1087
  style,
1015
1088
  testID
1016
1089
  }) {
1017
1090
  const { theme } = uiFeedback.useUi();
1018
1091
  const { colors, palette } = theme;
1019
- const anchorRef = React.useRef(null);
1020
- const [isOpen, setIsOpen] = React.useState(false);
1021
- const selectedLabel = React.useMemo(() => {
1092
+ const anchorRef = React2.useRef(null);
1093
+ const [isOpen, setIsOpen] = React2.useState(false);
1094
+ const selectedLabel = React2.useMemo(() => {
1022
1095
  const found = options.find((o) => o.value === value);
1023
1096
  return found?.label ?? placeholder;
1024
1097
  }, [options, placeholder, value]);
1025
- const close = React.useCallback(() => {
1098
+ const close = React2.useCallback(() => {
1026
1099
  setIsOpen(false);
1027
1100
  }, []);
1028
- const toggle = React.useCallback(() => {
1101
+ const toggle = React2.useCallback(() => {
1029
1102
  setIsOpen((prev) => !prev);
1030
1103
  }, []);
1031
- const select = React.useCallback(
1104
+ const select = React2.useCallback(
1032
1105
  (next) => {
1033
1106
  onChange(next);
1034
1107
  setIsOpen(false);
@@ -1044,6 +1117,7 @@ function SelectControl({
1044
1117
  accessibilityHint,
1045
1118
  accessibilityState: { expanded: isOpen },
1046
1119
  "aria-expanded": isOpen,
1120
+ "aria-labelledby": labelledById,
1047
1121
  onPress: toggle,
1048
1122
  style: [controlStyles.selectTrigger, { borderColor: colors.border, backgroundColor: colors.surface }],
1049
1123
  testID: `${testID}-trigger`,
@@ -1120,22 +1194,22 @@ function TypeaheadControl({
1120
1194
  }) {
1121
1195
  const { theme } = uiFeedback.useUi();
1122
1196
  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(
1197
+ const anchorRef = React2.useRef(null);
1198
+ const [isOpen, setIsOpen] = React2.useState(false);
1199
+ const exactPick = React2.useMemo(() => options.some((o) => o.label === value), [options, value]);
1200
+ const suggestions = React2.useMemo(
1127
1201
  () => exactPick ? [] : suggestOptions(options, value, minChars, maxSuggestions),
1128
1202
  [exactPick, options, value, minChars, maxSuggestions]
1129
1203
  );
1130
1204
  const showMenu = isOpen && suggestions.length > 0;
1131
- const handleChange = React.useCallback(
1205
+ const handleChange = React2.useCallback(
1132
1206
  (next) => {
1133
1207
  onChange(next);
1134
1208
  setIsOpen(true);
1135
1209
  },
1136
1210
  [onChange]
1137
1211
  );
1138
- const pick = React.useCallback(
1212
+ const pick = React2.useCallback(
1139
1213
  (optionValue) => {
1140
1214
  const opt = options.find((o) => o.value === optionValue);
1141
1215
  onChange(opt?.label ?? optionValue);
@@ -1143,10 +1217,10 @@ function TypeaheadControl({
1143
1217
  },
1144
1218
  [options, onChange]
1145
1219
  );
1146
- const handleBlur = React.useCallback(() => {
1220
+ const handleBlur = React2.useCallback(() => {
1147
1221
  setTimeout(() => setIsOpen(false), CLOSE_DELAY_MS);
1148
1222
  }, []);
1149
- const handleSubmit = React.useCallback(() => {
1223
+ const handleSubmit = React2.useCallback(() => {
1150
1224
  setIsOpen(false);
1151
1225
  onSubmit?.();
1152
1226
  }, [onSubmit]);