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