@dloizides/ui-forms 1.9.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +327 -280
- package/dist/index.d.mts +79 -3
- package/dist/index.d.ts +79 -3
- package/dist/index.js +240 -166
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +216 -142
- package/dist/index.mjs.map +1 -1
- package/package.json +110 -107
package/dist/index.d.mts
CHANGED
|
@@ -94,6 +94,21 @@ interface FieldChildContext {
|
|
|
94
94
|
*/
|
|
95
95
|
describedById?: string;
|
|
96
96
|
hasError: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* `id` this field's `<label for>` points at. Assign it to the control (RN `id` → DOM `id`) and
|
|
99
|
+
* clicking the label text focuses the control — a free doubling of the hit area that every
|
|
100
|
+
* field was throwing away.
|
|
101
|
+
*
|
|
102
|
+
* A plain-node child gets this injected automatically (see below); a render-function child has
|
|
103
|
+
* to place it, because only the caller knows which element inside is the actual control.
|
|
104
|
+
*/
|
|
105
|
+
controlId: string;
|
|
106
|
+
/**
|
|
107
|
+
* `id` OF the label element. For a control that `<label for>` cannot address — anything that is
|
|
108
|
+
* not a native form element, e.g. the `<div role="button">` a `SelectControl` trigger renders —
|
|
109
|
+
* point `aria-labelledby` here instead.
|
|
110
|
+
*/
|
|
111
|
+
labelId: string;
|
|
97
112
|
}
|
|
98
113
|
type FieldChildren = React.ReactNode | ((context: FieldChildContext) => React.ReactNode);
|
|
99
114
|
interface FieldProps {
|
|
@@ -356,13 +371,31 @@ declare const FormActions: ({ onSave, onCancel, saveLabel, cancelLabel, saveHint
|
|
|
356
371
|
* soft ring) for free, driven by the active theme.
|
|
357
372
|
*/
|
|
358
373
|
|
|
374
|
+
/**
|
|
375
|
+
* The private `styles.input` that used to live here — padding 10/12, radius 8, border 1 — was a
|
|
376
|
+
* verbatim duplicate of `controlStyles.input`, and `ThemedTextInput` now applies that itself. The
|
|
377
|
+
* duplication is what hid the real defect: because `FormField` supplied metrics, inputs reached
|
|
378
|
+
* through it looked correct, while every DIRECT `ThemedTextInput` caller got an unstyled 19px box
|
|
379
|
+
* and nobody could see the difference from in here.
|
|
380
|
+
*/
|
|
359
381
|
interface FormFieldProps extends Omit<TextInputProps, 'style'> {
|
|
360
382
|
label: string;
|
|
383
|
+
/**
|
|
384
|
+
* Hide the VISIBLE label row while keeping the accessible name.
|
|
385
|
+
*
|
|
386
|
+
* For a field whose placeholder already names it (a toolbar search box, an inline
|
|
387
|
+
* filter) the stacked visible label both wastes a row and misaligns the input
|
|
388
|
+
* against a bare button beside it. Setting this drops ONLY the visible `<label>`:
|
|
389
|
+
* the input still carries `accessibilityLabel={label}`, so a screen reader reads
|
|
390
|
+
* the same name and `label` stays REQUIRED — you never lose the accessible name by
|
|
391
|
+
* blanking it, which is the trap this option exists to remove. Default `false`.
|
|
392
|
+
*/
|
|
393
|
+
labelHidden?: boolean;
|
|
361
394
|
required?: boolean;
|
|
362
395
|
error?: string;
|
|
363
396
|
containerStyle?: ViewStyle;
|
|
364
397
|
}
|
|
365
|
-
declare const FormField: ({ label, required, error, containerStyle, ...textInputProps }: FormFieldProps) => React.ReactElement;
|
|
398
|
+
declare const FormField: ({ label, labelHidden, required, error, containerStyle, ...textInputProps }: FormFieldProps) => React.ReactElement;
|
|
366
399
|
|
|
367
400
|
/**
|
|
368
401
|
* `ThemedTextInput` — the shared, readable + lively text-input primitive. A thin `<TextInput>`
|
|
@@ -373,6 +406,20 @@ declare const FormField: ({ label, required, error, containerStyle, ...textInput
|
|
|
373
406
|
* It is the ONE source of truth for the input look: `FormField` renders it, and raw filter inputs
|
|
374
407
|
* (search / country / year / date fallbacks) use it directly instead of forking the styles.
|
|
375
408
|
*
|
|
409
|
+
* ## The box metrics are applied HERE, and that is new
|
|
410
|
+
*
|
|
411
|
+
* This component claimed to be the source of truth for the input look while shipping only
|
|
412
|
+
* COLOURS — no border, no radius, no padding, no height. `FormField` happened to pass its own
|
|
413
|
+
* private copy of those metrics, so every input reached through `FormField` looked right and the
|
|
414
|
+
* gap stayed invisible. Any caller that used `ThemedTextInput` directly — the documented, blessed
|
|
415
|
+
* way to use it — got a raw browser input: **19px tall**, measured in production on an admin CMS
|
|
416
|
+
* where all 10 inputs failed the hit-target floor.
|
|
417
|
+
*
|
|
418
|
+
* So `controlStyles.input` is now the component's own base, ahead of the caller's `style` in the
|
|
419
|
+
* cascade. Callers that already passed the same metrics (`FormField`) are unaffected — the values
|
|
420
|
+
* are identical, which is precisely why the duplication was invisible — and callers that pass
|
|
421
|
+
* their own overrides still win, because their `style` merges after the base.
|
|
422
|
+
*
|
|
376
423
|
* Every existing `TextInputProps` (value, placeholder, onChangeText, testID, accessibility…) is
|
|
377
424
|
* forwarded untouched; `onFocus`/`onBlur` you pass are composed with the internal focus state.
|
|
378
425
|
*/
|
|
@@ -387,6 +434,16 @@ interface ThemedTextInputProps extends TextInputProps {
|
|
|
387
434
|
describedById?: string;
|
|
388
435
|
/** Marks the field required for assistive tech (`aria-required` on web). No-op on native. */
|
|
389
436
|
requiredField?: boolean;
|
|
437
|
+
/**
|
|
438
|
+
* Opt OUT of the shared box metrics (border, radius, padding, the kit's min-height floor) and
|
|
439
|
+
* render colours only — the pre-1.11 behaviour.
|
|
440
|
+
*
|
|
441
|
+
* For the rare control that supplies its own complete box, e.g. an input embedded inside an
|
|
442
|
+
* already-bordered composite where a second border would double up. It is a prop rather than
|
|
443
|
+
* the default because the default has to be the accessible one: the previous default shipped
|
|
444
|
+
* 19px inputs to production precisely because "no metrics" was the silent fallback.
|
|
445
|
+
*/
|
|
446
|
+
unstyledBox?: boolean;
|
|
390
447
|
}
|
|
391
448
|
declare const ThemedTextInput: React.ForwardRefExoticComponent<ThemedTextInputProps & React.RefAttributes<TextInput>>;
|
|
392
449
|
|
|
@@ -576,6 +633,17 @@ interface SelectControlProps {
|
|
|
576
633
|
*/
|
|
577
634
|
accessibilityLabel: string;
|
|
578
635
|
accessibilityHint?: string;
|
|
636
|
+
/**
|
|
637
|
+
* `id` of the `Field` label naming this control — take it from the render-child context's
|
|
638
|
+
* `labelId`. The trigger is a `<div role="button">`, which `<label for>` cannot address (that
|
|
639
|
+
* only works for native form elements), so the association runs the other way: the trigger
|
|
640
|
+
* points AT the label.
|
|
641
|
+
*
|
|
642
|
+
* `accessibilityLabel` still wins for the accessible NAME, deliberately — it is the only one of
|
|
643
|
+
* the two that carries the current selection ("Status: Active"). This adds the structural link
|
|
644
|
+
* without changing a single announcement.
|
|
645
|
+
*/
|
|
646
|
+
labelledById?: string;
|
|
579
647
|
/** PRE-LOCALIZED accessibility hint applied to every option in the menu. */
|
|
580
648
|
optionHint?: string;
|
|
581
649
|
/** Merged LAST onto the anchor wrapper — the consumer always wins over the shared style. */
|
|
@@ -583,7 +651,7 @@ interface SelectControlProps {
|
|
|
583
651
|
/** Trigger is `${testID}-trigger`, menu `${testID}-menu`, options `${testID}-option-${value}`. */
|
|
584
652
|
testID: string;
|
|
585
653
|
}
|
|
586
|
-
declare function SelectControl({ options, value, onChange, placeholder, accessibilityLabel, accessibilityHint, optionHint, style, testID, }: SelectControlProps): React.ReactElement;
|
|
654
|
+
declare function SelectControl({ options, value, onChange, placeholder, accessibilityLabel, accessibilityHint, labelledById, optionHint, style, testID, }: SelectControlProps): React.ReactElement;
|
|
587
655
|
|
|
588
656
|
/**
|
|
589
657
|
* `TypeaheadControl` — a search-as-you-type combobox (aml-v2's CountryPicker, generalized). A
|
|
@@ -702,16 +770,24 @@ interface AnchoredMenuProps {
|
|
|
702
770
|
declare function AnchoredMenu({ options, selectedValue, onSelect, onDismiss, colors, optionHint, testID, anchorRef, }: AnchoredMenuProps): React.ReactElement;
|
|
703
771
|
|
|
704
772
|
declare const controlStyles: {
|
|
705
|
-
/**
|
|
773
|
+
/**
|
|
774
|
+
* The shared bordered box every dense text control renders in.
|
|
775
|
+
*
|
|
776
|
+
* `minHeight` is the kit floor: padding alone produced a ~38px box, and a bare input with no
|
|
777
|
+
* box style at all measured **19px** in production. Height is a hit-target guarantee, so it
|
|
778
|
+
* is stated rather than left to emerge from font size + padding.
|
|
779
|
+
*/
|
|
706
780
|
input: {
|
|
707
781
|
borderWidth: number;
|
|
708
782
|
borderRadius: number;
|
|
709
783
|
paddingHorizontal: number;
|
|
710
784
|
paddingVertical: number;
|
|
711
785
|
fontSize: number;
|
|
786
|
+
minHeight: number;
|
|
712
787
|
};
|
|
713
788
|
/** The select trigger: a bordered field box matching the text inputs + a chevron. */
|
|
714
789
|
selectTrigger: {
|
|
790
|
+
minHeight: number;
|
|
715
791
|
flexDirection: "row";
|
|
716
792
|
alignItems: "center";
|
|
717
793
|
justifyContent: "space-between";
|
package/dist/index.d.ts
CHANGED
|
@@ -94,6 +94,21 @@ interface FieldChildContext {
|
|
|
94
94
|
*/
|
|
95
95
|
describedById?: string;
|
|
96
96
|
hasError: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* `id` this field's `<label for>` points at. Assign it to the control (RN `id` → DOM `id`) and
|
|
99
|
+
* clicking the label text focuses the control — a free doubling of the hit area that every
|
|
100
|
+
* field was throwing away.
|
|
101
|
+
*
|
|
102
|
+
* A plain-node child gets this injected automatically (see below); a render-function child has
|
|
103
|
+
* to place it, because only the caller knows which element inside is the actual control.
|
|
104
|
+
*/
|
|
105
|
+
controlId: string;
|
|
106
|
+
/**
|
|
107
|
+
* `id` OF the label element. For a control that `<label for>` cannot address — anything that is
|
|
108
|
+
* not a native form element, e.g. the `<div role="button">` a `SelectControl` trigger renders —
|
|
109
|
+
* point `aria-labelledby` here instead.
|
|
110
|
+
*/
|
|
111
|
+
labelId: string;
|
|
97
112
|
}
|
|
98
113
|
type FieldChildren = React.ReactNode | ((context: FieldChildContext) => React.ReactNode);
|
|
99
114
|
interface FieldProps {
|
|
@@ -356,13 +371,31 @@ declare const FormActions: ({ onSave, onCancel, saveLabel, cancelLabel, saveHint
|
|
|
356
371
|
* soft ring) for free, driven by the active theme.
|
|
357
372
|
*/
|
|
358
373
|
|
|
374
|
+
/**
|
|
375
|
+
* The private `styles.input` that used to live here — padding 10/12, radius 8, border 1 — was a
|
|
376
|
+
* verbatim duplicate of `controlStyles.input`, and `ThemedTextInput` now applies that itself. The
|
|
377
|
+
* duplication is what hid the real defect: because `FormField` supplied metrics, inputs reached
|
|
378
|
+
* through it looked correct, while every DIRECT `ThemedTextInput` caller got an unstyled 19px box
|
|
379
|
+
* and nobody could see the difference from in here.
|
|
380
|
+
*/
|
|
359
381
|
interface FormFieldProps extends Omit<TextInputProps, 'style'> {
|
|
360
382
|
label: string;
|
|
383
|
+
/**
|
|
384
|
+
* Hide the VISIBLE label row while keeping the accessible name.
|
|
385
|
+
*
|
|
386
|
+
* For a field whose placeholder already names it (a toolbar search box, an inline
|
|
387
|
+
* filter) the stacked visible label both wastes a row and misaligns the input
|
|
388
|
+
* against a bare button beside it. Setting this drops ONLY the visible `<label>`:
|
|
389
|
+
* the input still carries `accessibilityLabel={label}`, so a screen reader reads
|
|
390
|
+
* the same name and `label` stays REQUIRED — you never lose the accessible name by
|
|
391
|
+
* blanking it, which is the trap this option exists to remove. Default `false`.
|
|
392
|
+
*/
|
|
393
|
+
labelHidden?: boolean;
|
|
361
394
|
required?: boolean;
|
|
362
395
|
error?: string;
|
|
363
396
|
containerStyle?: ViewStyle;
|
|
364
397
|
}
|
|
365
|
-
declare const FormField: ({ label, required, error, containerStyle, ...textInputProps }: FormFieldProps) => React.ReactElement;
|
|
398
|
+
declare const FormField: ({ label, labelHidden, required, error, containerStyle, ...textInputProps }: FormFieldProps) => React.ReactElement;
|
|
366
399
|
|
|
367
400
|
/**
|
|
368
401
|
* `ThemedTextInput` — the shared, readable + lively text-input primitive. A thin `<TextInput>`
|
|
@@ -373,6 +406,20 @@ declare const FormField: ({ label, required, error, containerStyle, ...textInput
|
|
|
373
406
|
* It is the ONE source of truth for the input look: `FormField` renders it, and raw filter inputs
|
|
374
407
|
* (search / country / year / date fallbacks) use it directly instead of forking the styles.
|
|
375
408
|
*
|
|
409
|
+
* ## The box metrics are applied HERE, and that is new
|
|
410
|
+
*
|
|
411
|
+
* This component claimed to be the source of truth for the input look while shipping only
|
|
412
|
+
* COLOURS — no border, no radius, no padding, no height. `FormField` happened to pass its own
|
|
413
|
+
* private copy of those metrics, so every input reached through `FormField` looked right and the
|
|
414
|
+
* gap stayed invisible. Any caller that used `ThemedTextInput` directly — the documented, blessed
|
|
415
|
+
* way to use it — got a raw browser input: **19px tall**, measured in production on an admin CMS
|
|
416
|
+
* where all 10 inputs failed the hit-target floor.
|
|
417
|
+
*
|
|
418
|
+
* So `controlStyles.input` is now the component's own base, ahead of the caller's `style` in the
|
|
419
|
+
* cascade. Callers that already passed the same metrics (`FormField`) are unaffected — the values
|
|
420
|
+
* are identical, which is precisely why the duplication was invisible — and callers that pass
|
|
421
|
+
* their own overrides still win, because their `style` merges after the base.
|
|
422
|
+
*
|
|
376
423
|
* Every existing `TextInputProps` (value, placeholder, onChangeText, testID, accessibility…) is
|
|
377
424
|
* forwarded untouched; `onFocus`/`onBlur` you pass are composed with the internal focus state.
|
|
378
425
|
*/
|
|
@@ -387,6 +434,16 @@ interface ThemedTextInputProps extends TextInputProps {
|
|
|
387
434
|
describedById?: string;
|
|
388
435
|
/** Marks the field required for assistive tech (`aria-required` on web). No-op on native. */
|
|
389
436
|
requiredField?: boolean;
|
|
437
|
+
/**
|
|
438
|
+
* Opt OUT of the shared box metrics (border, radius, padding, the kit's min-height floor) and
|
|
439
|
+
* render colours only — the pre-1.11 behaviour.
|
|
440
|
+
*
|
|
441
|
+
* For the rare control that supplies its own complete box, e.g. an input embedded inside an
|
|
442
|
+
* already-bordered composite where a second border would double up. It is a prop rather than
|
|
443
|
+
* the default because the default has to be the accessible one: the previous default shipped
|
|
444
|
+
* 19px inputs to production precisely because "no metrics" was the silent fallback.
|
|
445
|
+
*/
|
|
446
|
+
unstyledBox?: boolean;
|
|
390
447
|
}
|
|
391
448
|
declare const ThemedTextInput: React.ForwardRefExoticComponent<ThemedTextInputProps & React.RefAttributes<TextInput>>;
|
|
392
449
|
|
|
@@ -576,6 +633,17 @@ interface SelectControlProps {
|
|
|
576
633
|
*/
|
|
577
634
|
accessibilityLabel: string;
|
|
578
635
|
accessibilityHint?: string;
|
|
636
|
+
/**
|
|
637
|
+
* `id` of the `Field` label naming this control — take it from the render-child context's
|
|
638
|
+
* `labelId`. The trigger is a `<div role="button">`, which `<label for>` cannot address (that
|
|
639
|
+
* only works for native form elements), so the association runs the other way: the trigger
|
|
640
|
+
* points AT the label.
|
|
641
|
+
*
|
|
642
|
+
* `accessibilityLabel` still wins for the accessible NAME, deliberately — it is the only one of
|
|
643
|
+
* the two that carries the current selection ("Status: Active"). This adds the structural link
|
|
644
|
+
* without changing a single announcement.
|
|
645
|
+
*/
|
|
646
|
+
labelledById?: string;
|
|
579
647
|
/** PRE-LOCALIZED accessibility hint applied to every option in the menu. */
|
|
580
648
|
optionHint?: string;
|
|
581
649
|
/** Merged LAST onto the anchor wrapper — the consumer always wins over the shared style. */
|
|
@@ -583,7 +651,7 @@ interface SelectControlProps {
|
|
|
583
651
|
/** Trigger is `${testID}-trigger`, menu `${testID}-menu`, options `${testID}-option-${value}`. */
|
|
584
652
|
testID: string;
|
|
585
653
|
}
|
|
586
|
-
declare function SelectControl({ options, value, onChange, placeholder, accessibilityLabel, accessibilityHint, optionHint, style, testID, }: SelectControlProps): React.ReactElement;
|
|
654
|
+
declare function SelectControl({ options, value, onChange, placeholder, accessibilityLabel, accessibilityHint, labelledById, optionHint, style, testID, }: SelectControlProps): React.ReactElement;
|
|
587
655
|
|
|
588
656
|
/**
|
|
589
657
|
* `TypeaheadControl` — a search-as-you-type combobox (aml-v2's CountryPicker, generalized). A
|
|
@@ -702,16 +770,24 @@ interface AnchoredMenuProps {
|
|
|
702
770
|
declare function AnchoredMenu({ options, selectedValue, onSelect, onDismiss, colors, optionHint, testID, anchorRef, }: AnchoredMenuProps): React.ReactElement;
|
|
703
771
|
|
|
704
772
|
declare const controlStyles: {
|
|
705
|
-
/**
|
|
773
|
+
/**
|
|
774
|
+
* The shared bordered box every dense text control renders in.
|
|
775
|
+
*
|
|
776
|
+
* `minHeight` is the kit floor: padding alone produced a ~38px box, and a bare input with no
|
|
777
|
+
* box style at all measured **19px** in production. Height is a hit-target guarantee, so it
|
|
778
|
+
* is stated rather than left to emerge from font size + padding.
|
|
779
|
+
*/
|
|
706
780
|
input: {
|
|
707
781
|
borderWidth: number;
|
|
708
782
|
borderRadius: number;
|
|
709
783
|
paddingHorizontal: number;
|
|
710
784
|
paddingVertical: number;
|
|
711
785
|
fontSize: number;
|
|
786
|
+
minHeight: number;
|
|
712
787
|
};
|
|
713
788
|
/** The select trigger: a bordered field box matching the text inputs + a chevron. */
|
|
714
789
|
selectTrigger: {
|
|
790
|
+
minHeight: number;
|
|
715
791
|
flexDirection: "row";
|
|
716
792
|
alignItems: "center";
|
|
717
793
|
justifyContent: "space-between";
|