asma-ui-core 3.111.0 → 3.113.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.
Files changed (36) hide show
  1. package/dist/asma-ui-core.css +1 -1
  2. package/dist/components/inputs/dynamic-select/components/DynamicSelectAutocomplete.js +95 -93
  3. package/dist/components/inputs/full-screen-picker/FullScreenPicker.js +93 -0
  4. package/dist/components/inputs/full-screen-picker/PickerLiveRegion.js +34 -0
  5. package/dist/components/inputs/full-screen-picker/pickerCopy.js +34 -0
  6. package/dist/components/inputs/full-screen-picker/useVisualViewportHeight.js +10 -0
  7. package/dist/components/inputs/input-field/StyledInputField.js +147 -157
  8. package/dist/components/inputs/phone-field/CountryCodeOptions.js +28 -24
  9. package/dist/components/inputs/phone-field/CountryCodeSelect.js +82 -75
  10. package/dist/components/inputs/select-autocomplete/SelectAutocompletePicker.js +217 -0
  11. package/dist/components/inputs/select-autocomplete/StyledSelectAutocomplete.js +270 -220
  12. package/dist/components/inputs/select-autocomplete/draftChanges.js +24 -0
  13. package/dist/components/inputs/select-autocomplete/listboxCursor.js +7 -0
  14. package/dist/components/inputs/select-autocomplete/readFieldName.js +18 -0
  15. package/dist/components/inputs/textarea/StyledTextarea.js +46 -47
  16. package/dist/hooks/useMediaQuery.hook.js +12 -8
  17. package/dist/index.js +137 -133
  18. package/dist/src/components/inputs/dynamic-select/StyledDynamicSelect.d.ts +4 -1
  19. package/dist/src/components/inputs/full-screen-picker/FullScreenPicker.d.ts +62 -0
  20. package/dist/src/components/inputs/full-screen-picker/PickerLiveRegion.d.ts +31 -0
  21. package/dist/src/components/inputs/full-screen-picker/index.d.ts +2 -0
  22. package/dist/src/components/inputs/full-screen-picker/pickerCopy.d.ts +29 -0
  23. package/dist/src/components/inputs/full-screen-picker/useVisualViewportHeight.d.ts +8 -0
  24. package/dist/src/components/inputs/input-field/StyledInputField.d.ts +4 -1
  25. package/dist/src/components/inputs/phone-field/CountryCodeOptions.d.ts +9 -1
  26. package/dist/src/components/inputs/phone-field/CountryCodeSelect.d.ts +4 -3
  27. package/dist/src/components/inputs/phone-field/useCountryPicker.d.ts +2 -1
  28. package/dist/src/components/inputs/select-autocomplete/SelectAutocompletePicker.d.ts +52 -0
  29. package/dist/src/components/inputs/select-autocomplete/StyledSelectAutocomplete.d.ts +21 -2
  30. package/dist/src/components/inputs/select-autocomplete/draftChanges.d.ts +17 -0
  31. package/dist/src/components/inputs/select-autocomplete/listboxCursor.d.ts +9 -0
  32. package/dist/src/components/inputs/select-autocomplete/readFieldName.d.ts +13 -0
  33. package/dist/src/helpers/CharacterCounter.d.ts +2 -1
  34. package/dist/src/hooks/useMediaQuery.hook.d.ts +6 -0
  35. package/dist/src/index.d.ts +1 -0
  36. package/package.json +1 -1
@@ -0,0 +1,62 @@
1
+ import { ReactNode } from 'react';
2
+ import { DialogCloseReason } from '../../feedback/dialog/StyledDialog';
3
+ import { PickerAnnouncement } from './PickerLiveRegion';
4
+ export type FullScreenPickerCloseReason = DialogCloseReason | 'closeButton';
5
+ export interface FullScreenPickerProps {
6
+ open: boolean;
7
+ /** Close button, Escape and native Back. Always a cancel: the caller discards anything not applied. */
8
+ onClose: (reason: FullScreenPickerCloseReason) => void;
9
+ dataTest: string;
10
+ /** The field label. Wraps onto as many lines as it needs; it is never truncated. */
11
+ title?: ReactNode;
12
+ /** Accessible name of the dialog when there is no `title`. */
13
+ fallbackName?: string;
14
+ /** Accessible name of the close button. */
15
+ closeLabel: string;
16
+ /** The search field — rendered full width under the title, or beside it on a short viewport. */
17
+ search: ReactNode;
18
+ /** The selection row (several choices) or the clear row (one choice). Part of the fixed header. */
19
+ headerRow?: ReactNode;
20
+ /** The list. It is the only part that scrolls. */
21
+ children: ReactNode;
22
+ /** Polite announcement rendered inside the dialog (counts, matches, clears). */
23
+ announcement?: PickerAnnouncement;
24
+ }
25
+ /**
26
+ * @figmaNode wXrXt5uKNNzV2DnQCgyYZH#45614-217265
27
+ * Figma "Select mobile full-screen picker": a full-screen sheet with a fixed **Header** (title, close,
28
+ * search, optional selection/clear row; 1 px `border/separator` delta-200 under it) and one scrolling
29
+ * **List**. Title = Mobile/Page title, SemiBold 20/28, `text-icon/title-label` delta-800, wraps.
30
+ * Close = 40 × 40 Quaternary button with a 24 px ×. Search row = `pt-8 px-16`. On a short viewport
31
+ * (`Variant2`, ≤ 480 px high) title, search (max 240 px) and close share the first row.
32
+ *
33
+ * The shared shell for every full-screen picker in ui-core: `StyledSelectAutocomplete` and
34
+ * `CountryCodeSelect` both render through it (ASMA-8468). Presentation only — the caller owns the
35
+ * query, the list, the keyboard and what closing means. Nothing is focused for typing on open, so
36
+ * the on-screen keyboard appears only when the user taps the search field (REQ-006). Opened from
37
+ * the keyboard, focus goes to the `listbox` in `children` when it is a Tab stop (else to the close
38
+ * button), where the arrows work at once. Native Back comes from `StyledDialog` (D-004).
39
+ *
40
+ * For a project's own picker (exported from `asma-ui-core`): render it below the same breakpoint
41
+ * as the selects (`max-width: 743px` or `max-height: 480px`); pass the search field and the list;
42
+ * make the list a `role="listbox"` with `tabIndex={0}` and `aria-activedescendant` if it should
43
+ * take keyboard focus on open; announce counts with `useAnnouncer`.
44
+ *
45
+ * @example
46
+ * const [announcement, announce] = useAnnouncer()
47
+ * <FullScreenPicker
48
+ * open={open}
49
+ * onClose={() => setOpen(false)} // ×, Escape and Back: a cancel
50
+ * dataTest='templates-picker'
51
+ * title='Add questionnaires'
52
+ * closeLabel='Close'
53
+ * search={<StyledSearchField dataTest='templates-search' label='Search' value={query} fullWidth onChange={…} />}
54
+ * headerRow={<MySelectionRow />} // optional: count, clear, Done
55
+ * announcement={announcement}
56
+ * >
57
+ * <ul role='listbox' aria-label='Questionnaires' tabIndex={0} aria-activedescendant={activeId}>…</ul>
58
+ * </FullScreenPicker>
59
+ *
60
+ * @see asma-modules/_docs/frontend/plans/2026-10-19-11-04-plan-full-screen-select-picker-phones.md:203 — TASK-004
61
+ */
62
+ export declare const FullScreenPicker: ({ open, onClose, dataTest, title, fallbackName, ...sheet }: FullScreenPickerProps) => JSX.Element;
@@ -0,0 +1,31 @@
1
+ /** One message for a live region. `id` changes on every `announce`, so a repeated text is read again. */
2
+ export interface PickerAnnouncement {
3
+ text: string;
4
+ id: number;
5
+ }
6
+ /**
7
+ * Holds the latest announcement for `FullScreenPicker`'s `announcement` prop, which a screen reader
8
+ * reads out politely from inside the dialog (counts, matches, *No matches*). `announce` accepts the
9
+ * same text twice and still re-announces it; `reset` forgets it, so the next opening starts silent.
10
+ *
11
+ * @example
12
+ * const [announcement, announce, resetAnnouncement] = useAnnouncer()
13
+ * // on a tick: announce(`${ticked.length} selected`); when the picker opens: resetAnnouncement()
14
+ * <FullScreenPicker announcement={announcement} … />
15
+ */
16
+ export declare const useAnnouncer: () => [PickerAnnouncement | undefined, (text: string) => void, () => void];
17
+ /**
18
+ * Polite live region for the picker (ASMA-8468 REQ-011/013/016). Inside the dialog it must be
19
+ * rendered **in** the dialog: while `showModal()` is active everything outside is inert and not
20
+ * reliably announced (ALT-005).
21
+ *
22
+ * The text is emptied and set again on the next frame, so the same message twice in a row
23
+ * (`0 valgt` after two clears) is read out again. Both steps write the DOM directly, not React
24
+ * state: two state updates that land before React renders merge into "no change", and an unchanged
25
+ * region is not read out again. The frame's delay also lets a closing dialog leave the top layer
26
+ * first, so a message set as it closes is heard.
27
+ */
28
+ export declare const PickerLiveRegion: ({ announcement, dataTest, }: {
29
+ announcement?: PickerAnnouncement;
30
+ dataTest?: string;
31
+ }) => JSX.Element;
@@ -0,0 +1,2 @@
1
+ export { FullScreenPicker, type FullScreenPickerProps, type FullScreenPickerCloseReason } from './FullScreenPicker';
2
+ export { useAnnouncer } from './PickerLiveRegion';
@@ -0,0 +1,29 @@
1
+ import { UiCoreLocale } from '../../../helpers/uiCoreLocale';
2
+ /**
3
+ * Every string the full-screen picker shows or announces (ASMA-8468). ui-core ships Norwegian and
4
+ * English defaults; a consumer overrides any of them through `pickerText`.
5
+ *
6
+ * @see asma-modules/_docs/frontend/plans/2026-10-19-11-04-plan-full-screen-select-picker-phones.md:206 — TASK-007
7
+ */
8
+ export interface PickerText {
9
+ /** Accessible name of the close button. */
10
+ close: string;
11
+ /** Label of the search field. */
12
+ search: string;
13
+ /** Several choices: applies the ticks and closes. */
14
+ done: string;
15
+ /** One choice, optional field: empties the field. */
16
+ clearOne: string;
17
+ /** Several choices: unticks everything, including ticks hidden by the search. */
18
+ clearAll: string;
19
+ selectedCount: (count: number) => string;
20
+ noMatches: string;
21
+ matchCount: (count: number) => string;
22
+ /** Announced after `clearOne`. */
23
+ cleared: string;
24
+ /** Announced after `clearAll`. */
25
+ clearedAll: string;
26
+ truncated: (shown: number, total: number) => string;
27
+ }
28
+ /** Default picker copy for `locale`, with any consumer overrides applied on top. */
29
+ export declare const getPickerText: (locale: UiCoreLocale, overrides?: Partial<PickerText>) => PickerText;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The visible height in CSS px — the part of the screen the on-screen keyboard does not cover.
3
+ *
4
+ * iOS Safari keeps `100%`/`dvh` at the full layout height while the keyboard is up, so a list sized
5
+ * by CSS alone ends behind the keyboard. The picker sizes its column with this instead (ASMA-8468
6
+ * REQ-008, RISK-004 — verify on a real device).
7
+ */
8
+ export declare const useVisualViewportHeight: () => number;
@@ -44,7 +44,10 @@ export interface StyledInputFieldProps {
44
44
  expandHelperText?: boolean;
45
45
  /** Character limit; also readable from `slotProps.htmlInput.maxLength`. */
46
46
  maxLength?: number;
47
- /** Shows the count against `maxLength` as the user types, for free text with a limit. */
47
+ /**
48
+ * Count against `maxLength`, on by default whenever the field has a limit (WID-12). Pass `false`
49
+ * for a fixed format such as a postal code, whose length the label or hint already states.
50
+ */
48
51
  counter?: boolean;
49
52
  /** Language of the counter text. */
50
53
  locale?: UiCoreLocale;
@@ -1,3 +1,4 @@
1
+ import { KeyboardEvent } from 'react';
1
2
  import { PhoneCountryChoice, RenderCountryFlag } from './types';
2
3
  export interface CountryCodeOptionsProps {
3
4
  dataTest: string;
@@ -12,9 +13,16 @@ export interface CountryCodeOptionsProps {
12
13
  label: string;
13
14
  renderFlag?: RenderCountryFlag;
14
15
  className?: string;
16
+ /**
17
+ * Makes the list a Tab stop of its own (the mobile sheet, ASMA-8468), driven by these keys and
18
+ * the 3 px active-row marker. Without it the list is only driven from the combobox (desktop).
19
+ */
20
+ onKeyDown?: (event: KeyboardEvent<HTMLElement>) => void;
21
+ /** Tabbing into the list: put the active-row marker on a row. */
22
+ onFocus?: () => void;
15
23
  }
16
24
  /**
17
25
  * The country listbox. Presentation only — filtering, the active row and the keyboard live in
18
26
  * [[useCountryPicker]], so the desktop trigger and the mobile sheet render the same list.
19
27
  */
20
- export declare const CountryCodeOptions: ({ dataTest, visible, selectedIso2, activeIndex, optionId, onSelect, listId, label, renderFlag, className, }: CountryCodeOptionsProps) => JSX.Element;
28
+ export declare const CountryCodeOptions: ({ dataTest, visible, selectedIso2, activeIndex, optionId, onSelect, listId, label, renderFlag, className, onKeyDown, onFocus, }: CountryCodeOptionsProps) => JSX.Element;
@@ -23,8 +23,9 @@ export interface CountryCodeSelectProps {
23
23
  * - **Desktop** — the trigger *is* the combobox. Opening it turns the calling-code slot into an
24
24
  * input and typing filters the list below, which is what Figma draws (the open trigger carries a
25
25
  * text cursor, and the menu holds rows only — node 8565:284950 has no search field).
26
- * - **Mobile** — a full-screen sheet with its own search box, per node 8699:112423. The break is
27
- * `StyledDialog`'s own `fullScreen ?? isMobile` default (≤743 px), so the two never disagree
28
- * about what "mobile" means.
26
+ * - **Mobile** — a full-screen sheet with its own search box, per node 8699:112423, drawn by the
27
+ * shared `FullScreenPicker` (ASMA-8468). The break is the selects' picker break — below 744 px
28
+ * wide **or** at most 480 px high (D-002) — so a landscape phone gets the sheet too, not a
29
+ * popover squeezed against the keyboard.
29
30
  */
30
31
  export declare const CountryCodeSelect: ({ dataTest, countries, value, onChange, disabled, selectCountryLabel, searchPlaceholder, renderFlag, labelledBy, className, }: CountryCodeSelectProps) => JSX.Element;
@@ -18,7 +18,8 @@ export interface CountryPicker {
18
18
  activeIndex: number | null;
19
19
  optionId: (index: number) => string;
20
20
  openWithKeyboard: (direction: -1 | 1) => void;
21
- select: (iso2: string) => void;
21
+ /** Chooses a country; `close = false` keeps the list open (Space). */
22
+ select: (iso2: string, close?: boolean) => void;
22
23
  dismiss: () => void;
23
24
  handleKeyDown: (event: KeyboardEvent<HTMLElement>) => void;
24
25
  }
@@ -0,0 +1,52 @@
1
+ import { ReactNode, SyntheticEvent } from 'react';
2
+ import { PickerText } from '../full-screen-picker/pickerCopy';
3
+ import { AutocompleteRenderOptionState, OptionLiProps } from './StyledSelectAutocomplete';
4
+ export interface SelectAutocompletePickerProps<T> {
5
+ open: boolean;
6
+ dataTest: string;
7
+ listboxId: string;
8
+ title?: ReactNode;
9
+ /** Plain-text field name: names the listbox, and the dialog when there is no `title`. */
10
+ name: string;
11
+ text: PickerText;
12
+ options: readonly T[];
13
+ multiple: boolean;
14
+ /** Current value: the option (or `null`) with one choice, the chosen options with several. */
15
+ value: T | null | readonly T[];
16
+ /** The field may be emptied: not `disableClearable`, not read-only. */
17
+ clearable: boolean;
18
+ isEqual: (a: T, b: T) => boolean;
19
+ isOptionDisabled: (option: T) => boolean;
20
+ getLabel: (option: T) => string;
21
+ getOptionKey?: (option: T) => string | number;
22
+ filterOptions?: (options: T[], state: {
23
+ inputValue: string;
24
+ }) => T[];
25
+ renderOptionRow: (props: OptionLiProps, option: T, state: AutocompleteRenderOptionState) => ReactNode;
26
+ optionRowClassName: string;
27
+ loading?: boolean;
28
+ loadingText: ReactNode;
29
+ noOptionsText: ReactNode;
30
+ allowSelectAll?: boolean;
31
+ selectAllLabel: string;
32
+ /**
33
+ * One choice: an option was chosen. The caller applies it, and closes when `close` is set — a tap
34
+ * or Enter; Space chooses and keeps the picker open, as in the attached list.
35
+ */
36
+ onSelect: (event: SyntheticEvent, option: T, close: boolean) => void;
37
+ /** One choice: *Fjern valget*. The caller empties the field, closes and announces. */
38
+ onClear: (event: SyntheticEvent) => void;
39
+ /** Several choices: *Ferdig*. The caller applies the draft (see `getDraftChanges`) and closes. */
40
+ onApply: (event: SyntheticEvent, draft: T[]) => void;
41
+ /** Close button, Escape, Back: a cancel — nothing is applied. */
42
+ onCancel: () => void;
43
+ }
44
+ /**
45
+ * The body of `StyledSelectAutocomplete` on a small viewport (ASMA-8468): its own search query
46
+ * (the field's text is left alone), filtering, the 50-row cap, the keyboard, and the choice rules —
47
+ * one choice closes on tap; several choices tick a draft that only *Ferdig* applies. The draft and
48
+ * the clear-row visibility are taken at the moment it opens (REQ-011).
49
+ *
50
+ * @see asma-modules/_docs/frontend/plans/2026-10-19-11-04-plan-full-screen-select-picker-phones.md:218 — TASK-012..018
51
+ */
52
+ export declare function SelectAutocompletePicker<T>({ open, dataTest, listboxId, title, name, text, options, multiple, value, clearable, isEqual, isOptionDisabled, getLabel, getOptionKey, filterOptions, renderOptionRow, optionRowClassName, loading, loadingText, noOptionsText, allowSelectAll, selectAllLabel, onSelect, onClear, onApply, onCancel, }: SelectAutocompletePickerProps<T>): JSX.Element;
@@ -1,11 +1,14 @@
1
1
  import { CSSProperties, HTMLAttributes, KeyboardEvent, ReactNode, SyntheticEvent } from 'react';
2
2
  import { FieldWidth } from '../field-styles';
3
+ import { PickerText } from '../full-screen-picker/pickerCopy';
3
4
  export type AutocompleteChangeReason = 'createOption' | 'selectOption' | 'removeOption' | 'clear' | 'blur';
4
5
  export type AutocompleteInputChangeReason = 'input' | 'reset' | 'clear';
5
6
  export interface AutocompleteRenderOptionState {
6
7
  selected: boolean;
7
8
  index: number;
8
9
  inputValue: string;
10
+ /** The row is drawn in the full-screen picker (ASMA-8468): show the full label, no line clamp. */
11
+ inPicker?: boolean;
9
12
  }
10
13
  /** Props the combobox hands to `renderInput`; spread straight onto `StyledInputField`. */
11
14
  export interface AutocompleteRenderInputParams {
@@ -29,7 +32,7 @@ export interface AutocompleteRenderInputParams {
29
32
  };
30
33
  };
31
34
  }
32
- type OptionLiProps = HTMLAttributes<HTMLLIElement> & {
35
+ export type OptionLiProps = HTMLAttributes<HTMLLIElement> & {
33
36
  key?: string;
34
37
  ref?: React.Ref<HTMLLIElement>;
35
38
  [dataAttr: `data-${string}`]: unknown;
@@ -128,6 +131,22 @@ export interface StyledSelectAutocompleteProps<T, Multiple extends boolean | und
128
131
  };
129
132
  };
130
133
  sx?: unknown;
134
+ /**
135
+ * Title of the full-screen picker on a small viewport (ASMA-8468). Defaults to the field's label,
136
+ * read when the picker opens from the input's `<label>`, `aria-labelledby` or `aria-label`. With
137
+ * none, there is no title and the dialog and list are named by the placeholder, else by
138
+ * `pickerText.search`.
139
+ */
140
+ pickerTitle?: ReactNode;
141
+ /** Overrides for any picker string (*Ferdig*, *Fjern valget*, counts, …). */
142
+ pickerText?: Partial<PickerText>;
143
+ /** Language of the default picker copy. Defaults to English, like the rest of ui-core. */
144
+ locale?: 'no' | 'en';
145
+ /**
146
+ * Keep the attached list on every viewport. By default the list opens as a full-screen picker
147
+ * below 744 CSS px wide or at most 480 CSS px high (ASMA-8468).
148
+ */
149
+ disableFullScreenPicker?: boolean;
131
150
  }
132
151
  /**
133
152
  * single + multiple selection (with tag chips + optional select-all), type-ahead filtering,
@@ -136,5 +155,5 @@ export interface StyledSelectAutocompleteProps<T, Multiple extends boolean | und
136
155
  * ponytail: `freeSolo` is accepted (arbitrary input is emitted via onInputChange) but not turned
137
156
  * into option values; exact keyboard-parity edge cases are Chromatic/axe-gated.
138
157
  */
139
- export declare function StyledSelectAutocomplete<T, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false>({ dataTest, options, renderInput, value, onChange, getOptionLabel, getOptionKey, isOptionEqualToValue, renderOption, renderValue, filterOptions, loading, loadingText, noOptionsText, disabled, readOnly, error, helperText, size, rowSize, disableClearable, disableCloseOnSelect, popupIcon, autoHeight, multiple, allowSelectAll, selectAllLabel, width, fullWidth: _fullWidth, classes, inputValue: controlledInput, onInputChange, open: controlledOpen, onOpen, onClose, className, wrapperClassName, getOptionDisabled, slotProps, }: StyledSelectAutocompleteProps<T, Multiple, DisableClearable, FreeSolo>): JSX.Element;
158
+ export declare function StyledSelectAutocomplete<T, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false>({ dataTest, options, renderInput, value, onChange, getOptionLabel, getOptionKey, isOptionEqualToValue, renderOption, renderValue, filterOptions, loading, loadingText, noOptionsText, disabled, readOnly, error, helperText, size, rowSize, disableClearable, disableCloseOnSelect, popupIcon, autoHeight, multiple, allowSelectAll, selectAllLabel, width, fullWidth: _fullWidth, classes, inputValue: controlledInput, onInputChange, open: controlledOpen, onOpen, onClose, className, wrapperClassName, getOptionDisabled, slotProps, pickerTitle, pickerText, locale, disableFullScreenPicker, }: StyledSelectAutocompleteProps<T, Multiple, DisableClearable, FreeSolo>): JSX.Element;
140
159
  export {};
@@ -0,0 +1,17 @@
1
+ export type DraftChange<T> = {
2
+ value: T[];
3
+ reason: 'removeOption' | 'selectOption';
4
+ option: T;
5
+ } | {
6
+ value: T[];
7
+ reason: 'clear';
8
+ option?: undefined;
9
+ };
10
+ /**
11
+ * The `onChange` calls that turn `applied` into `draft` the way ticking in the attached list would
12
+ * have (ASMA-8468 D-007): one `removeOption`/`selectOption` per changed option, carrying
13
+ * `details.option`, so consumers that act per tick keep working. Removals come first, additions in
14
+ * the order they were ticked. Emptying a non-empty value is one `clear`, as the field's × does. No
15
+ * change, no calls.
16
+ */
17
+ export declare const getDraftChanges: <T>(applied: readonly T[], draft: readonly T[], isEqual: (a: T, b: T) => boolean) => DraftChange<T>[];
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Where ArrowUp/ArrowDown moves the keyboard cursor of a select listbox. Shared by the attached list
3
+ * and the full-screen picker (ASMA-8468), which must step the same way.
4
+ *
5
+ * `stops` are the cursor positions in order (`-1` is the *Select all* row). Without a cursor the
6
+ * move starts from `fallback` (the chosen option); from outside the stops it lands on the first or
7
+ * the last one. Clamped at both ends, no wrap-around. `null` when there is nowhere to go.
8
+ */
9
+ export declare const nextActiveIndex: (stops: readonly number[], current: number | null, fallback: number, direction: -1 | 1) => number | null;
@@ -0,0 +1,13 @@
1
+ export interface FieldName {
2
+ /** The field's label: shown as the picker title. */
3
+ label: string;
4
+ /** What names the dialog and list when there is no label: the label, else the placeholder. */
5
+ name: string;
6
+ }
7
+ /**
8
+ * The field's name for the full-screen picker (ASMA-8468 TASK-015), in the order a browser names an
9
+ * input: its first `<label>`, else the elements named by `aria-labelledby`, else `aria-label` — a
10
+ * visible label that is not linked to the input usually reaches it that way (RISK-002). A
11
+ * placeholder only names the dialog and list, it is no title. Empty strings when there is nothing.
12
+ */
13
+ export declare const readFieldName: (input: HTMLInputElement | null) => FieldName;
@@ -11,6 +11,7 @@ export interface CharacterCounterProps {
11
11
  }
12
12
  /**
13
13
  * The count of a free-text field with a character limit, such as *12 av 100 tegn* (field width
14
- * pattern WID-2, Q12), so typed text is never cut without notice.
14
+ * pattern WID-2, WID-12), so typed text is never cut without notice.
15
+ * @figmaNode wXrXt5uKNNzV2DnQCgyYZH#45907-218317
15
16
  */
16
17
  export declare const CharacterCounter: ({ id, length, limit, locale, className }: CharacterCounterProps) => JSX.Element;
@@ -2,3 +2,9 @@ export declare function makeMediaQueryStore(mediaQuery: string): () => boolean;
2
2
  export declare const useMobileMediaQuery: () => boolean;
3
3
  /** A phone in landscape: too short for a sheet capped at 90vh to leave a usable body. */
4
4
  export declare const useShortViewportMediaQuery: () => boolean;
5
+ /**
6
+ * ASMA-8468 REQ-001: a searchable select opens as a full-screen picker below 744 CSS px wide or at
7
+ * most 480 CSS px high. The height half is the same boundary as {@link useShortViewportMediaQuery}
8
+ * (D-003), so a landscape phone counts as "small" for every overlay.
9
+ */
10
+ export declare const useFullScreenPickerMediaQuery: () => boolean;
@@ -21,6 +21,7 @@ export * from './components/inputs/search-field';
21
21
  export * from './components/inputs/textarea';
22
22
  export * from './components/inputs/select';
23
23
  export * from './components/inputs/select-autocomplete';
24
+ export * from './components/inputs/full-screen-picker';
24
25
  export * from './components/inputs/slider';
25
26
  export * from './components/inputs/switch';
26
27
  export * from './components/inputs/radio-button';
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "3.111.0",
6
+ "version": "3.113.0",
7
7
  "type": "module",
8
8
  "sideEffects": [
9
9
  "**/*.css",