@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/CHANGELOG.md +32 -0
- package/dist/index.d.mts +67 -2
- package/dist/index.d.ts +67 -2
- package/dist/index.js +231 -166
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +207 -142
- package/dist/index.mjs.map +1 -1
- package/package.json +110 -107
package/dist/index.js
CHANGED
|
@@ -1,25 +1,63 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
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
|
|
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
|
-
|
|
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 =
|
|
100
|
-
var GapOwnedContext =
|
|
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
|
|
142
|
+
return React2__default.default.useContext(FormGridContext);
|
|
105
143
|
}
|
|
106
144
|
function useIsGapOwned() {
|
|
107
|
-
return
|
|
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] =
|
|
163
|
+
const [ids] = React2__default.default.useState(() => {
|
|
126
164
|
fieldSeq += 1;
|
|
127
165
|
const seq = String(fieldSeq);
|
|
128
|
-
return {
|
|
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 =
|
|
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
|
|
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 =
|
|
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] =
|
|
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
|
|
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 (!
|
|
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] =
|
|
369
|
-
const [isHovered, setIsHovered] =
|
|
370
|
-
const handleFocus =
|
|
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 =
|
|
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 =
|
|
385
|
-
const handleHoverOut =
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
418
|
-
const hoverBind =
|
|
419
|
-
() =>
|
|
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,19 +558,44 @@ function useThemedInput(options = {}) {
|
|
|
428
558
|
hoverBind
|
|
429
559
|
};
|
|
430
560
|
}
|
|
431
|
-
var
|
|
432
|
-
|
|
561
|
+
var IS_WEB4 = reactNative.Platform.OS === "web";
|
|
562
|
+
function webFieldA11y({ describedById, hasError = false, required = false }) {
|
|
563
|
+
if (!IS_WEB4) {
|
|
564
|
+
return {};
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
"aria-invalid": hasError ? true : void 0,
|
|
568
|
+
"aria-describedby": describedById,
|
|
569
|
+
"aria-required": required ? true : void 0
|
|
570
|
+
};
|
|
571
|
+
}
|
|
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) {
|
|
433
590
|
const {
|
|
434
591
|
style: themedStyle,
|
|
435
592
|
placeholderTextColor: themedPlaceholder,
|
|
436
593
|
focusBind,
|
|
437
594
|
hoverBind
|
|
438
595
|
} = useThemedInput({ hasError, onFocus, onBlur });
|
|
439
|
-
const webA11y =
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
"aria-required": requiredField ? true : void 0
|
|
443
|
-
} : {};
|
|
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;
|
|
444
599
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
445
600
|
reactNative.TextInput,
|
|
446
601
|
{
|
|
@@ -451,37 +606,25 @@ var ThemedTextInput = React__default.default.forwardRef(function ThemedTextInput
|
|
|
451
606
|
...hoverBind,
|
|
452
607
|
onFocus: focusBind.onFocus,
|
|
453
608
|
onBlur: focusBind.onBlur,
|
|
454
|
-
style: [style, themedStyle]
|
|
609
|
+
style: [boxStyle, multilineStyle, style, themedStyle]
|
|
455
610
|
}
|
|
456
611
|
);
|
|
457
612
|
});
|
|
458
|
-
var INPUT_PADDING_V = 10;
|
|
459
|
-
var INPUT_PADDING_H = 12;
|
|
460
|
-
var INPUT_BORDER_RADIUS = 8;
|
|
461
|
-
var INPUT_BORDER_WIDTH = 1;
|
|
462
|
-
var styles5 = reactNative.StyleSheet.create({
|
|
463
|
-
input: {
|
|
464
|
-
paddingVertical: INPUT_PADDING_V,
|
|
465
|
-
paddingHorizontal: INPUT_PADDING_H,
|
|
466
|
-
borderRadius: INPUT_BORDER_RADIUS,
|
|
467
|
-
borderWidth: INPUT_BORDER_WIDTH
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
613
|
var FormField = ({
|
|
471
614
|
label,
|
|
472
615
|
required = false,
|
|
473
616
|
error,
|
|
474
617
|
containerStyle,
|
|
475
618
|
...textInputProps
|
|
476
|
-
}) => /* @__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(
|
|
477
620
|
ThemedTextInput,
|
|
478
621
|
{
|
|
479
622
|
accessibilityHint: `Enter ${label}`,
|
|
480
623
|
accessibilityLabel: label,
|
|
481
624
|
describedById,
|
|
482
625
|
hasError,
|
|
626
|
+
id: controlId,
|
|
483
627
|
requiredField: required,
|
|
484
|
-
style: styles5.input,
|
|
485
628
|
testID: "form-field-input",
|
|
486
629
|
...textInputProps
|
|
487
630
|
}
|
|
@@ -519,7 +662,7 @@ function withAlpha2(hex, alpha) {
|
|
|
519
662
|
const isValid = !Number.isNaN(r) && !Number.isNaN(g) && !Number.isNaN(b);
|
|
520
663
|
return isValid ? `rgba(${r}, ${g}, ${b}, ${alpha})` : hex;
|
|
521
664
|
}
|
|
522
|
-
var
|
|
665
|
+
var styles5 = reactNative.StyleSheet.create({
|
|
523
666
|
chipContainer: {
|
|
524
667
|
flexDirection: "row",
|
|
525
668
|
flexWrap: "wrap",
|
|
@@ -571,7 +714,7 @@ function OutlineChip({
|
|
|
571
714
|
ariaPressed,
|
|
572
715
|
onPress
|
|
573
716
|
}) {
|
|
574
|
-
const [hovered, setHovered] =
|
|
717
|
+
const [hovered, setHovered] = React2__default.default.useState(false);
|
|
575
718
|
const active = selected || hovered;
|
|
576
719
|
const borderColor = active ? colors.primary : colors.border;
|
|
577
720
|
const backgroundColor = selected ? colors.tint : colors.muted;
|
|
@@ -590,7 +733,7 @@ function OutlineChip({
|
|
|
590
733
|
onHoverIn: () => setHovered(true),
|
|
591
734
|
onHoverOut: () => setHovered(false),
|
|
592
735
|
onPress,
|
|
593
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
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 }) })
|
|
594
737
|
}
|
|
595
738
|
);
|
|
596
739
|
}
|
|
@@ -618,15 +761,10 @@ function SolidChip({
|
|
|
618
761
|
hitSlop: CHIP_HIT_SLOP,
|
|
619
762
|
testID,
|
|
620
763
|
onPress,
|
|
621
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
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 }) })
|
|
622
765
|
}
|
|
623
766
|
);
|
|
624
767
|
}
|
|
625
|
-
var IS_WEB3 = reactNative.Platform.OS === "web";
|
|
626
|
-
function groupA11yProps(describedById, hasError) {
|
|
627
|
-
if (!IS_WEB3) return {};
|
|
628
|
-
return { "aria-invalid": hasError ? true : void 0, "aria-describedby": describedById };
|
|
629
|
-
}
|
|
630
768
|
var ChipSelector = ({
|
|
631
769
|
label,
|
|
632
770
|
options,
|
|
@@ -645,7 +783,7 @@ var ChipSelector = ({
|
|
|
645
783
|
}) => {
|
|
646
784
|
const { theme } = uiFeedback.useUi();
|
|
647
785
|
const { colors, palette } = theme;
|
|
648
|
-
const chipColors =
|
|
786
|
+
const chipColors = React2__default.default.useMemo(() => {
|
|
649
787
|
const primary = palette.primary["500"];
|
|
650
788
|
return {
|
|
651
789
|
border: colors.border,
|
|
@@ -688,12 +826,12 @@ var ChipSelector = ({
|
|
|
688
826
|
required,
|
|
689
827
|
spacing,
|
|
690
828
|
testID,
|
|
691
|
-
children: ({ describedById, hasError }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style:
|
|
829
|
+
children: ({ describedById, hasError }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles5.chipContainer, ...webFieldA11y({ describedById, hasError }), children: options.map(renderChip) })
|
|
692
830
|
}
|
|
693
831
|
);
|
|
694
832
|
};
|
|
695
833
|
var DESCRIPTION_FONT_SIZE2 = 12;
|
|
696
|
-
var
|
|
834
|
+
var styles6 = reactNative.StyleSheet.create({
|
|
697
835
|
container: {
|
|
698
836
|
flexDirection: "row",
|
|
699
837
|
justifyContent: "space-between",
|
|
@@ -724,7 +862,7 @@ var FormSwitch = ({
|
|
|
724
862
|
const { theme } = uiFeedback.useUi();
|
|
725
863
|
const { colors } = theme;
|
|
726
864
|
const hasDescription = typeof description === "string" && description !== "";
|
|
727
|
-
const themeStyles =
|
|
865
|
+
const themeStyles = React2__default.default.useMemo(() => {
|
|
728
866
|
const labelStyle = { color: colors.text };
|
|
729
867
|
const descriptionStyle = { color: colors.textSecondary };
|
|
730
868
|
return { label: labelStyle, description: descriptionStyle };
|
|
@@ -733,13 +871,13 @@ var FormSwitch = ({
|
|
|
733
871
|
reactNative.View,
|
|
734
872
|
{
|
|
735
873
|
style: [
|
|
736
|
-
|
|
874
|
+
styles6.container,
|
|
737
875
|
containerStyle
|
|
738
876
|
],
|
|
739
877
|
children: [
|
|
740
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
741
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
742
|
-
hasDescription ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
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
|
|
743
881
|
] }),
|
|
744
882
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
745
883
|
reactNative.Switch,
|
|
@@ -764,7 +902,7 @@ var BOX_SIZE = 20;
|
|
|
764
902
|
var BOX_RADIUS = 5;
|
|
765
903
|
var CHECK_FONT_SIZE = 14;
|
|
766
904
|
var DISABLED_OPACITY = 0.5;
|
|
767
|
-
var
|
|
905
|
+
var styles7 = reactNative.StyleSheet.create({
|
|
768
906
|
container: {
|
|
769
907
|
flexDirection: "row",
|
|
770
908
|
alignItems: "flex-start",
|
|
@@ -815,7 +953,7 @@ var FormCheckbox = ({
|
|
|
815
953
|
const brand = palette.primary["500"];
|
|
816
954
|
const hintText = typeof hint === "string" && hint !== "" ? hint : description;
|
|
817
955
|
const hasHint = typeof hintText === "string" && hintText !== "";
|
|
818
|
-
const themeStyles =
|
|
956
|
+
const themeStyles = React2__default.default.useMemo(() => {
|
|
819
957
|
const boxOn = { backgroundColor: brand, borderColor: brand };
|
|
820
958
|
const boxOff = { backgroundColor: "transparent", borderColor: colors.border };
|
|
821
959
|
const labelStyle = { color: colors.text };
|
|
@@ -846,101 +984,26 @@ var FormCheckbox = ({
|
|
|
846
984
|
disabled,
|
|
847
985
|
onPress: toggle,
|
|
848
986
|
role: "checkbox",
|
|
849
|
-
style: [
|
|
987
|
+
style: [styles7.container, containerStyle, disabled ? { opacity: DISABLED_OPACITY } : null],
|
|
850
988
|
testID,
|
|
851
989
|
...keyboardProps,
|
|
852
990
|
children: [
|
|
853
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
854
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
855
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
856
|
-
hasHint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
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
|
|
857
995
|
] })
|
|
858
996
|
]
|
|
859
997
|
}
|
|
860
998
|
);
|
|
861
999
|
};
|
|
862
|
-
var
|
|
863
|
-
var LABEL_FONT = 11;
|
|
864
|
-
var LABEL_LETTER_SPACING = 0.4;
|
|
865
|
-
var INPUT_RADIUS = 8;
|
|
866
|
-
var INPUT_PAD_H = 12;
|
|
867
|
-
var INPUT_PAD_V = 10;
|
|
868
|
-
var INPUT_FONT = 14;
|
|
869
|
-
var INPUT_BORDER = 1;
|
|
870
|
-
var DATE_ROW_GAP = 8;
|
|
871
|
-
var CHEVRON_FONT = 10;
|
|
872
|
-
var MENU_TOP_GAP = 4;
|
|
873
|
-
var MENU_RADIUS = 8;
|
|
874
|
-
var MENU_PAD_V = 4;
|
|
875
|
-
var MENU_OPT_PAD_H = 12;
|
|
876
|
-
var MENU_OPT_PAD_V = 8;
|
|
877
|
-
var MENU_MIN_WIDTH = 160;
|
|
878
|
-
var MENU_MAX_HEIGHT = 260;
|
|
879
|
-
var MENU_Z = 1e3;
|
|
880
|
-
var MENU_ELEVATION = 8;
|
|
881
|
-
var MENU_BOX_SHADOW = "0px 2px 8px rgba(0, 0, 0, 0.15)";
|
|
882
|
-
var OPTION_FONT = 14;
|
|
883
|
-
var ERROR_FONT = 12;
|
|
884
|
-
var ERROR_GAP = 3;
|
|
885
|
-
var controlStyles = reactNative.StyleSheet.create({
|
|
886
|
-
/** The shared bordered box every dense text control renders in. */
|
|
887
|
-
input: {
|
|
888
|
-
borderWidth: INPUT_BORDER,
|
|
889
|
-
borderRadius: INPUT_RADIUS,
|
|
890
|
-
paddingHorizontal: INPUT_PAD_H,
|
|
891
|
-
paddingVertical: INPUT_PAD_V,
|
|
892
|
-
fontSize: INPUT_FONT
|
|
893
|
-
},
|
|
894
|
-
/** The select trigger: a bordered field box matching the text inputs + a chevron. */
|
|
895
|
-
selectTrigger: {
|
|
896
|
-
flexDirection: "row",
|
|
897
|
-
alignItems: "center",
|
|
898
|
-
justifyContent: "space-between",
|
|
899
|
-
gap: DATE_ROW_GAP,
|
|
900
|
-
borderWidth: INPUT_BORDER,
|
|
901
|
-
borderRadius: INPUT_RADIUS,
|
|
902
|
-
paddingHorizontal: INPUT_PAD_H,
|
|
903
|
-
paddingVertical: INPUT_PAD_V
|
|
904
|
-
},
|
|
905
|
-
selectTriggerText: { fontSize: INPUT_FONT, flexShrink: 1 },
|
|
906
|
-
chevron: { fontSize: CHEVRON_FONT },
|
|
907
|
-
dateRow: { flexDirection: "row", gap: DATE_ROW_GAP },
|
|
908
|
-
dateCol: { gap: FIELD_GAP, flex: 1 },
|
|
909
|
-
/**
|
|
910
|
-
* The micro-caption above a sub-control (a date range's From/To) and beside a switch. Note it
|
|
911
|
-
* is deliberately NOT the `control` label voice: it carries no `700`/uppercase, because it
|
|
912
|
-
* names a HALF of a control that already has a `control`-voice label above it. Uppercasing it
|
|
913
|
-
* too would make one field shout twice.
|
|
914
|
-
*/
|
|
915
|
-
subLabel: { fontSize: LABEL_FONT, letterSpacing: LABEL_LETTER_SPACING },
|
|
916
|
-
/** Positioning context for a floating `AnchoredMenu` (`position:absolute; top:100%`). */
|
|
917
|
-
anchor: { position: "relative" },
|
|
918
|
-
menu: {
|
|
919
|
-
position: "absolute",
|
|
920
|
-
top: "100%",
|
|
921
|
-
left: 0,
|
|
922
|
-
marginTop: MENU_TOP_GAP,
|
|
923
|
-
minWidth: MENU_MIN_WIDTH,
|
|
924
|
-
maxHeight: MENU_MAX_HEIGHT,
|
|
925
|
-
borderWidth: INPUT_BORDER,
|
|
926
|
-
borderRadius: MENU_RADIUS,
|
|
927
|
-
paddingVertical: MENU_PAD_V,
|
|
928
|
-
zIndex: MENU_Z,
|
|
929
|
-
boxShadow: MENU_BOX_SHADOW,
|
|
930
|
-
elevation: MENU_ELEVATION,
|
|
931
|
-
overflow: "hidden"
|
|
932
|
-
},
|
|
933
|
-
option: { paddingHorizontal: MENU_OPT_PAD_H, paddingVertical: MENU_OPT_PAD_V },
|
|
934
|
-
optionText: { fontSize: OPTION_FONT },
|
|
935
|
-
error: { fontSize: ERROR_FONT, marginTop: ERROR_GAP }
|
|
936
|
-
});
|
|
937
|
-
var IS_WEB4 = typeof document !== "undefined";
|
|
1000
|
+
var IS_WEB5 = typeof document !== "undefined";
|
|
938
1001
|
var ESCAPE_KEY = "Escape";
|
|
939
1002
|
var ACTIVE_WEIGHT = "700";
|
|
940
1003
|
var IDLE_WEIGHT = "400";
|
|
941
1004
|
function useDismiss(anchorRef, menuRef, onDismiss) {
|
|
942
|
-
|
|
943
|
-
if (!
|
|
1005
|
+
React2.useEffect(() => {
|
|
1006
|
+
if (!IS_WEB5) return void 0;
|
|
944
1007
|
const onMouseDown = (event) => {
|
|
945
1008
|
const anchorNode = anchorRef.current;
|
|
946
1009
|
const menuNode = menuRef.current;
|
|
@@ -970,7 +1033,7 @@ function AnchoredMenu({
|
|
|
970
1033
|
testID,
|
|
971
1034
|
anchorRef
|
|
972
1035
|
}) {
|
|
973
|
-
const menuRef =
|
|
1036
|
+
const menuRef = React2__default.default.useRef(null);
|
|
974
1037
|
useDismiss(anchorRef, menuRef, onDismiss);
|
|
975
1038
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
976
1039
|
reactNative.View,
|
|
@@ -1009,25 +1072,26 @@ function SelectControl({
|
|
|
1009
1072
|
placeholder = "",
|
|
1010
1073
|
accessibilityLabel,
|
|
1011
1074
|
accessibilityHint,
|
|
1075
|
+
labelledById,
|
|
1012
1076
|
optionHint = "",
|
|
1013
1077
|
style,
|
|
1014
1078
|
testID
|
|
1015
1079
|
}) {
|
|
1016
1080
|
const { theme } = uiFeedback.useUi();
|
|
1017
1081
|
const { colors, palette } = theme;
|
|
1018
|
-
const anchorRef =
|
|
1019
|
-
const [isOpen, setIsOpen] =
|
|
1020
|
-
const selectedLabel =
|
|
1082
|
+
const anchorRef = React2.useRef(null);
|
|
1083
|
+
const [isOpen, setIsOpen] = React2.useState(false);
|
|
1084
|
+
const selectedLabel = React2.useMemo(() => {
|
|
1021
1085
|
const found = options.find((o) => o.value === value);
|
|
1022
1086
|
return found?.label ?? placeholder;
|
|
1023
1087
|
}, [options, placeholder, value]);
|
|
1024
|
-
const close =
|
|
1088
|
+
const close = React2.useCallback(() => {
|
|
1025
1089
|
setIsOpen(false);
|
|
1026
1090
|
}, []);
|
|
1027
|
-
const toggle =
|
|
1091
|
+
const toggle = React2.useCallback(() => {
|
|
1028
1092
|
setIsOpen((prev) => !prev);
|
|
1029
1093
|
}, []);
|
|
1030
|
-
const select =
|
|
1094
|
+
const select = React2.useCallback(
|
|
1031
1095
|
(next) => {
|
|
1032
1096
|
onChange(next);
|
|
1033
1097
|
setIsOpen(false);
|
|
@@ -1043,6 +1107,7 @@ function SelectControl({
|
|
|
1043
1107
|
accessibilityHint,
|
|
1044
1108
|
accessibilityState: { expanded: isOpen },
|
|
1045
1109
|
"aria-expanded": isOpen,
|
|
1110
|
+
"aria-labelledby": labelledById,
|
|
1046
1111
|
onPress: toggle,
|
|
1047
1112
|
style: [controlStyles.selectTrigger, { borderColor: colors.border, backgroundColor: colors.surface }],
|
|
1048
1113
|
testID: `${testID}-trigger`,
|
|
@@ -1119,22 +1184,22 @@ function TypeaheadControl({
|
|
|
1119
1184
|
}) {
|
|
1120
1185
|
const { theme } = uiFeedback.useUi();
|
|
1121
1186
|
const { colors, palette, semantic } = theme;
|
|
1122
|
-
const anchorRef =
|
|
1123
|
-
const [isOpen, setIsOpen] =
|
|
1124
|
-
const exactPick =
|
|
1125
|
-
const suggestions =
|
|
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(
|
|
1126
1191
|
() => exactPick ? [] : suggestOptions(options, value, minChars, maxSuggestions),
|
|
1127
1192
|
[exactPick, options, value, minChars, maxSuggestions]
|
|
1128
1193
|
);
|
|
1129
1194
|
const showMenu = isOpen && suggestions.length > 0;
|
|
1130
|
-
const handleChange =
|
|
1195
|
+
const handleChange = React2.useCallback(
|
|
1131
1196
|
(next) => {
|
|
1132
1197
|
onChange(next);
|
|
1133
1198
|
setIsOpen(true);
|
|
1134
1199
|
},
|
|
1135
1200
|
[onChange]
|
|
1136
1201
|
);
|
|
1137
|
-
const pick =
|
|
1202
|
+
const pick = React2.useCallback(
|
|
1138
1203
|
(optionValue) => {
|
|
1139
1204
|
const opt = options.find((o) => o.value === optionValue);
|
|
1140
1205
|
onChange(opt?.label ?? optionValue);
|
|
@@ -1142,10 +1207,10 @@ function TypeaheadControl({
|
|
|
1142
1207
|
},
|
|
1143
1208
|
[options, onChange]
|
|
1144
1209
|
);
|
|
1145
|
-
const handleBlur =
|
|
1210
|
+
const handleBlur = React2.useCallback(() => {
|
|
1146
1211
|
setTimeout(() => setIsOpen(false), CLOSE_DELAY_MS);
|
|
1147
1212
|
}, []);
|
|
1148
|
-
const handleSubmit =
|
|
1213
|
+
const handleSubmit = React2.useCallback(() => {
|
|
1149
1214
|
setIsOpen(false);
|
|
1150
1215
|
onSubmit?.();
|
|
1151
1216
|
}, [onSubmit]);
|