@dloizides/ui-forms 1.7.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 +59 -0
- package/README.md +42 -0
- package/dist/index.d.mts +275 -2
- package/dist/index.d.ts +275 -2
- package/dist/index.js +408 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +401 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { StyleProp, TextStyle, ViewStyle, TextInputProps, TextInput } from 'react-native';
|
|
2
|
+
import { StyleProp, TextStyle, ViewStyle, TextInputProps, TextInput, View } from 'react-native';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The TWO legitimate label voices, per the shared forms contract
|
|
@@ -351,4 +351,277 @@ interface FormCheckboxProps {
|
|
|
351
351
|
}
|
|
352
352
|
declare const FormCheckbox: ({ label, value, onValueChange, onChange, hint, description, disabled, containerStyle, testID, accessibilityLabel, accessibilityHint, }: FormCheckboxProps) => React.ReactElement;
|
|
353
353
|
|
|
354
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Shared value types for the dense CONTROLS (select / typeahead / date-range) promoted out of
|
|
356
|
+
* `@dloizides/ui-tables`' private filter bar.
|
|
357
|
+
*
|
|
358
|
+
* They live here, not in `ui-tables`, because a control's option list and a date range are form
|
|
359
|
+
* concepts — a filter bar is only one surface that happens to compose them. `ui-tables` aliases
|
|
360
|
+
* its long-standing public names (`FilterOption`, `DateRangeValue`) onto these, so there is ONE
|
|
361
|
+
* definition and the two can never drift into structurally-compatible-but-separate twins.
|
|
362
|
+
*/
|
|
363
|
+
/** A single choice in a select / typeahead. `label` is PRE-LOCALIZED by the caller. */
|
|
364
|
+
interface ControlOption {
|
|
365
|
+
readonly label: string;
|
|
366
|
+
readonly value: string;
|
|
367
|
+
}
|
|
368
|
+
/** The inclusive from/to value of a date-range control (each an ISO `YYYY-MM-DD` string, or ''). */
|
|
369
|
+
interface DateRangeValue {
|
|
370
|
+
readonly from: string;
|
|
371
|
+
readonly to: string;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* `SelectControl` — a single-select enum/status dropdown. A bordered trigger shows the selected
|
|
376
|
+
* option's label (or the placeholder when unset) plus a chevron; pressing it opens the shared
|
|
377
|
+
* {@link AnchoredMenu}. Selecting fires `onChange` and closes.
|
|
378
|
+
*
|
|
379
|
+
* Promoted out of `@dloizides/ui-tables`' private `Filters/fields/SelectField`, where six portals
|
|
380
|
+
* could see it working and none could import it — which is why the fleet grew SIX independent
|
|
381
|
+
* select implementations.
|
|
382
|
+
*
|
|
383
|
+
* The control is LABEL-FREE by design: wrap it in a `Field` (with `labelVariant="control"` on a
|
|
384
|
+
* dense surface) to get the label, hint and error slots. That keeps one label implementation for
|
|
385
|
+
* every control instead of one per control.
|
|
386
|
+
*
|
|
387
|
+
* i18n: this package never calls `t`. `accessibilityLabel`, `placeholder`, `accessibilityHint`
|
|
388
|
+
* and `optionHint` are PRE-LOCALIZED strings supplied by the caller — the only shape that can
|
|
389
|
+
* serve both aml-v2's positional `{0}` `@dloizides/i18n` and the other six portals' i18next
|
|
390
|
+
* `{{p1}}` from one component.
|
|
391
|
+
*/
|
|
392
|
+
|
|
393
|
+
interface SelectControlProps {
|
|
394
|
+
options: readonly ControlOption[];
|
|
395
|
+
/** The selected option's `value`. `''` (or any unmatched value) renders the placeholder. */
|
|
396
|
+
value: string;
|
|
397
|
+
onChange: (value: string) => void;
|
|
398
|
+
/** PRE-LOCALIZED text shown when the value matches no option. */
|
|
399
|
+
placeholder?: string;
|
|
400
|
+
/**
|
|
401
|
+
* PRE-LOCALIZED accessible NAME of the trigger. It REPLACES the trigger's visible text for a
|
|
402
|
+
* screen reader, so it must carry the SELECTION too — "Status: Active", not just "Status",
|
|
403
|
+
* which announces the control but silently hides its value.
|
|
404
|
+
*/
|
|
405
|
+
accessibilityLabel: string;
|
|
406
|
+
accessibilityHint?: string;
|
|
407
|
+
/** PRE-LOCALIZED accessibility hint applied to every option in the menu. */
|
|
408
|
+
optionHint?: string;
|
|
409
|
+
/** Merged LAST onto the anchor wrapper — the consumer always wins over the shared style. */
|
|
410
|
+
style?: StyleProp<ViewStyle>;
|
|
411
|
+
/** Trigger is `${testID}-trigger`, menu `${testID}-menu`, options `${testID}-option-${value}`. */
|
|
412
|
+
testID: string;
|
|
413
|
+
}
|
|
414
|
+
declare function SelectControl({ options, value, onChange, placeholder, accessibilityLabel, accessibilityHint, optionHint, style, testID, }: SelectControlProps): React.ReactElement;
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* `TypeaheadControl` — a search-as-you-type combobox (aml-v2's CountryPicker, generalized). A
|
|
418
|
+
* text input plus a floating {@link AnchoredMenu} of ranked suggestions ({@link suggestOptions});
|
|
419
|
+
* picking one fills the canonical label.
|
|
420
|
+
*
|
|
421
|
+
* Free typing is PRESERVED: the value is the raw text, so a caller can accept "CY", "cyprus" or
|
|
422
|
+
* "Cyprus (CY)" and normalise it themselves at query time, exactly as aml does. An optional
|
|
423
|
+
* PRE-LOCALIZED `error` shows a persistent banner and tints the border.
|
|
424
|
+
*
|
|
425
|
+
* Promoted out of `@dloizides/ui-tables`' private `Filters/fields/TypeaheadField`.
|
|
426
|
+
*
|
|
427
|
+
* Label-free by design — wrap in a `Field` for the label/hint slots.
|
|
428
|
+
*/
|
|
429
|
+
|
|
430
|
+
/** Default min characters before suggestions surface. */
|
|
431
|
+
declare const DEFAULT_TYPEAHEAD_MIN_CHARS = 1;
|
|
432
|
+
/** Default cap on rendered suggestions. */
|
|
433
|
+
declare const DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS = 8;
|
|
434
|
+
interface TypeaheadControlProps {
|
|
435
|
+
options: readonly ControlOption[];
|
|
436
|
+
/** The raw text in the box — NOT necessarily an option value. */
|
|
437
|
+
value: string;
|
|
438
|
+
onChange: (value: string) => void;
|
|
439
|
+
/** Called on Enter/submit (after the menu closes). Omit if the surface has nothing to commit. */
|
|
440
|
+
onSubmit?: () => void;
|
|
441
|
+
placeholder?: string;
|
|
442
|
+
/** PRE-LOCALIZED accessible name of the input. */
|
|
443
|
+
accessibilityLabel: string;
|
|
444
|
+
accessibilityHint?: string;
|
|
445
|
+
/** PRE-LOCALIZED accessibility hint applied to every suggestion. */
|
|
446
|
+
optionHint?: string;
|
|
447
|
+
/** PRE-LOCALIZED inline error (e.g. "Unrecognised country"). Shows a persistent banner. */
|
|
448
|
+
error?: string;
|
|
449
|
+
/** Minimum characters before suggestions surface. Default 1. */
|
|
450
|
+
minChars?: number;
|
|
451
|
+
/** Cap on rendered suggestions. Default 8. */
|
|
452
|
+
maxSuggestions?: number;
|
|
453
|
+
/** Merged LAST onto the anchor wrapper. */
|
|
454
|
+
style?: StyleProp<ViewStyle>;
|
|
455
|
+
/** Input is `${testID}-input`, menu `${testID}-menu`, error `${testID}-error`. */
|
|
456
|
+
testID: string;
|
|
457
|
+
}
|
|
458
|
+
declare function TypeaheadControl({ options, value, onChange, onSubmit, placeholder, accessibilityLabel, accessibilityHint, optionHint, error, minChars, maxSuggestions, style, testID, }: TypeaheadControlProps): React.ReactElement;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* `DateRangeControl` — an inclusive from/to pair (aml cases `from`/`to`, kefi audit `from`/`to`).
|
|
462
|
+
* Each side is a `YYYY-MM-DD` text input; on web the browser's native date UI is offered via
|
|
463
|
+
* `type="date"` (a react-native-web `TextInput` passes unknown props to the DOM input). Editing
|
|
464
|
+
* either side patches ONLY that side, so the other survives the edit.
|
|
465
|
+
*
|
|
466
|
+
* Promoted out of `@dloizides/ui-tables`' private `Filters/fields/DateRangeField`, which is why
|
|
467
|
+
* the fleet grew FIVE independent date fields.
|
|
468
|
+
*
|
|
469
|
+
* Label-free by design — the From/To sub-labels name the two HALVES; wrap in a `Field` for the
|
|
470
|
+
* range's own label.
|
|
471
|
+
*/
|
|
472
|
+
|
|
473
|
+
interface DateRangeControlProps {
|
|
474
|
+
value: DateRangeValue;
|
|
475
|
+
onChange: (value: DateRangeValue) => void;
|
|
476
|
+
/** Called on Enter/submit in either side. */
|
|
477
|
+
onSubmit?: () => void;
|
|
478
|
+
/** PRE-LOCALIZED sub-label for the FROM input (e.g. "From"). Also its accessible name. */
|
|
479
|
+
fromLabel: string;
|
|
480
|
+
/** PRE-LOCALIZED sub-label for the TO input (e.g. "To"). Also its accessible name. */
|
|
481
|
+
toLabel: string;
|
|
482
|
+
fromPlaceholder?: string;
|
|
483
|
+
toPlaceholder?: string;
|
|
484
|
+
/** PRE-LOCALIZED hint applied to BOTH inputs. */
|
|
485
|
+
accessibilityHint?: string;
|
|
486
|
+
/** Merged LAST onto the row wrapper. */
|
|
487
|
+
style?: StyleProp<ViewStyle>;
|
|
488
|
+
/** Row is `${testID}-range`, inputs `${testID}-from` / `${testID}-to`. */
|
|
489
|
+
testID: string;
|
|
490
|
+
}
|
|
491
|
+
declare function DateRangeControl({ value, onChange, onSubmit, fromLabel, toLabel, fromPlaceholder, toPlaceholder, accessibilityHint, style, testID, }: DateRangeControlProps): React.ReactElement;
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* `AnchoredMenu` — the floating option list shared by `SelectControl` and `TypeaheadControl`.
|
|
495
|
+
*
|
|
496
|
+
* Renders an in-tree, absolutely-positioned popover under its anchor (the pattern aml-v2's
|
|
497
|
+
* CountryPicker proved: `position:absolute; top:100%; zIndex; elevation`), so it floats over
|
|
498
|
+
* sibling fields without a portal. On web it dismisses on outside-click / Escape; on native the
|
|
499
|
+
* caller closes it on blur/select. Purely presentational + theme-flat, so it stays reusable.
|
|
500
|
+
*
|
|
501
|
+
* Promoted verbatim out of `@dloizides/ui-tables`' private `Filters/components/AnchoredMenu`.
|
|
502
|
+
*
|
|
503
|
+
* Deliberately in-tree (no `ui-layout` dependency). That was a considered choice in `ui-tables`
|
|
504
|
+
* and it still holds here: `ui-forms` is a low-level primitive and must not take a runtime
|
|
505
|
+
* dependency on the higher-level `ui-layout` `ModalDropdown` — that would invert the graph for
|
|
506
|
+
* every consumer. Apps needing the responsive modal/bottom-sheet variant inject it instead.
|
|
507
|
+
*/
|
|
508
|
+
|
|
509
|
+
/** The four theme colours the menu paints with — passed in, so the menu stays theme-flat. */
|
|
510
|
+
interface AnchoredMenuColors {
|
|
511
|
+
text: string;
|
|
512
|
+
border: string;
|
|
513
|
+
surface: string;
|
|
514
|
+
brand: string;
|
|
515
|
+
}
|
|
516
|
+
interface AnchoredMenuProps {
|
|
517
|
+
options: readonly ControlOption[];
|
|
518
|
+
/** The currently-selected value, highlighted in the list ('' = none). */
|
|
519
|
+
selectedValue: string;
|
|
520
|
+
onSelect: (value: string) => void;
|
|
521
|
+
onDismiss: () => void;
|
|
522
|
+
colors: AnchoredMenuColors;
|
|
523
|
+
/** PRE-LOCALIZED accessibility hint applied to every option. */
|
|
524
|
+
optionHint: string;
|
|
525
|
+
/** Base testID: the menu is `${testID}-menu`, each option `${testID}-option-${value}`. */
|
|
526
|
+
testID: string;
|
|
527
|
+
/** Node whose clicks count as "inside" (the anchor) so its own press doesn't self-dismiss. */
|
|
528
|
+
anchorRef: React.RefObject<View | null>;
|
|
529
|
+
}
|
|
530
|
+
declare function AnchoredMenu({ options, selectedValue, onSelect, onDismiss, colors, optionHint, testID, anchorRef, }: AnchoredMenuProps): React.ReactElement;
|
|
531
|
+
|
|
532
|
+
declare const controlStyles: {
|
|
533
|
+
/** The shared bordered box every dense text control renders in. */
|
|
534
|
+
input: {
|
|
535
|
+
borderWidth: number;
|
|
536
|
+
borderRadius: number;
|
|
537
|
+
paddingHorizontal: number;
|
|
538
|
+
paddingVertical: number;
|
|
539
|
+
fontSize: number;
|
|
540
|
+
};
|
|
541
|
+
/** The select trigger: a bordered field box matching the text inputs + a chevron. */
|
|
542
|
+
selectTrigger: {
|
|
543
|
+
flexDirection: "row";
|
|
544
|
+
alignItems: "center";
|
|
545
|
+
justifyContent: "space-between";
|
|
546
|
+
gap: number;
|
|
547
|
+
borderWidth: number;
|
|
548
|
+
borderRadius: number;
|
|
549
|
+
paddingHorizontal: number;
|
|
550
|
+
paddingVertical: number;
|
|
551
|
+
};
|
|
552
|
+
selectTriggerText: {
|
|
553
|
+
fontSize: number;
|
|
554
|
+
flexShrink: number;
|
|
555
|
+
};
|
|
556
|
+
chevron: {
|
|
557
|
+
fontSize: number;
|
|
558
|
+
};
|
|
559
|
+
dateRow: {
|
|
560
|
+
flexDirection: "row";
|
|
561
|
+
gap: number;
|
|
562
|
+
};
|
|
563
|
+
dateCol: {
|
|
564
|
+
gap: number;
|
|
565
|
+
flex: number;
|
|
566
|
+
};
|
|
567
|
+
/**
|
|
568
|
+
* The micro-caption above a sub-control (a date range's From/To) and beside a switch. Note it
|
|
569
|
+
* is deliberately NOT the `control` label voice: it carries no `700`/uppercase, because it
|
|
570
|
+
* names a HALF of a control that already has a `control`-voice label above it. Uppercasing it
|
|
571
|
+
* too would make one field shout twice.
|
|
572
|
+
*/
|
|
573
|
+
subLabel: {
|
|
574
|
+
fontSize: number;
|
|
575
|
+
letterSpacing: number;
|
|
576
|
+
};
|
|
577
|
+
/** Positioning context for a floating `AnchoredMenu` (`position:absolute; top:100%`). */
|
|
578
|
+
anchor: {
|
|
579
|
+
position: "relative";
|
|
580
|
+
};
|
|
581
|
+
menu: {
|
|
582
|
+
position: "absolute";
|
|
583
|
+
top: "100%";
|
|
584
|
+
left: number;
|
|
585
|
+
marginTop: number;
|
|
586
|
+
minWidth: number;
|
|
587
|
+
maxHeight: number;
|
|
588
|
+
borderWidth: number;
|
|
589
|
+
borderRadius: number;
|
|
590
|
+
paddingVertical: number;
|
|
591
|
+
zIndex: number;
|
|
592
|
+
boxShadow: string;
|
|
593
|
+
elevation: number;
|
|
594
|
+
overflow: "hidden";
|
|
595
|
+
};
|
|
596
|
+
option: {
|
|
597
|
+
paddingHorizontal: number;
|
|
598
|
+
paddingVertical: number;
|
|
599
|
+
};
|
|
600
|
+
optionText: {
|
|
601
|
+
fontSize: number;
|
|
602
|
+
};
|
|
603
|
+
error: {
|
|
604
|
+
fontSize: number;
|
|
605
|
+
marginTop: number;
|
|
606
|
+
};
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* `suggestOptions` — the ranked substring matcher behind `TypeaheadControl`, generalized from
|
|
611
|
+
* aml-v2's `suggestCountries` and promoted verbatim out of `@dloizides/ui-tables`' private
|
|
612
|
+
* `Filters/utils/suggest.ts`.
|
|
613
|
+
*
|
|
614
|
+
* Ranks: value exact (0) > value prefix (1) > label prefix (2) > label contains (3); ties broken
|
|
615
|
+
* by `label.localeCompare`. Empty/short queries return `[]` — nothing surfaces until `minChars`
|
|
616
|
+
* is typed, mirroring a native `<datalist>`.
|
|
617
|
+
*/
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Rank + cap the options matching `query`. Returns `[]` until `query` (trimmed) is at least
|
|
621
|
+
* `minChars` long, then the best `limit` matches, ranked as documented above.
|
|
622
|
+
*/
|
|
623
|
+
declare function suggestOptions(options: readonly ControlOption[], query: string, minChars: number, limit: number): readonly ControlOption[];
|
|
624
|
+
/** True when `text` is non-empty yet matches no option's value or label exactly (case-insensitive). */
|
|
625
|
+
declare function isUnmatched(options: readonly ControlOption[], text: string): boolean;
|
|
626
|
+
|
|
627
|
+
export { AnchoredMenu, type AnchoredMenuColors, type AnchoredMenuProps, type ChipOption, ChipSelector, type ChipSelectorProps, type ChipVariant, type ControlOption, DEFAULT_OPTIONAL_LABEL, DEFAULT_TYPEAHEAD_MAX_SUGGESTIONS, DEFAULT_TYPEAHEAD_MIN_CHARS, DateRangeControl, type DateRangeControlProps, type DateRangeValue, Field, type FieldChildContext, type FieldChildren, type FieldLabelVariant, type FieldMarker, type FieldProps, FormActions, type FormActionsProps, FormCheckbox, type FormCheckboxProps, FormField, type FormFieldProps, FormSwitch, type FormSwitchProps, SelectControl, type SelectControlProps, type ThemedInput, ThemedTextInput, type ThemedTextInputProps, TypeaheadControl, type TypeaheadControlProps, type UseThemedInputOptions, controlStyles, isUnmatched, suggestOptions, useThemedInput };
|