@dloizides/ui-forms 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -0
- package/README.md +42 -0
- package/dist/index.d.mts +436 -9
- package/dist/index.d.ts +436 -9
- package/dist/index.js +625 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +616 -54
- package/dist/index.mjs.map +1 -1
- package/package.json +110 -108
package/dist/index.js
CHANGED
|
@@ -4,48 +4,119 @@ var React = 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
|
+
var uiButtons = require('@dloizides/ui-buttons');
|
|
7
8
|
|
|
8
9
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
10
|
|
|
10
11
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
11
12
|
|
|
12
13
|
// src/Field/Field.tsx
|
|
14
|
+
function FieldLabel({
|
|
15
|
+
label,
|
|
16
|
+
marker,
|
|
17
|
+
optionalLabel,
|
|
18
|
+
style,
|
|
19
|
+
requiredMarkStyle,
|
|
20
|
+
optionalMarkStyle
|
|
21
|
+
}) {
|
|
22
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style, children: [
|
|
23
|
+
label,
|
|
24
|
+
" ",
|
|
25
|
+
marker === "required" ? (
|
|
26
|
+
// The asterisk is decorative — the control's `aria-required` conveys "required" to
|
|
27
|
+
// assistive tech, so a screen reader says "required" instead of "star".
|
|
28
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { "aria-hidden": true, accessibilityElementsHidden: true, importantForAccessibility: "no", style: requiredMarkStyle, children: "*" })
|
|
29
|
+
) : null,
|
|
30
|
+
marker === "optional" && optionalLabel !== "" ? (
|
|
31
|
+
// Also decorative: "optional" is the ABSENCE of `aria-required`, which assistive tech
|
|
32
|
+
// already conveys. Rendering it to a screen reader would double-announce the same fact.
|
|
33
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { "aria-hidden": true, accessibilityElementsHidden: true, importantForAccessibility: "no", style: optionalMarkStyle, children: optionalLabel })
|
|
34
|
+
) : null
|
|
35
|
+
] });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/Field/fieldMarkers.ts
|
|
39
|
+
var DEFAULT_OPTIONAL_LABEL = "(optional)";
|
|
40
|
+
function resolveFieldMarker(required, optional) {
|
|
41
|
+
if (required) return "required";
|
|
42
|
+
if (optional) return "optional";
|
|
43
|
+
return "none";
|
|
44
|
+
}
|
|
45
|
+
var FIELD_FONT_SIZE = 13;
|
|
46
|
+
var FIELD_FONT_WEIGHT = "600";
|
|
47
|
+
var CONTROL_FONT_SIZE = 11;
|
|
48
|
+
var CONTROL_FONT_WEIGHT = "700";
|
|
49
|
+
var CONTROL_LETTER_SPACING = 0.4;
|
|
50
|
+
var labelVariantStyles = reactNative.StyleSheet.create({
|
|
51
|
+
field: {
|
|
52
|
+
fontSize: FIELD_FONT_SIZE,
|
|
53
|
+
fontWeight: FIELD_FONT_WEIGHT
|
|
54
|
+
},
|
|
55
|
+
control: {
|
|
56
|
+
fontSize: CONTROL_FONT_SIZE,
|
|
57
|
+
fontWeight: CONTROL_FONT_WEIGHT,
|
|
58
|
+
letterSpacing: CONTROL_LETTER_SPACING,
|
|
59
|
+
textTransform: "uppercase"
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
function resolveLabelVariantStyle(variant) {
|
|
63
|
+
return variant === "control" ? labelVariantStyles.control : labelVariantStyles.field;
|
|
64
|
+
}
|
|
13
65
|
var CONTAINER_MARGIN_BOTTOM = 16;
|
|
14
|
-
var LABEL_FONT_SIZE = 13;
|
|
15
|
-
var LABEL_FONT_WEIGHT = "600";
|
|
16
66
|
var LABEL_MARGIN_BOTTOM = 4;
|
|
17
67
|
var ERROR_FONT_SIZE = 12;
|
|
18
68
|
var ERROR_MARGIN_TOP = 4;
|
|
69
|
+
var HINT_FONT_SIZE = 12;
|
|
70
|
+
var HINT_MARGIN_TOP = 4;
|
|
71
|
+
var OPTIONAL_MARK_FONT_WEIGHT = "400";
|
|
19
72
|
var styles = reactNative.StyleSheet.create({
|
|
20
73
|
container: {
|
|
21
74
|
marginBottom: CONTAINER_MARGIN_BOTTOM
|
|
22
75
|
},
|
|
23
76
|
label: {
|
|
24
|
-
marginBottom: LABEL_MARGIN_BOTTOM
|
|
25
|
-
|
|
26
|
-
|
|
77
|
+
marginBottom: LABEL_MARGIN_BOTTOM
|
|
78
|
+
},
|
|
79
|
+
hintText: {
|
|
80
|
+
fontSize: HINT_FONT_SIZE,
|
|
81
|
+
marginTop: HINT_MARGIN_TOP
|
|
27
82
|
},
|
|
28
83
|
errorText: {
|
|
29
84
|
fontSize: ERROR_FONT_SIZE,
|
|
30
85
|
marginTop: ERROR_MARGIN_TOP
|
|
86
|
+
},
|
|
87
|
+
optionalMark: {
|
|
88
|
+
fontWeight: OPTIONAL_MARK_FONT_WEIGHT
|
|
31
89
|
}
|
|
32
90
|
});
|
|
33
91
|
var fieldSeq = 0;
|
|
34
|
-
function
|
|
35
|
-
const [
|
|
92
|
+
function useFieldIds() {
|
|
93
|
+
const [ids] = React__default.default.useState(() => {
|
|
36
94
|
fieldSeq += 1;
|
|
37
|
-
|
|
95
|
+
const seq = String(fieldSeq);
|
|
96
|
+
return { hintId: `field-hint-${seq}`, errorId: `field-error-${seq}` };
|
|
38
97
|
});
|
|
39
|
-
return
|
|
98
|
+
return ids;
|
|
99
|
+
}
|
|
100
|
+
function isPresent(value) {
|
|
101
|
+
return typeof value === "string" && value !== "";
|
|
40
102
|
}
|
|
41
103
|
function hasFieldError(error) {
|
|
42
|
-
return
|
|
104
|
+
return isPresent(error);
|
|
105
|
+
}
|
|
106
|
+
function joinDescribedBy(hintId, errorId) {
|
|
107
|
+
const ids = [hintId, errorId].filter((id) => isPresent(id));
|
|
108
|
+
return ids.length > 0 ? ids.join(" ") : void 0;
|
|
43
109
|
}
|
|
44
110
|
var Field = ({
|
|
45
111
|
label,
|
|
46
112
|
children,
|
|
47
113
|
required = false,
|
|
114
|
+
optional = false,
|
|
115
|
+
optionalLabel = DEFAULT_OPTIONAL_LABEL,
|
|
116
|
+
hint,
|
|
48
117
|
error,
|
|
118
|
+
labelVariant,
|
|
119
|
+
labelStyle,
|
|
49
120
|
containerStyle,
|
|
50
121
|
testID
|
|
51
122
|
}) => {
|
|
@@ -53,31 +124,89 @@ var Field = ({
|
|
|
53
124
|
const { colors, semantic } = theme;
|
|
54
125
|
const errorColor = semantic.error["500"];
|
|
55
126
|
const hasError = hasFieldError(error);
|
|
56
|
-
const
|
|
57
|
-
const
|
|
127
|
+
const hasHint = isPresent(hint);
|
|
128
|
+
const labelText = label ?? "";
|
|
129
|
+
const hasLabel = labelText !== "";
|
|
130
|
+
const marker = resolveFieldMarker(required, optional);
|
|
131
|
+
const { hintId, errorId } = useFieldIds();
|
|
58
132
|
const themeStyles = React__default.default.useMemo(
|
|
59
133
|
() => ({
|
|
60
134
|
label: { color: colors.textSecondary },
|
|
61
135
|
requiredMark: { color: errorColor },
|
|
136
|
+
// The optional marker is an aside, so it sits a step back from the label's own ink.
|
|
137
|
+
optionalMark: { color: colors.textSecondary },
|
|
138
|
+
hintText: { color: colors.textSecondary },
|
|
62
139
|
errorText: { color: errorColor }
|
|
63
140
|
}),
|
|
64
141
|
[colors.textSecondary, errorColor]
|
|
65
142
|
);
|
|
66
|
-
const
|
|
143
|
+
const describedById = joinDescribedBy(hasHint ? hintId : void 0, hasError ? errorId : void 0);
|
|
144
|
+
const control = typeof children === "function" ? children({ describedById, hasError }) : children;
|
|
67
145
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles.container, containerStyle], testID, children: [
|
|
68
|
-
hasLabel ? /* @__PURE__ */ jsxRuntime.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
146
|
+
hasLabel ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
147
|
+
FieldLabel,
|
|
148
|
+
{
|
|
149
|
+
label: labelText,
|
|
150
|
+
marker,
|
|
151
|
+
optionalLabel,
|
|
152
|
+
optionalMarkStyle: [styles.optionalMark, themeStyles.optionalMark],
|
|
153
|
+
requiredMarkStyle: themeStyles.requiredMark,
|
|
154
|
+
style: [styles.label, resolveLabelVariantStyle(labelVariant), themeStyles.label, labelStyle]
|
|
155
|
+
}
|
|
156
|
+
) : null,
|
|
77
157
|
control,
|
|
158
|
+
hasHint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { nativeID: hintId, style: [styles.hintText, themeStyles.hintText], children: hint }) : null,
|
|
78
159
|
hasError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { nativeID: errorId, role: "alert", style: [styles.errorText, themeStyles.errorText], children: error }) : null
|
|
79
160
|
] });
|
|
80
161
|
};
|
|
162
|
+
var ACTIONS_GAP = 12;
|
|
163
|
+
var styles2 = reactNative.StyleSheet.create({
|
|
164
|
+
container: {
|
|
165
|
+
flexDirection: "row",
|
|
166
|
+
justifyContent: "flex-end",
|
|
167
|
+
gap: ACTIONS_GAP
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
var FormActions = ({
|
|
171
|
+
onSave,
|
|
172
|
+
onCancel,
|
|
173
|
+
saveLabel,
|
|
174
|
+
cancelLabel = "",
|
|
175
|
+
saveHint,
|
|
176
|
+
cancelHint,
|
|
177
|
+
saving = false,
|
|
178
|
+
saveDisabled = false,
|
|
179
|
+
containerStyle,
|
|
180
|
+
saveTestID = "save-button",
|
|
181
|
+
cancelTestID = "cancel-button",
|
|
182
|
+
testID
|
|
183
|
+
}) => /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles2.container, containerStyle], testID, children: [
|
|
184
|
+
typeof onCancel === "function" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
185
|
+
uiButtons.Button,
|
|
186
|
+
{
|
|
187
|
+
accessibilityHint: cancelHint ?? cancelLabel,
|
|
188
|
+
accessibilityLabel: cancelLabel,
|
|
189
|
+
disabled: saving,
|
|
190
|
+
label: cancelLabel,
|
|
191
|
+
testID: cancelTestID,
|
|
192
|
+
variant: "outline",
|
|
193
|
+
onPress: onCancel
|
|
194
|
+
}
|
|
195
|
+
) : null,
|
|
196
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
197
|
+
uiButtons.Button,
|
|
198
|
+
{
|
|
199
|
+
accessibilityHint: saveHint ?? saveLabel,
|
|
200
|
+
accessibilityLabel: saveLabel,
|
|
201
|
+
disabled: saveDisabled,
|
|
202
|
+
label: saveLabel,
|
|
203
|
+
loading: saving,
|
|
204
|
+
testID: saveTestID,
|
|
205
|
+
variant: "primary",
|
|
206
|
+
onPress: onSave
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
] });
|
|
81
210
|
var IS_WEB = reactNative.Platform.OS === "web";
|
|
82
211
|
var TRANSITION_PROPERTY = "border-color, background-color, box-shadow";
|
|
83
212
|
var TRANSITION_DURATION = "0.14s";
|
|
@@ -137,7 +266,8 @@ function useThemedInput(options = {}) {
|
|
|
137
266
|
const backgroundColor = isFocused ? colors.surface : colors.surfaceElevated;
|
|
138
267
|
const base = { color: colors.text, borderColor, backgroundColor };
|
|
139
268
|
if (!IS_WEB) return base;
|
|
140
|
-
const
|
|
269
|
+
const ringColor = hasError ? errorColor : primary;
|
|
270
|
+
const ring = isFocused ? `0 0 0 ${FOCUS_RING_WIDTH}px ${withAlpha(ringColor, FOCUS_RING_ALPHA)}` : "none";
|
|
141
271
|
const webStyle = {
|
|
142
272
|
transitionProperty: TRANSITION_PROPERTY,
|
|
143
273
|
// Collapse the eased transition to instant when the user prefers reduced motion.
|
|
@@ -203,7 +333,7 @@ var INPUT_PADDING_V = 10;
|
|
|
203
333
|
var INPUT_PADDING_H = 12;
|
|
204
334
|
var INPUT_BORDER_RADIUS = 8;
|
|
205
335
|
var INPUT_BORDER_WIDTH = 1;
|
|
206
|
-
var
|
|
336
|
+
var styles3 = reactNative.StyleSheet.create({
|
|
207
337
|
input: {
|
|
208
338
|
paddingVertical: INPUT_PADDING_V,
|
|
209
339
|
paddingHorizontal: INPUT_PADDING_H,
|
|
@@ -225,7 +355,7 @@ var FormField = ({
|
|
|
225
355
|
describedById,
|
|
226
356
|
hasError,
|
|
227
357
|
requiredField: required,
|
|
228
|
-
style:
|
|
358
|
+
style: styles3.input,
|
|
229
359
|
testID: "form-field-input",
|
|
230
360
|
...textInputProps
|
|
231
361
|
}
|
|
@@ -246,6 +376,13 @@ var RGB_G_END = 4;
|
|
|
246
376
|
var RGB_B_END = 6;
|
|
247
377
|
var CHIP_HIT_SLOP = { top: 10, bottom: 10, left: 4, right: 4 };
|
|
248
378
|
var CHIP_GUTTER = 8;
|
|
379
|
+
var DEFAULT_CHIP_TESTID_PREFIX = "chip-selector-chip";
|
|
380
|
+
var ON_BRAND_SCALE = "onBrand";
|
|
381
|
+
var MAIN_STEP = "500";
|
|
382
|
+
function onBrandInk(palette) {
|
|
383
|
+
const scales = palette;
|
|
384
|
+
return scales[ON_BRAND_SCALE]?.[MAIN_STEP] ?? WHITE_COLOR;
|
|
385
|
+
}
|
|
249
386
|
function withAlpha2(hex, alpha) {
|
|
250
387
|
const normalized = hex.replace("#", "");
|
|
251
388
|
const isShort = normalized.length === HEX_SHORT_LENGTH2;
|
|
@@ -256,7 +393,7 @@ function withAlpha2(hex, alpha) {
|
|
|
256
393
|
const isValid = !Number.isNaN(r) && !Number.isNaN(g) && !Number.isNaN(b);
|
|
257
394
|
return isValid ? `rgba(${r}, ${g}, ${b}, ${alpha})` : hex;
|
|
258
395
|
}
|
|
259
|
-
var
|
|
396
|
+
var styles4 = reactNative.StyleSheet.create({
|
|
260
397
|
chipContainer: {
|
|
261
398
|
flexDirection: "row",
|
|
262
399
|
flexWrap: "wrap",
|
|
@@ -285,11 +422,23 @@ var styles3 = reactNative.StyleSheet.create({
|
|
|
285
422
|
chipText: {},
|
|
286
423
|
chipTextOutline: { fontSize: OUTLINE_FONT_SIZE }
|
|
287
424
|
});
|
|
425
|
+
function chipIdentity(option, selected, config) {
|
|
426
|
+
return {
|
|
427
|
+
testID: `${config.prefix}-${String(option.value)}`,
|
|
428
|
+
// The English default is retained verbatim: erevna/katalogos assert the exact string
|
|
429
|
+
// `Selects Red`. Consumers that localize pass `optionAccessibilityHint`.
|
|
430
|
+
accessibilityHint: config.hint ?? `Selects ${option.label}`,
|
|
431
|
+
ariaPressed: config.multiple ? selected : void 0
|
|
432
|
+
};
|
|
433
|
+
}
|
|
288
434
|
function OutlineChip({
|
|
289
435
|
option,
|
|
290
436
|
selected,
|
|
291
437
|
disabled,
|
|
292
438
|
colors,
|
|
439
|
+
testID,
|
|
440
|
+
accessibilityHint,
|
|
441
|
+
ariaPressed,
|
|
293
442
|
onPress
|
|
294
443
|
}) {
|
|
295
444
|
const [hovered, setHovered] = React__default.default.useState(false);
|
|
@@ -300,18 +449,19 @@ function OutlineChip({
|
|
|
300
449
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
301
450
|
reactNative.Pressable,
|
|
302
451
|
{
|
|
303
|
-
accessibilityHint
|
|
452
|
+
accessibilityHint,
|
|
304
453
|
accessibilityLabel: option.label,
|
|
305
454
|
accessibilityRole: "button",
|
|
306
455
|
accessibilityState: { selected, disabled },
|
|
456
|
+
"aria-pressed": ariaPressed,
|
|
307
457
|
disabled,
|
|
308
458
|
hitSlop: CHIP_HIT_SLOP,
|
|
309
|
-
style:
|
|
310
|
-
testID
|
|
459
|
+
style: styles4.chipWrapper,
|
|
460
|
+
testID,
|
|
311
461
|
onHoverIn: () => setHovered(true),
|
|
312
462
|
onHoverOut: () => setHovered(false),
|
|
313
463
|
onPress,
|
|
314
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
464
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles4.chipOutline, { borderColor, backgroundColor }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.chipText, styles4.chipTextOutline, { color: textColor }], children: option.label }) })
|
|
315
465
|
}
|
|
316
466
|
);
|
|
317
467
|
}
|
|
@@ -320,23 +470,27 @@ function SolidChip({
|
|
|
320
470
|
selected,
|
|
321
471
|
disabled,
|
|
322
472
|
colors,
|
|
473
|
+
testID,
|
|
474
|
+
accessibilityHint,
|
|
475
|
+
ariaPressed,
|
|
323
476
|
onPress
|
|
324
477
|
}) {
|
|
325
478
|
const backgroundColor = selected ? colors.primary : TRANSPARENT_COLOR;
|
|
326
|
-
const textColor = selected ?
|
|
479
|
+
const textColor = selected ? colors.onPrimary : colors.textStrong;
|
|
327
480
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
328
481
|
reactNative.TouchableOpacity,
|
|
329
482
|
{
|
|
330
|
-
accessibilityHint
|
|
483
|
+
accessibilityHint,
|
|
331
484
|
accessibilityLabel: option.label,
|
|
332
485
|
accessibilityRole: "button",
|
|
333
486
|
accessibilityState: { selected, disabled },
|
|
487
|
+
"aria-pressed": ariaPressed,
|
|
334
488
|
disabled,
|
|
335
489
|
hitSlop: CHIP_HIT_SLOP,
|
|
336
|
-
style:
|
|
337
|
-
testID
|
|
490
|
+
style: styles4.chipWrapper,
|
|
491
|
+
testID,
|
|
338
492
|
onPress,
|
|
339
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
493
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles4.chip, { borderColor: colors.border, backgroundColor }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.chipText, { color: textColor }], children: option.label }) })
|
|
340
494
|
}
|
|
341
495
|
);
|
|
342
496
|
}
|
|
@@ -356,7 +510,9 @@ var ChipSelector = ({
|
|
|
356
510
|
containerStyle,
|
|
357
511
|
required = false,
|
|
358
512
|
error,
|
|
359
|
-
testID
|
|
513
|
+
testID,
|
|
514
|
+
chipTestIDPrefix = DEFAULT_CHIP_TESTID_PREFIX,
|
|
515
|
+
optionAccessibilityHint
|
|
360
516
|
}) => {
|
|
361
517
|
const { theme } = uiFeedback.useUi();
|
|
362
518
|
const { colors, palette } = theme;
|
|
@@ -367,30 +523,37 @@ var ChipSelector = ({
|
|
|
367
523
|
textStrong: colors.text,
|
|
368
524
|
textMuted: colors.textSecondary,
|
|
369
525
|
primary,
|
|
526
|
+
onPrimary: onBrandInk(palette),
|
|
370
527
|
muted: colors.background,
|
|
371
528
|
tint: withAlpha2(primary, OUTLINE_TINT_ALPHA)
|
|
372
529
|
};
|
|
373
|
-
}, [colors.border, colors.text, colors.textSecondary, colors.background, palette
|
|
530
|
+
}, [colors.border, colors.text, colors.textSecondary, colors.background, palette]);
|
|
374
531
|
function isSelected(optionValue) {
|
|
375
532
|
if (multiple && Array.isArray(value))
|
|
376
533
|
return value.includes(optionValue);
|
|
377
534
|
return value === optionValue;
|
|
378
535
|
}
|
|
379
536
|
function renderChip(option) {
|
|
537
|
+
const selected = isSelected(option.value);
|
|
380
538
|
const chipProps = {
|
|
381
539
|
colors: chipColors,
|
|
382
540
|
disabled,
|
|
383
541
|
option,
|
|
384
|
-
selected
|
|
542
|
+
selected,
|
|
543
|
+
...chipIdentity(option, selected, {
|
|
544
|
+
prefix: chipTestIDPrefix,
|
|
545
|
+
hint: optionAccessibilityHint,
|
|
546
|
+
multiple
|
|
547
|
+
}),
|
|
385
548
|
onPress: () => onChange(option.value)
|
|
386
549
|
};
|
|
387
550
|
const key = String(option.value);
|
|
388
551
|
return variant === "outline" ? /* @__PURE__ */ jsxRuntime.jsx(OutlineChip, { ...chipProps }, key) : /* @__PURE__ */ jsxRuntime.jsx(SolidChip, { ...chipProps }, key);
|
|
389
552
|
}
|
|
390
|
-
return /* @__PURE__ */ jsxRuntime.jsx(Field, { containerStyle, error, label, required, testID, children: ({ describedById, hasError }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style:
|
|
553
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Field, { containerStyle, error, label, required, testID, children: ({ describedById, hasError }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles4.chipContainer, ...groupA11yProps(describedById, hasError), children: options.map(renderChip) }) });
|
|
391
554
|
};
|
|
392
555
|
var DESCRIPTION_FONT_SIZE = 12;
|
|
393
|
-
var
|
|
556
|
+
var styles5 = reactNative.StyleSheet.create({
|
|
394
557
|
container: {
|
|
395
558
|
flexDirection: "row",
|
|
396
559
|
justifyContent: "space-between",
|
|
@@ -430,13 +593,13 @@ var FormSwitch = ({
|
|
|
430
593
|
reactNative.View,
|
|
431
594
|
{
|
|
432
595
|
style: [
|
|
433
|
-
|
|
596
|
+
styles5.container,
|
|
434
597
|
containerStyle
|
|
435
598
|
],
|
|
436
599
|
children: [
|
|
437
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
438
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
439
|
-
hasDescription ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
600
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.textColumn, children: [
|
|
601
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.label, themeStyles.label], children: label }),
|
|
602
|
+
hasDescription ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.description, themeStyles.description], children: description }) : null
|
|
440
603
|
] }),
|
|
441
604
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
442
605
|
reactNative.Switch,
|
|
@@ -456,12 +619,12 @@ var FormSwitch = ({
|
|
|
456
619
|
);
|
|
457
620
|
};
|
|
458
621
|
var ACTIVATION_KEYS = ["Enter", " ", "Spacebar"];
|
|
459
|
-
var
|
|
622
|
+
var HINT_FONT_SIZE2 = 12;
|
|
460
623
|
var BOX_SIZE = 20;
|
|
461
624
|
var BOX_RADIUS = 5;
|
|
462
625
|
var CHECK_FONT_SIZE = 14;
|
|
463
626
|
var DISABLED_OPACITY = 0.5;
|
|
464
|
-
var
|
|
627
|
+
var styles6 = reactNative.StyleSheet.create({
|
|
465
628
|
container: {
|
|
466
629
|
flexDirection: "row",
|
|
467
630
|
alignItems: "flex-start",
|
|
@@ -490,7 +653,7 @@ var styles5 = reactNative.StyleSheet.create({
|
|
|
490
653
|
fontWeight: "600"
|
|
491
654
|
},
|
|
492
655
|
hint: {
|
|
493
|
-
fontSize:
|
|
656
|
+
fontSize: HINT_FONT_SIZE2,
|
|
494
657
|
marginTop: 2
|
|
495
658
|
}
|
|
496
659
|
});
|
|
@@ -543,26 +706,436 @@ var FormCheckbox = ({
|
|
|
543
706
|
disabled,
|
|
544
707
|
onPress: toggle,
|
|
545
708
|
role: "checkbox",
|
|
546
|
-
style: [
|
|
709
|
+
style: [styles6.container, containerStyle, disabled ? { opacity: DISABLED_OPACITY } : null],
|
|
547
710
|
testID,
|
|
548
711
|
...keyboardProps,
|
|
549
712
|
children: [
|
|
550
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
551
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
552
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
553
|
-
hasHint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
713
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles6.box, value ? themeStyles.boxOn : themeStyles.boxOff], children: value ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.check, { color: colors.surface }], accessibilityElementsHidden: true, children: "\u2713" }) : null }),
|
|
714
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles6.textColumn, children: [
|
|
715
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.label, themeStyles.label], children: label }),
|
|
716
|
+
hasHint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.hint, themeStyles.hint], children: hintText }) : null
|
|
554
717
|
] })
|
|
555
718
|
]
|
|
556
719
|
}
|
|
557
720
|
);
|
|
558
721
|
};
|
|
722
|
+
var FIELD_GAP = 4;
|
|
723
|
+
var LABEL_FONT = 11;
|
|
724
|
+
var LABEL_LETTER_SPACING = 0.4;
|
|
725
|
+
var INPUT_RADIUS = 8;
|
|
726
|
+
var INPUT_PAD_H = 12;
|
|
727
|
+
var INPUT_PAD_V = 10;
|
|
728
|
+
var INPUT_FONT = 14;
|
|
729
|
+
var INPUT_BORDER = 1;
|
|
730
|
+
var DATE_ROW_GAP = 8;
|
|
731
|
+
var CHEVRON_FONT = 10;
|
|
732
|
+
var MENU_TOP_GAP = 4;
|
|
733
|
+
var MENU_RADIUS = 8;
|
|
734
|
+
var MENU_PAD_V = 4;
|
|
735
|
+
var MENU_OPT_PAD_H = 12;
|
|
736
|
+
var MENU_OPT_PAD_V = 8;
|
|
737
|
+
var MENU_MIN_WIDTH = 160;
|
|
738
|
+
var MENU_MAX_HEIGHT = 260;
|
|
739
|
+
var MENU_Z = 1e3;
|
|
740
|
+
var MENU_ELEVATION = 8;
|
|
741
|
+
var MENU_BOX_SHADOW = "0px 2px 8px rgba(0, 0, 0, 0.15)";
|
|
742
|
+
var OPTION_FONT = 14;
|
|
743
|
+
var ERROR_FONT = 12;
|
|
744
|
+
var ERROR_GAP = 3;
|
|
745
|
+
var controlStyles = reactNative.StyleSheet.create({
|
|
746
|
+
/** The shared bordered box every dense text control renders in. */
|
|
747
|
+
input: {
|
|
748
|
+
borderWidth: INPUT_BORDER,
|
|
749
|
+
borderRadius: INPUT_RADIUS,
|
|
750
|
+
paddingHorizontal: INPUT_PAD_H,
|
|
751
|
+
paddingVertical: INPUT_PAD_V,
|
|
752
|
+
fontSize: INPUT_FONT
|
|
753
|
+
},
|
|
754
|
+
/** The select trigger: a bordered field box matching the text inputs + a chevron. */
|
|
755
|
+
selectTrigger: {
|
|
756
|
+
flexDirection: "row",
|
|
757
|
+
alignItems: "center",
|
|
758
|
+
justifyContent: "space-between",
|
|
759
|
+
gap: DATE_ROW_GAP,
|
|
760
|
+
borderWidth: INPUT_BORDER,
|
|
761
|
+
borderRadius: INPUT_RADIUS,
|
|
762
|
+
paddingHorizontal: INPUT_PAD_H,
|
|
763
|
+
paddingVertical: INPUT_PAD_V
|
|
764
|
+
},
|
|
765
|
+
selectTriggerText: { fontSize: INPUT_FONT, flexShrink: 1 },
|
|
766
|
+
chevron: { fontSize: CHEVRON_FONT },
|
|
767
|
+
dateRow: { flexDirection: "row", gap: DATE_ROW_GAP },
|
|
768
|
+
dateCol: { gap: FIELD_GAP, flex: 1 },
|
|
769
|
+
/**
|
|
770
|
+
* The micro-caption above a sub-control (a date range's From/To) and beside a switch. Note it
|
|
771
|
+
* is deliberately NOT the `control` label voice: it carries no `700`/uppercase, because it
|
|
772
|
+
* names a HALF of a control that already has a `control`-voice label above it. Uppercasing it
|
|
773
|
+
* too would make one field shout twice.
|
|
774
|
+
*/
|
|
775
|
+
subLabel: { fontSize: LABEL_FONT, letterSpacing: LABEL_LETTER_SPACING },
|
|
776
|
+
/** Positioning context for a floating `AnchoredMenu` (`position:absolute; top:100%`). */
|
|
777
|
+
anchor: { position: "relative" },
|
|
778
|
+
menu: {
|
|
779
|
+
position: "absolute",
|
|
780
|
+
top: "100%",
|
|
781
|
+
left: 0,
|
|
782
|
+
marginTop: MENU_TOP_GAP,
|
|
783
|
+
minWidth: MENU_MIN_WIDTH,
|
|
784
|
+
maxHeight: MENU_MAX_HEIGHT,
|
|
785
|
+
borderWidth: INPUT_BORDER,
|
|
786
|
+
borderRadius: MENU_RADIUS,
|
|
787
|
+
paddingVertical: MENU_PAD_V,
|
|
788
|
+
zIndex: MENU_Z,
|
|
789
|
+
boxShadow: MENU_BOX_SHADOW,
|
|
790
|
+
elevation: MENU_ELEVATION,
|
|
791
|
+
overflow: "hidden"
|
|
792
|
+
},
|
|
793
|
+
option: { paddingHorizontal: MENU_OPT_PAD_H, paddingVertical: MENU_OPT_PAD_V },
|
|
794
|
+
optionText: { fontSize: OPTION_FONT },
|
|
795
|
+
error: { fontSize: ERROR_FONT, marginTop: ERROR_GAP }
|
|
796
|
+
});
|
|
797
|
+
var IS_WEB4 = typeof document !== "undefined";
|
|
798
|
+
var ESCAPE_KEY = "Escape";
|
|
799
|
+
var ACTIVE_WEIGHT = "700";
|
|
800
|
+
var IDLE_WEIGHT = "400";
|
|
801
|
+
function useDismiss(anchorRef, menuRef, onDismiss) {
|
|
802
|
+
React.useEffect(() => {
|
|
803
|
+
if (!IS_WEB4) return void 0;
|
|
804
|
+
const onMouseDown = (event) => {
|
|
805
|
+
const anchorNode = anchorRef.current;
|
|
806
|
+
const menuNode = menuRef.current;
|
|
807
|
+
const target = event.target instanceof Node ? event.target : null;
|
|
808
|
+
const insideAnchor = anchorNode !== null && target !== null && anchorNode.contains(target);
|
|
809
|
+
const insideMenu = menuNode !== null && target !== null && menuNode.contains(target);
|
|
810
|
+
if (!insideAnchor && !insideMenu) onDismiss();
|
|
811
|
+
};
|
|
812
|
+
const onKeyDown = (event) => {
|
|
813
|
+
if (event.key === ESCAPE_KEY) onDismiss();
|
|
814
|
+
};
|
|
815
|
+
document.addEventListener("mousedown", onMouseDown);
|
|
816
|
+
document.addEventListener("keydown", onKeyDown);
|
|
817
|
+
return () => {
|
|
818
|
+
document.removeEventListener("mousedown", onMouseDown);
|
|
819
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
820
|
+
};
|
|
821
|
+
}, [anchorRef, menuRef, onDismiss]);
|
|
822
|
+
}
|
|
823
|
+
function AnchoredMenu({
|
|
824
|
+
options,
|
|
825
|
+
selectedValue,
|
|
826
|
+
onSelect,
|
|
827
|
+
onDismiss,
|
|
828
|
+
colors,
|
|
829
|
+
optionHint,
|
|
830
|
+
testID,
|
|
831
|
+
anchorRef
|
|
832
|
+
}) {
|
|
833
|
+
const menuRef = React__default.default.useRef(null);
|
|
834
|
+
useDismiss(anchorRef, menuRef, onDismiss);
|
|
835
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
836
|
+
reactNative.View,
|
|
837
|
+
{
|
|
838
|
+
ref: menuRef,
|
|
839
|
+
accessibilityRole: "menu",
|
|
840
|
+
style: [controlStyles.menu, { borderColor: colors.border, backgroundColor: colors.surface }],
|
|
841
|
+
testID: `${testID}-menu`,
|
|
842
|
+
children: options.map((opt) => {
|
|
843
|
+
const active = opt.value === selectedValue;
|
|
844
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
845
|
+
reactNative.Pressable,
|
|
846
|
+
{
|
|
847
|
+
accessibilityRole: "menuitem",
|
|
848
|
+
accessibilityLabel: opt.label,
|
|
849
|
+
accessibilityHint: optionHint,
|
|
850
|
+
accessibilityState: { selected: active },
|
|
851
|
+
"aria-selected": active,
|
|
852
|
+
onPress: () => onSelect(opt.value),
|
|
853
|
+
style: controlStyles.option,
|
|
854
|
+
testID: `${testID}-option-${opt.value}`,
|
|
855
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [controlStyles.optionText, { color: active ? colors.brand : colors.text, fontWeight: active ? ACTIVE_WEIGHT : IDLE_WEIGHT }], children: opt.label })
|
|
856
|
+
},
|
|
857
|
+
opt.value
|
|
858
|
+
);
|
|
859
|
+
})
|
|
860
|
+
}
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
var CHEVRON_DOWN = "\u25BE";
|
|
864
|
+
var CHEVRON_UP = "\u25B4";
|
|
865
|
+
function SelectControl({
|
|
866
|
+
options,
|
|
867
|
+
value,
|
|
868
|
+
onChange,
|
|
869
|
+
placeholder = "",
|
|
870
|
+
accessibilityLabel,
|
|
871
|
+
accessibilityHint,
|
|
872
|
+
optionHint = "",
|
|
873
|
+
style,
|
|
874
|
+
testID
|
|
875
|
+
}) {
|
|
876
|
+
const { theme } = uiFeedback.useUi();
|
|
877
|
+
const { colors, palette } = theme;
|
|
878
|
+
const anchorRef = React.useRef(null);
|
|
879
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
880
|
+
const selectedLabel = React.useMemo(() => {
|
|
881
|
+
const found = options.find((o) => o.value === value);
|
|
882
|
+
return found?.label ?? placeholder;
|
|
883
|
+
}, [options, placeholder, value]);
|
|
884
|
+
const close = React.useCallback(() => {
|
|
885
|
+
setIsOpen(false);
|
|
886
|
+
}, []);
|
|
887
|
+
const toggle = React.useCallback(() => {
|
|
888
|
+
setIsOpen((prev) => !prev);
|
|
889
|
+
}, []);
|
|
890
|
+
const select = React.useCallback(
|
|
891
|
+
(next) => {
|
|
892
|
+
onChange(next);
|
|
893
|
+
setIsOpen(false);
|
|
894
|
+
},
|
|
895
|
+
[onChange]
|
|
896
|
+
);
|
|
897
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { ref: anchorRef, style: [controlStyles.anchor, style], children: [
|
|
898
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
899
|
+
reactNative.Pressable,
|
|
900
|
+
{
|
|
901
|
+
accessibilityRole: "button",
|
|
902
|
+
accessibilityLabel,
|
|
903
|
+
accessibilityHint,
|
|
904
|
+
accessibilityState: { expanded: isOpen },
|
|
905
|
+
"aria-expanded": isOpen,
|
|
906
|
+
onPress: toggle,
|
|
907
|
+
style: [controlStyles.selectTrigger, { borderColor: colors.border, backgroundColor: colors.surface }],
|
|
908
|
+
testID: `${testID}-trigger`,
|
|
909
|
+
children: [
|
|
910
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { numberOfLines: 1, style: [controlStyles.selectTriggerText, { color: value === "" ? colors.textSecondary : colors.text }], children: selectedLabel }),
|
|
911
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { "aria-hidden": true, style: [controlStyles.chevron, { color: colors.textSecondary }], children: isOpen ? CHEVRON_UP : CHEVRON_DOWN })
|
|
912
|
+
]
|
|
913
|
+
}
|
|
914
|
+
),
|
|
915
|
+
isOpen ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
916
|
+
AnchoredMenu,
|
|
917
|
+
{
|
|
918
|
+
options,
|
|
919
|
+
selectedValue: value,
|
|
920
|
+
onSelect: select,
|
|
921
|
+
onDismiss: close,
|
|
922
|
+
colors: { text: colors.text, border: colors.border, surface: colors.surface, brand: palette.primary["500"] },
|
|
923
|
+
optionHint,
|
|
924
|
+
testID,
|
|
925
|
+
anchorRef
|
|
926
|
+
}
|
|
927
|
+
) : null
|
|
928
|
+
] });
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/controls/suggestOptions.ts
|
|
932
|
+
var RANK_VALUE_EXACT = 0;
|
|
933
|
+
var RANK_VALUE_PREFIX = 1;
|
|
934
|
+
var RANK_LABEL_PREFIX = 2;
|
|
935
|
+
var RANK_LABEL_CONTAINS = 3;
|
|
936
|
+
var NO_MATCH = -1;
|
|
937
|
+
function rankOf(option, needle) {
|
|
938
|
+
const value = option.value.toLowerCase();
|
|
939
|
+
const label = option.label.toLowerCase();
|
|
940
|
+
if (value === needle) return RANK_VALUE_EXACT;
|
|
941
|
+
if (value.startsWith(needle)) return RANK_VALUE_PREFIX;
|
|
942
|
+
if (label.startsWith(needle)) return RANK_LABEL_PREFIX;
|
|
943
|
+
if (label.includes(needle)) return RANK_LABEL_CONTAINS;
|
|
944
|
+
return NO_MATCH;
|
|
945
|
+
}
|
|
946
|
+
function suggestOptions(options, query, minChars, limit) {
|
|
947
|
+
const needle = query.trim().toLowerCase();
|
|
948
|
+
if (needle.length < minChars) return [];
|
|
949
|
+
const scored = [];
|
|
950
|
+
for (const option of options) {
|
|
951
|
+
const rank = rankOf(option, needle);
|
|
952
|
+
if (rank !== NO_MATCH) scored.push({ option, rank });
|
|
953
|
+
}
|
|
954
|
+
scored.sort((a, b) => a.rank !== b.rank ? a.rank - b.rank : a.option.label.localeCompare(b.option.label));
|
|
955
|
+
return scored.slice(0, limit).map((entry) => entry.option);
|
|
956
|
+
}
|
|
957
|
+
function isUnmatched(options, text) {
|
|
958
|
+
const needle = text.trim().toLowerCase();
|
|
959
|
+
if (needle === "") return false;
|
|
960
|
+
return !options.some((o) => o.value.toLowerCase() === needle || o.label.toLowerCase() === needle);
|
|
961
|
+
}
|
|
962
|
+
var CLOSE_DELAY_MS = 140;
|
|
963
|
+
var DEFAULT_TYPEAHEAD_MIN_CHARS = 1;
|
|
964
|
+
var DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS = 8;
|
|
965
|
+
function TypeaheadControl({
|
|
966
|
+
options,
|
|
967
|
+
value,
|
|
968
|
+
onChange,
|
|
969
|
+
onSubmit,
|
|
970
|
+
placeholder,
|
|
971
|
+
accessibilityLabel,
|
|
972
|
+
accessibilityHint,
|
|
973
|
+
optionHint = "",
|
|
974
|
+
error,
|
|
975
|
+
minChars = DEFAULT_TYPEAHEAD_MIN_CHARS,
|
|
976
|
+
maxSuggestions = DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS,
|
|
977
|
+
style,
|
|
978
|
+
testID
|
|
979
|
+
}) {
|
|
980
|
+
const { theme } = uiFeedback.useUi();
|
|
981
|
+
const { colors, palette, semantic } = theme;
|
|
982
|
+
const anchorRef = React.useRef(null);
|
|
983
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
984
|
+
const exactPick = React.useMemo(() => options.some((o) => o.label === value), [options, value]);
|
|
985
|
+
const suggestions = React.useMemo(
|
|
986
|
+
() => exactPick ? [] : suggestOptions(options, value, minChars, maxSuggestions),
|
|
987
|
+
[exactPick, options, value, minChars, maxSuggestions]
|
|
988
|
+
);
|
|
989
|
+
const showMenu = isOpen && suggestions.length > 0;
|
|
990
|
+
const handleChange = React.useCallback(
|
|
991
|
+
(next) => {
|
|
992
|
+
onChange(next);
|
|
993
|
+
setIsOpen(true);
|
|
994
|
+
},
|
|
995
|
+
[onChange]
|
|
996
|
+
);
|
|
997
|
+
const pick = React.useCallback(
|
|
998
|
+
(optionValue) => {
|
|
999
|
+
const opt = options.find((o) => o.value === optionValue);
|
|
1000
|
+
onChange(opt?.label ?? optionValue);
|
|
1001
|
+
setIsOpen(false);
|
|
1002
|
+
},
|
|
1003
|
+
[options, onChange]
|
|
1004
|
+
);
|
|
1005
|
+
const handleBlur = React.useCallback(() => {
|
|
1006
|
+
setTimeout(() => setIsOpen(false), CLOSE_DELAY_MS);
|
|
1007
|
+
}, []);
|
|
1008
|
+
const handleSubmit = React.useCallback(() => {
|
|
1009
|
+
setIsOpen(false);
|
|
1010
|
+
onSubmit?.();
|
|
1011
|
+
}, [onSubmit]);
|
|
1012
|
+
const hasError = error !== void 0 && error !== "";
|
|
1013
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { ref: anchorRef, style: [controlStyles.anchor, style], children: [
|
|
1014
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1015
|
+
reactNative.TextInput,
|
|
1016
|
+
{
|
|
1017
|
+
accessibilityLabel,
|
|
1018
|
+
accessibilityHint,
|
|
1019
|
+
autoCapitalize: "none",
|
|
1020
|
+
autoCorrect: false,
|
|
1021
|
+
placeholder,
|
|
1022
|
+
placeholderTextColor: colors.textSecondary,
|
|
1023
|
+
spellCheck: false,
|
|
1024
|
+
value,
|
|
1025
|
+
onChangeText: handleChange,
|
|
1026
|
+
onFocus: () => setIsOpen(true),
|
|
1027
|
+
onBlur: handleBlur,
|
|
1028
|
+
onSubmitEditing: handleSubmit,
|
|
1029
|
+
style: [controlStyles.input, { borderColor: hasError ? semantic.error["500"] : colors.border, backgroundColor: colors.surface, color: colors.text }],
|
|
1030
|
+
testID: `${testID}-input`
|
|
1031
|
+
}
|
|
1032
|
+
),
|
|
1033
|
+
showMenu ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1034
|
+
AnchoredMenu,
|
|
1035
|
+
{
|
|
1036
|
+
options: suggestions,
|
|
1037
|
+
selectedValue: value,
|
|
1038
|
+
onSelect: pick,
|
|
1039
|
+
onDismiss: () => setIsOpen(false),
|
|
1040
|
+
colors: { text: colors.text, border: colors.border, surface: colors.surface, brand: palette.primary["500"] },
|
|
1041
|
+
optionHint,
|
|
1042
|
+
testID,
|
|
1043
|
+
anchorRef
|
|
1044
|
+
}
|
|
1045
|
+
) : null,
|
|
1046
|
+
hasError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { role: "alert", style: [controlStyles.error, { color: semantic.error["500"] }], testID: `${testID}-error`, children: error }) : null
|
|
1047
|
+
] });
|
|
1048
|
+
}
|
|
1049
|
+
var WEB_DATE_PROPS = typeof document !== "undefined" ? { type: "date" } : {};
|
|
1050
|
+
function DateInput(p) {
|
|
1051
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: controlStyles.dateCol, children: [
|
|
1052
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [controlStyles.subLabel, { color: p.muted }], children: p.label }),
|
|
1053
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1054
|
+
reactNative.TextInput,
|
|
1055
|
+
{
|
|
1056
|
+
accessibilityLabel: p.label,
|
|
1057
|
+
accessibilityHint: p.hint,
|
|
1058
|
+
placeholder: p.placeholder,
|
|
1059
|
+
placeholderTextColor: p.muted,
|
|
1060
|
+
value: p.value,
|
|
1061
|
+
onChangeText: p.onChangeText,
|
|
1062
|
+
onSubmitEditing: p.onSubmit,
|
|
1063
|
+
style: [controlStyles.input, { borderColor: p.border, backgroundColor: p.surface, color: p.color }],
|
|
1064
|
+
testID: `${p.testID}-${p.side}`,
|
|
1065
|
+
...WEB_DATE_PROPS
|
|
1066
|
+
}
|
|
1067
|
+
)
|
|
1068
|
+
] });
|
|
1069
|
+
}
|
|
1070
|
+
function DateRangeControl({
|
|
1071
|
+
value,
|
|
1072
|
+
onChange,
|
|
1073
|
+
onSubmit,
|
|
1074
|
+
fromLabel,
|
|
1075
|
+
toLabel,
|
|
1076
|
+
fromPlaceholder,
|
|
1077
|
+
toPlaceholder,
|
|
1078
|
+
accessibilityHint,
|
|
1079
|
+
style,
|
|
1080
|
+
testID
|
|
1081
|
+
}) {
|
|
1082
|
+
const { theme } = uiFeedback.useUi();
|
|
1083
|
+
const { colors } = theme;
|
|
1084
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [controlStyles.dateRow, style], testID: `${testID}-range`, children: [
|
|
1085
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1086
|
+
DateInput,
|
|
1087
|
+
{
|
|
1088
|
+
side: "from",
|
|
1089
|
+
label: fromLabel,
|
|
1090
|
+
placeholder: fromPlaceholder,
|
|
1091
|
+
value: value.from,
|
|
1092
|
+
color: colors.text,
|
|
1093
|
+
border: colors.border,
|
|
1094
|
+
surface: colors.surface,
|
|
1095
|
+
muted: colors.textSecondary,
|
|
1096
|
+
hint: accessibilityHint,
|
|
1097
|
+
testID,
|
|
1098
|
+
onChangeText: (from) => onChange({ from, to: value.to }),
|
|
1099
|
+
onSubmit
|
|
1100
|
+
}
|
|
1101
|
+
),
|
|
1102
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1103
|
+
DateInput,
|
|
1104
|
+
{
|
|
1105
|
+
side: "to",
|
|
1106
|
+
label: toLabel,
|
|
1107
|
+
placeholder: toPlaceholder,
|
|
1108
|
+
value: value.to,
|
|
1109
|
+
color: colors.text,
|
|
1110
|
+
border: colors.border,
|
|
1111
|
+
surface: colors.surface,
|
|
1112
|
+
muted: colors.textSecondary,
|
|
1113
|
+
hint: accessibilityHint,
|
|
1114
|
+
testID,
|
|
1115
|
+
onChangeText: (to) => onChange({ from: value.from, to }),
|
|
1116
|
+
onSubmit
|
|
1117
|
+
}
|
|
1118
|
+
)
|
|
1119
|
+
] });
|
|
1120
|
+
}
|
|
559
1121
|
|
|
1122
|
+
exports.AnchoredMenu = AnchoredMenu;
|
|
560
1123
|
exports.ChipSelector = ChipSelector;
|
|
1124
|
+
exports.DEFAULT_OPTIONAL_LABEL = DEFAULT_OPTIONAL_LABEL;
|
|
1125
|
+
exports.DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS = DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS;
|
|
1126
|
+
exports.DEFAULT_TYPEAHEAD_MIN_CHARS = DEFAULT_TYPEAHEAD_MIN_CHARS;
|
|
1127
|
+
exports.DateRangeControl = DateRangeControl;
|
|
561
1128
|
exports.Field = Field;
|
|
1129
|
+
exports.FormActions = FormActions;
|
|
562
1130
|
exports.FormCheckbox = FormCheckbox;
|
|
563
1131
|
exports.FormField = FormField;
|
|
564
1132
|
exports.FormSwitch = FormSwitch;
|
|
1133
|
+
exports.SelectControl = SelectControl;
|
|
565
1134
|
exports.ThemedTextInput = ThemedTextInput;
|
|
1135
|
+
exports.TypeaheadControl = TypeaheadControl;
|
|
1136
|
+
exports.controlStyles = controlStyles;
|
|
1137
|
+
exports.isUnmatched = isUnmatched;
|
|
1138
|
+
exports.suggestOptions = suggestOptions;
|
|
566
1139
|
exports.useThemedInput = useThemedInput;
|
|
567
1140
|
//# sourceMappingURL=index.js.map
|
|
568
1141
|
//# sourceMappingURL=index.js.map
|