@dloizides/ui-forms 1.9.0 → 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.mjs CHANGED
@@ -1,19 +1,57 @@
1
- import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
2
- import { StyleSheet, Platform, TextInput, View, Text, Switch, Pressable, TouchableOpacity } from 'react-native';
1
+ import React2, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
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
+ import { MIN_TOUCH_TARGET_PX } from '@dloizides/a11y';
6
7
 
7
8
  // src/Field/Field.tsx
9
+ function attachControlId(child, candidateId, describedById) {
10
+ if (!React2.isValidElement(child) || child.type === React2.Fragment) {
11
+ return { node: child };
12
+ }
13
+ const props = child.props;
14
+ const existing = props.id ?? props.nativeID;
15
+ const element = child;
16
+ const controlId = existing !== void 0 && existing !== "" ? existing : candidateId;
17
+ const injected = { id: controlId };
18
+ if (describedById !== void 0 && props.describedById === void 0) {
19
+ injected.describedById = describedById;
20
+ }
21
+ return { node: React2.cloneElement(element, injected), controlId };
22
+ }
23
+ var IS_WEB = Platform.OS === "web";
24
+ function isAttributeHost(node) {
25
+ return typeof node === "object" && node !== null && typeof node.setAttribute === "function";
26
+ }
27
+ function useHtmlFor(controlId) {
28
+ const ref = useRef(null);
29
+ useEffect(() => {
30
+ const node = ref.current;
31
+ if (!IS_WEB || !isAttributeHost(node)) return;
32
+ const target = controlId !== void 0 && controlId !== "" ? node.ownerDocument?.getElementById(controlId) : null;
33
+ if (target === null || target === void 0) {
34
+ node.removeAttribute("for");
35
+ return;
36
+ }
37
+ node.setAttribute("for", controlId);
38
+ }, [controlId]);
39
+ return ref;
40
+ }
41
+ var IS_WEB2 = Platform.OS === "web";
8
42
  function FieldLabel({
9
43
  label,
10
44
  marker,
11
45
  optionalLabel,
46
+ controlId,
47
+ labelId,
12
48
  style,
13
49
  requiredMarkStyle,
14
50
  optionalMarkStyle
15
51
  }) {
16
- return /* @__PURE__ */ jsxs(Text, { style, children: [
52
+ const webProps = IS_WEB2 ? { role: "label" } : {};
53
+ const labelRef = useHtmlFor(controlId);
54
+ return /* @__PURE__ */ jsxs(Text, { id: labelId, ref: labelRef, style, ...webProps, children: [
17
55
  label,
18
56
  " ",
19
57
  marker === "required" ? (
@@ -90,15 +128,15 @@ var labelVariantStyles = StyleSheet.create({
90
128
  function resolveLabelVariantStyle(variant) {
91
129
  return variant === "control" ? labelVariantStyles.control : labelVariantStyles.field;
92
130
  }
93
- var FormGridContext = React.createContext(null);
94
- var GapOwnedContext = React.createContext(false);
131
+ var FormGridContext = React2.createContext(null);
132
+ var GapOwnedContext = React2.createContext(false);
95
133
  var FormGridProvider = FormGridContext.Provider;
96
134
  var GapOwnedProvider = GapOwnedContext.Provider;
97
135
  function useFormGridMetrics() {
98
- return React.useContext(FormGridContext);
136
+ return React2.useContext(FormGridContext);
99
137
  }
100
138
  function useIsGapOwned() {
101
- return React.useContext(GapOwnedContext);
139
+ return React2.useContext(GapOwnedContext);
102
140
  }
103
141
  var ERROR_FONT_SIZE = 12;
104
142
  var HINT_FONT_SIZE = 12;
@@ -116,10 +154,15 @@ var styles = StyleSheet.create({
116
154
  });
117
155
  var fieldSeq = 0;
118
156
  function useFieldIds() {
119
- const [ids] = React.useState(() => {
157
+ const [ids] = React2.useState(() => {
120
158
  fieldSeq += 1;
121
159
  const seq = String(fieldSeq);
122
- return { hintId: `field-hint-${seq}`, errorId: `field-error-${seq}` };
160
+ return {
161
+ hintId: `field-hint-${seq}`,
162
+ errorId: `field-error-${seq}`,
163
+ controlId: `field-control-${seq}`,
164
+ labelId: `field-label-${seq}`
165
+ };
123
166
  });
124
167
  return ids;
125
168
  }
@@ -157,8 +200,8 @@ var Field = ({
157
200
  const labelText = label ?? "";
158
201
  const hasLabel = labelText !== "";
159
202
  const marker = resolveFieldMarker(required, optional);
160
- const { hintId, errorId } = useFieldIds();
161
- const themeStyles = React.useMemo(
203
+ const { hintId, errorId, controlId, labelId } = useFieldIds();
204
+ const themeStyles = React2.useMemo(
162
205
  () => ({
163
206
  label: { color: colors.textSecondary },
164
207
  requiredMark: { color: errorColor },
@@ -170,12 +213,16 @@ var Field = ({
170
213
  [colors.textSecondary, errorColor]
171
214
  );
172
215
  const describedById = joinDescribedBy(hasHint ? hintId : void 0, hasError ? errorId : void 0);
173
- const control = typeof children === "function" ? children({ describedById, hasError }) : children;
216
+ const isRenderChild = typeof children === "function";
217
+ const attached = isRenderChild ? { node: children({ describedById, hasError, controlId, labelId }), controlId } : attachControlId(children, controlId, describedById);
218
+ const control = attached.node;
174
219
  return /* @__PURE__ */ jsxs(View, { style: [spacingStyles.container, containerStyle], testID, children: [
175
220
  hasLabel ? /* @__PURE__ */ jsx(
176
221
  FieldLabel,
177
222
  {
223
+ controlId: attached.controlId,
178
224
  label: labelText,
225
+ labelId,
179
226
  marker,
180
227
  optionalLabel,
181
228
  optionalMarkStyle: [styles.optionalMark, themeStyles.optionalMark],
@@ -205,7 +252,7 @@ var FormGrid = ({
205
252
  style,
206
253
  testID
207
254
  }) => {
208
- const metrics = React.useMemo(() => ({ minWidth, basis }), [minWidth, basis]);
255
+ const metrics = React2.useMemo(() => ({ minWidth, basis }), [minWidth, basis]);
209
256
  return /* @__PURE__ */ jsx(FormGridProvider, { value: metrics, children: /* @__PURE__ */ jsx(GapOwnedProvider, { value: true, children: /* @__PURE__ */ jsx(View, { style: [styles2.grid, { gap }, style], testID, children }) }) });
210
257
  };
211
258
  var GROW = 1;
@@ -240,7 +287,7 @@ var styles3 = StyleSheet.create({
240
287
  });
241
288
  var sectionSeq = 0;
242
289
  function useSectionLegendId() {
243
- const [id] = React.useState(() => {
290
+ const [id] = React2.useState(() => {
244
291
  sectionSeq += 1;
245
292
  return `form-section-legend-${String(sectionSeq)}`;
246
293
  });
@@ -327,7 +374,90 @@ var FormActions = ({
327
374
  }
328
375
  )
329
376
  ] });
330
- var IS_WEB = Platform.OS === "web";
377
+ var FIELD_GAP = 4;
378
+ var LABEL_FONT = 11;
379
+ var LABEL_LETTER_SPACING = 0.4;
380
+ var INPUT_RADIUS = 8;
381
+ var INPUT_PAD_H = 12;
382
+ var INPUT_PAD_V = 10;
383
+ var INPUT_FONT = 14;
384
+ var INPUT_BORDER = 1;
385
+ var DATE_ROW_GAP = 8;
386
+ var CHEVRON_FONT = 10;
387
+ var MENU_TOP_GAP = 4;
388
+ var MENU_RADIUS = 8;
389
+ var MENU_PAD_V = 4;
390
+ var MENU_OPT_PAD_H = 12;
391
+ var MENU_OPT_PAD_V = 8;
392
+ var MENU_MIN_WIDTH = 160;
393
+ var MENU_MAX_HEIGHT = 260;
394
+ var MENU_Z = 1e3;
395
+ var MENU_ELEVATION = 8;
396
+ var MENU_BOX_SHADOW = "0px 2px 8px rgba(0, 0, 0, 0.15)";
397
+ var OPTION_FONT = 14;
398
+ var ERROR_FONT = 12;
399
+ var ERROR_GAP = 3;
400
+ var controlStyles = StyleSheet.create({
401
+ /**
402
+ * The shared bordered box every dense text control renders in.
403
+ *
404
+ * `minHeight` is the kit floor: padding alone produced a ~38px box, and a bare input with no
405
+ * box style at all measured **19px** in production. Height is a hit-target guarantee, so it
406
+ * is stated rather than left to emerge from font size + padding.
407
+ */
408
+ input: {
409
+ borderWidth: INPUT_BORDER,
410
+ borderRadius: INPUT_RADIUS,
411
+ paddingHorizontal: INPUT_PAD_H,
412
+ paddingVertical: INPUT_PAD_V,
413
+ fontSize: INPUT_FONT,
414
+ minHeight: MIN_TOUCH_TARGET_PX
415
+ },
416
+ /** The select trigger: a bordered field box matching the text inputs + a chevron. */
417
+ selectTrigger: {
418
+ minHeight: MIN_TOUCH_TARGET_PX,
419
+ flexDirection: "row",
420
+ alignItems: "center",
421
+ justifyContent: "space-between",
422
+ gap: DATE_ROW_GAP,
423
+ borderWidth: INPUT_BORDER,
424
+ borderRadius: INPUT_RADIUS,
425
+ paddingHorizontal: INPUT_PAD_H,
426
+ paddingVertical: INPUT_PAD_V
427
+ },
428
+ selectTriggerText: { fontSize: INPUT_FONT, flexShrink: 1 },
429
+ chevron: { fontSize: CHEVRON_FONT },
430
+ dateRow: { flexDirection: "row", gap: DATE_ROW_GAP },
431
+ dateCol: { gap: FIELD_GAP, flex: 1 },
432
+ /**
433
+ * The micro-caption above a sub-control (a date range's From/To) and beside a switch. Note it
434
+ * is deliberately NOT the `control` label voice: it carries no `700`/uppercase, because it
435
+ * names a HALF of a control that already has a `control`-voice label above it. Uppercasing it
436
+ * too would make one field shout twice.
437
+ */
438
+ subLabel: { fontSize: LABEL_FONT, letterSpacing: LABEL_LETTER_SPACING },
439
+ /** Positioning context for a floating `AnchoredMenu` (`position:absolute; top:100%`). */
440
+ anchor: { position: "relative" },
441
+ menu: {
442
+ position: "absolute",
443
+ top: "100%",
444
+ left: 0,
445
+ marginTop: MENU_TOP_GAP,
446
+ minWidth: MENU_MIN_WIDTH,
447
+ maxHeight: MENU_MAX_HEIGHT,
448
+ borderWidth: INPUT_BORDER,
449
+ borderRadius: MENU_RADIUS,
450
+ paddingVertical: MENU_PAD_V,
451
+ zIndex: MENU_Z,
452
+ boxShadow: MENU_BOX_SHADOW,
453
+ elevation: MENU_ELEVATION,
454
+ overflow: "hidden"
455
+ },
456
+ option: { paddingHorizontal: MENU_OPT_PAD_H, paddingVertical: MENU_OPT_PAD_V },
457
+ optionText: { fontSize: OPTION_FONT },
458
+ error: { fontSize: ERROR_FONT, marginTop: ERROR_GAP }
459
+ });
460
+ var IS_WEB3 = Platform.OS === "web";
331
461
  var TRANSITION_PROPERTY = "border-color, background-color, box-shadow";
332
462
  var TRANSITION_DURATION = "0.14s";
333
463
  var TRANSITION_TIMING = "ease";
@@ -342,7 +472,7 @@ var GREEN_START = 3;
342
472
  var BLUE_START = 5;
343
473
  var CHANNEL_END_OFFSET = 2;
344
474
  function prefersReducedMotion() {
345
- if (!IS_WEB || typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
475
+ if (!IS_WEB3 || typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
346
476
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
347
477
  }
348
478
  function withAlpha(color, alpha) {
@@ -385,7 +515,7 @@ function useThemedInput(options = {}) {
385
515
  const borderColor = isFocused ? focusBorder : isHovered ? withAlpha(colors.text, HOVER_BORDER_ALPHA) : restBorder;
386
516
  const backgroundColor = isFocused ? colors.surface : colors.surfaceElevated;
387
517
  const base = { color: colors.text, borderColor, backgroundColor };
388
- if (!IS_WEB) return base;
518
+ if (!IS_WEB3) return base;
389
519
  const ringColor = hasError ? errorColor : primary;
390
520
  const ring = isFocused ? `0 0 0 ${FOCUS_RING_WIDTH}px ${withAlpha(ringColor, FOCUS_RING_ALPHA)}` : "none";
391
521
  const webStyle = {
@@ -410,7 +540,7 @@ function useThemedInput(options = {}) {
410
540
  ]);
411
541
  const focusBind = useMemo(() => ({ onFocus: handleFocus, onBlur: handleBlur }), [handleFocus, handleBlur]);
412
542
  const hoverBind = useMemo(
413
- () => IS_WEB ? { onMouseEnter: handleHoverIn, onMouseLeave: handleHoverOut } : {},
543
+ () => IS_WEB3 ? { onMouseEnter: handleHoverIn, onMouseLeave: handleHoverOut } : {},
414
544
  [handleHoverIn, handleHoverOut]
415
545
  );
416
546
  return {
@@ -422,19 +552,44 @@ function useThemedInput(options = {}) {
422
552
  hoverBind
423
553
  };
424
554
  }
425
- var IS_WEB2 = Platform.OS === "web";
426
- var ThemedTextInput = React.forwardRef(function ThemedTextInput2({ hasError = false, describedById, requiredField = false, onFocus, onBlur, style, placeholderTextColor, ...rest }, ref) {
555
+ var IS_WEB4 = Platform.OS === "web";
556
+ function webFieldA11y({ describedById, hasError = false, required = false }) {
557
+ if (!IS_WEB4) {
558
+ return {};
559
+ }
560
+ return {
561
+ "aria-invalid": hasError ? true : void 0,
562
+ "aria-describedby": describedById,
563
+ "aria-required": required ? true : void 0
564
+ };
565
+ }
566
+ var MULTILINE_MIN_HEIGHT = 96;
567
+ var boxStyles = StyleSheet.create({
568
+ multiline: {
569
+ minHeight: MULTILINE_MIN_HEIGHT,
570
+ textAlignVertical: "top"
571
+ }
572
+ });
573
+ var ThemedTextInput = React2.forwardRef(function ThemedTextInput2({
574
+ hasError = false,
575
+ describedById,
576
+ requiredField = false,
577
+ unstyledBox = false,
578
+ onFocus,
579
+ onBlur,
580
+ style,
581
+ placeholderTextColor,
582
+ ...rest
583
+ }, ref) {
427
584
  const {
428
585
  style: themedStyle,
429
586
  placeholderTextColor: themedPlaceholder,
430
587
  focusBind,
431
588
  hoverBind
432
589
  } = useThemedInput({ hasError, onFocus, onBlur });
433
- const webA11y = IS_WEB2 ? {
434
- "aria-invalid": hasError ? true : void 0,
435
- "aria-describedby": describedById,
436
- "aria-required": requiredField ? true : void 0
437
- } : {};
590
+ const webA11y = webFieldA11y({ describedById, hasError, required: requiredField });
591
+ const boxStyle = unstyledBox ? null : controlStyles.input;
592
+ const multilineStyle = rest.multiline === true && !unstyledBox ? boxStyles.multiline : null;
438
593
  return /* @__PURE__ */ jsx(
439
594
  TextInput,
440
595
  {
@@ -445,37 +600,25 @@ var ThemedTextInput = React.forwardRef(function ThemedTextInput2({ hasError = fa
445
600
  ...hoverBind,
446
601
  onFocus: focusBind.onFocus,
447
602
  onBlur: focusBind.onBlur,
448
- style: [style, themedStyle]
603
+ style: [boxStyle, multilineStyle, style, themedStyle]
449
604
  }
450
605
  );
451
606
  });
452
- var INPUT_PADDING_V = 10;
453
- var INPUT_PADDING_H = 12;
454
- var INPUT_BORDER_RADIUS = 8;
455
- var INPUT_BORDER_WIDTH = 1;
456
- var styles5 = StyleSheet.create({
457
- input: {
458
- paddingVertical: INPUT_PADDING_V,
459
- paddingHorizontal: INPUT_PADDING_H,
460
- borderRadius: INPUT_BORDER_RADIUS,
461
- borderWidth: INPUT_BORDER_WIDTH
462
- }
463
- });
464
607
  var FormField = ({
465
608
  label,
466
609
  required = false,
467
610
  error,
468
611
  containerStyle,
469
612
  ...textInputProps
470
- }) => /* @__PURE__ */ jsx(Field, { containerStyle, error, label, required, children: ({ describedById, hasError }) => /* @__PURE__ */ jsx(
613
+ }) => /* @__PURE__ */ jsx(Field, { containerStyle, error, label, required, children: ({ describedById, hasError, controlId }) => /* @__PURE__ */ jsx(
471
614
  ThemedTextInput,
472
615
  {
473
616
  accessibilityHint: `Enter ${label}`,
474
617
  accessibilityLabel: label,
475
618
  describedById,
476
619
  hasError,
620
+ id: controlId,
477
621
  requiredField: required,
478
- style: styles5.input,
479
622
  testID: "form-field-input",
480
623
  ...textInputProps
481
624
  }
@@ -513,7 +656,7 @@ function withAlpha2(hex, alpha) {
513
656
  const isValid = !Number.isNaN(r) && !Number.isNaN(g) && !Number.isNaN(b);
514
657
  return isValid ? `rgba(${r}, ${g}, ${b}, ${alpha})` : hex;
515
658
  }
516
- var styles6 = StyleSheet.create({
659
+ var styles5 = StyleSheet.create({
517
660
  chipContainer: {
518
661
  flexDirection: "row",
519
662
  flexWrap: "wrap",
@@ -565,7 +708,7 @@ function OutlineChip({
565
708
  ariaPressed,
566
709
  onPress
567
710
  }) {
568
- const [hovered, setHovered] = React.useState(false);
711
+ const [hovered, setHovered] = React2.useState(false);
569
712
  const active = selected || hovered;
570
713
  const borderColor = active ? colors.primary : colors.border;
571
714
  const backgroundColor = selected ? colors.tint : colors.muted;
@@ -584,7 +727,7 @@ function OutlineChip({
584
727
  onHoverIn: () => setHovered(true),
585
728
  onHoverOut: () => setHovered(false),
586
729
  onPress,
587
- children: /* @__PURE__ */ jsx(View, { style: [styles6.chipOutline, { borderColor, backgroundColor }], children: /* @__PURE__ */ jsx(Text, { style: [styles6.chipText, styles6.chipTextOutline, { color: textColor }], children: option.label }) })
730
+ children: /* @__PURE__ */ jsx(View, { style: [styles5.chipOutline, { borderColor, backgroundColor }], children: /* @__PURE__ */ jsx(Text, { style: [styles5.chipText, styles5.chipTextOutline, { color: textColor }], children: option.label }) })
588
731
  }
589
732
  );
590
733
  }
@@ -612,15 +755,10 @@ function SolidChip({
612
755
  hitSlop: CHIP_HIT_SLOP,
613
756
  testID,
614
757
  onPress,
615
- children: /* @__PURE__ */ jsx(View, { style: [styles6.chip, { borderColor: colors.border, backgroundColor }], children: /* @__PURE__ */ jsx(Text, { style: [styles6.chipText, { color: textColor }], children: option.label }) })
758
+ children: /* @__PURE__ */ jsx(View, { style: [styles5.chip, { borderColor: colors.border, backgroundColor }], children: /* @__PURE__ */ jsx(Text, { style: [styles5.chipText, { color: textColor }], children: option.label }) })
616
759
  }
617
760
  );
618
761
  }
619
- var IS_WEB3 = Platform.OS === "web";
620
- function groupA11yProps(describedById, hasError) {
621
- if (!IS_WEB3) return {};
622
- return { "aria-invalid": hasError ? true : void 0, "aria-describedby": describedById };
623
- }
624
762
  var ChipSelector = ({
625
763
  label,
626
764
  options,
@@ -639,7 +777,7 @@ var ChipSelector = ({
639
777
  }) => {
640
778
  const { theme } = useUi();
641
779
  const { colors, palette } = theme;
642
- const chipColors = React.useMemo(() => {
780
+ const chipColors = React2.useMemo(() => {
643
781
  const primary = palette.primary["500"];
644
782
  return {
645
783
  border: colors.border,
@@ -682,12 +820,12 @@ var ChipSelector = ({
682
820
  required,
683
821
  spacing,
684
822
  testID,
685
- children: ({ describedById, hasError }) => /* @__PURE__ */ jsx(View, { style: styles6.chipContainer, ...groupA11yProps(describedById, hasError), children: options.map(renderChip) })
823
+ children: ({ describedById, hasError }) => /* @__PURE__ */ jsx(View, { style: styles5.chipContainer, ...webFieldA11y({ describedById, hasError }), children: options.map(renderChip) })
686
824
  }
687
825
  );
688
826
  };
689
827
  var DESCRIPTION_FONT_SIZE2 = 12;
690
- var styles7 = StyleSheet.create({
828
+ var styles6 = StyleSheet.create({
691
829
  container: {
692
830
  flexDirection: "row",
693
831
  justifyContent: "space-between",
@@ -718,7 +856,7 @@ var FormSwitch = ({
718
856
  const { theme } = useUi();
719
857
  const { colors } = theme;
720
858
  const hasDescription = typeof description === "string" && description !== "";
721
- const themeStyles = React.useMemo(() => {
859
+ const themeStyles = React2.useMemo(() => {
722
860
  const labelStyle = { color: colors.text };
723
861
  const descriptionStyle = { color: colors.textSecondary };
724
862
  return { label: labelStyle, description: descriptionStyle };
@@ -727,13 +865,13 @@ var FormSwitch = ({
727
865
  View,
728
866
  {
729
867
  style: [
730
- styles7.container,
868
+ styles6.container,
731
869
  containerStyle
732
870
  ],
733
871
  children: [
734
- /* @__PURE__ */ jsxs(View, { style: styles7.textColumn, children: [
735
- /* @__PURE__ */ jsx(Text, { style: [styles7.label, themeStyles.label], children: label }),
736
- hasDescription ? /* @__PURE__ */ jsx(Text, { style: [styles7.description, themeStyles.description], children: description }) : null
872
+ /* @__PURE__ */ jsxs(View, { style: styles6.textColumn, children: [
873
+ /* @__PURE__ */ jsx(Text, { style: [styles6.label, themeStyles.label], children: label }),
874
+ hasDescription ? /* @__PURE__ */ jsx(Text, { style: [styles6.description, themeStyles.description], children: description }) : null
737
875
  ] }),
738
876
  /* @__PURE__ */ jsx(
739
877
  Switch,
@@ -758,7 +896,7 @@ var BOX_SIZE = 20;
758
896
  var BOX_RADIUS = 5;
759
897
  var CHECK_FONT_SIZE = 14;
760
898
  var DISABLED_OPACITY = 0.5;
761
- var styles8 = StyleSheet.create({
899
+ var styles7 = StyleSheet.create({
762
900
  container: {
763
901
  flexDirection: "row",
764
902
  alignItems: "flex-start",
@@ -809,7 +947,7 @@ var FormCheckbox = ({
809
947
  const brand = palette.primary["500"];
810
948
  const hintText = typeof hint === "string" && hint !== "" ? hint : description;
811
949
  const hasHint = typeof hintText === "string" && hintText !== "";
812
- const themeStyles = React.useMemo(() => {
950
+ const themeStyles = React2.useMemo(() => {
813
951
  const boxOn = { backgroundColor: brand, borderColor: brand };
814
952
  const boxOff = { backgroundColor: "transparent", borderColor: colors.border };
815
953
  const labelStyle = { color: colors.text };
@@ -840,101 +978,26 @@ var FormCheckbox = ({
840
978
  disabled,
841
979
  onPress: toggle,
842
980
  role: "checkbox",
843
- style: [styles8.container, containerStyle, disabled ? { opacity: DISABLED_OPACITY } : null],
981
+ style: [styles7.container, containerStyle, disabled ? { opacity: DISABLED_OPACITY } : null],
844
982
  testID,
845
983
  ...keyboardProps,
846
984
  children: [
847
- /* @__PURE__ */ jsx(View, { style: [styles8.box, value ? themeStyles.boxOn : themeStyles.boxOff], children: value ? /* @__PURE__ */ jsx(Text, { style: [styles8.check, { color: colors.surface }], accessibilityElementsHidden: true, children: "\u2713" }) : null }),
848
- /* @__PURE__ */ jsxs(View, { style: styles8.textColumn, children: [
849
- /* @__PURE__ */ jsx(Text, { style: [styles8.label, themeStyles.label], children: label }),
850
- hasHint ? /* @__PURE__ */ jsx(Text, { style: [styles8.hint, themeStyles.hint], children: hintText }) : null
985
+ /* @__PURE__ */ jsx(View, { style: [styles7.box, value ? themeStyles.boxOn : themeStyles.boxOff], children: value ? /* @__PURE__ */ jsx(Text, { style: [styles7.check, { color: colors.surface }], accessibilityElementsHidden: true, children: "\u2713" }) : null }),
986
+ /* @__PURE__ */ jsxs(View, { style: styles7.textColumn, children: [
987
+ /* @__PURE__ */ jsx(Text, { style: [styles7.label, themeStyles.label], children: label }),
988
+ hasHint ? /* @__PURE__ */ jsx(Text, { style: [styles7.hint, themeStyles.hint], children: hintText }) : null
851
989
  ] })
852
990
  ]
853
991
  }
854
992
  );
855
993
  };
856
- var FIELD_GAP = 4;
857
- var LABEL_FONT = 11;
858
- var LABEL_LETTER_SPACING = 0.4;
859
- var INPUT_RADIUS = 8;
860
- var INPUT_PAD_H = 12;
861
- var INPUT_PAD_V = 10;
862
- var INPUT_FONT = 14;
863
- var INPUT_BORDER = 1;
864
- var DATE_ROW_GAP = 8;
865
- var CHEVRON_FONT = 10;
866
- var MENU_TOP_GAP = 4;
867
- var MENU_RADIUS = 8;
868
- var MENU_PAD_V = 4;
869
- var MENU_OPT_PAD_H = 12;
870
- var MENU_OPT_PAD_V = 8;
871
- var MENU_MIN_WIDTH = 160;
872
- var MENU_MAX_HEIGHT = 260;
873
- var MENU_Z = 1e3;
874
- var MENU_ELEVATION = 8;
875
- var MENU_BOX_SHADOW = "0px 2px 8px rgba(0, 0, 0, 0.15)";
876
- var OPTION_FONT = 14;
877
- var ERROR_FONT = 12;
878
- var ERROR_GAP = 3;
879
- var controlStyles = StyleSheet.create({
880
- /** The shared bordered box every dense text control renders in. */
881
- input: {
882
- borderWidth: INPUT_BORDER,
883
- borderRadius: INPUT_RADIUS,
884
- paddingHorizontal: INPUT_PAD_H,
885
- paddingVertical: INPUT_PAD_V,
886
- fontSize: INPUT_FONT
887
- },
888
- /** The select trigger: a bordered field box matching the text inputs + a chevron. */
889
- selectTrigger: {
890
- flexDirection: "row",
891
- alignItems: "center",
892
- justifyContent: "space-between",
893
- gap: DATE_ROW_GAP,
894
- borderWidth: INPUT_BORDER,
895
- borderRadius: INPUT_RADIUS,
896
- paddingHorizontal: INPUT_PAD_H,
897
- paddingVertical: INPUT_PAD_V
898
- },
899
- selectTriggerText: { fontSize: INPUT_FONT, flexShrink: 1 },
900
- chevron: { fontSize: CHEVRON_FONT },
901
- dateRow: { flexDirection: "row", gap: DATE_ROW_GAP },
902
- dateCol: { gap: FIELD_GAP, flex: 1 },
903
- /**
904
- * The micro-caption above a sub-control (a date range's From/To) and beside a switch. Note it
905
- * is deliberately NOT the `control` label voice: it carries no `700`/uppercase, because it
906
- * names a HALF of a control that already has a `control`-voice label above it. Uppercasing it
907
- * too would make one field shout twice.
908
- */
909
- subLabel: { fontSize: LABEL_FONT, letterSpacing: LABEL_LETTER_SPACING },
910
- /** Positioning context for a floating `AnchoredMenu` (`position:absolute; top:100%`). */
911
- anchor: { position: "relative" },
912
- menu: {
913
- position: "absolute",
914
- top: "100%",
915
- left: 0,
916
- marginTop: MENU_TOP_GAP,
917
- minWidth: MENU_MIN_WIDTH,
918
- maxHeight: MENU_MAX_HEIGHT,
919
- borderWidth: INPUT_BORDER,
920
- borderRadius: MENU_RADIUS,
921
- paddingVertical: MENU_PAD_V,
922
- zIndex: MENU_Z,
923
- boxShadow: MENU_BOX_SHADOW,
924
- elevation: MENU_ELEVATION,
925
- overflow: "hidden"
926
- },
927
- option: { paddingHorizontal: MENU_OPT_PAD_H, paddingVertical: MENU_OPT_PAD_V },
928
- optionText: { fontSize: OPTION_FONT },
929
- error: { fontSize: ERROR_FONT, marginTop: ERROR_GAP }
930
- });
931
- var IS_WEB4 = typeof document !== "undefined";
994
+ var IS_WEB5 = typeof document !== "undefined";
932
995
  var ESCAPE_KEY = "Escape";
933
996
  var ACTIVE_WEIGHT = "700";
934
997
  var IDLE_WEIGHT = "400";
935
998
  function useDismiss(anchorRef, menuRef, onDismiss) {
936
999
  useEffect(() => {
937
- if (!IS_WEB4) return void 0;
1000
+ if (!IS_WEB5) return void 0;
938
1001
  const onMouseDown = (event) => {
939
1002
  const anchorNode = anchorRef.current;
940
1003
  const menuNode = menuRef.current;
@@ -964,7 +1027,7 @@ function AnchoredMenu({
964
1027
  testID,
965
1028
  anchorRef
966
1029
  }) {
967
- const menuRef = React.useRef(null);
1030
+ const menuRef = React2.useRef(null);
968
1031
  useDismiss(anchorRef, menuRef, onDismiss);
969
1032
  return /* @__PURE__ */ jsx(
970
1033
  View,
@@ -1003,6 +1066,7 @@ function SelectControl({
1003
1066
  placeholder = "",
1004
1067
  accessibilityLabel,
1005
1068
  accessibilityHint,
1069
+ labelledById,
1006
1070
  optionHint = "",
1007
1071
  style,
1008
1072
  testID
@@ -1037,6 +1101,7 @@ function SelectControl({
1037
1101
  accessibilityHint,
1038
1102
  accessibilityState: { expanded: isOpen },
1039
1103
  "aria-expanded": isOpen,
1104
+ "aria-labelledby": labelledById,
1040
1105
  onPress: toggle,
1041
1106
  style: [controlStyles.selectTrigger, { borderColor: colors.border, backgroundColor: colors.surface }],
1042
1107
  testID: `${testID}-trigger`,